Skip to content

Commit

Permalink
Merge branch 'staging' of https://github.com/brainstormforce/force-ui
Browse files Browse the repository at this point in the history
…into stories-1
  • Loading branch information
vrundakansara committed Sep 18, 2024
2 parents b1441ee + 0bebea5 commit f07932c
Show file tree
Hide file tree
Showing 17 changed files with 808 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: "Chromatic"
on:
push:
branches:
- storybook-setup
- staging

jobs:
chromatic:
Expand Down
2 changes: 1 addition & 1 deletion dist/force-ui-rtl.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/force-ui.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom'), 'version' => 'a1a21a8168c2db91c31c');
<?php return array('dependencies' => array('react', 'react-dom'), 'version' => '37a62a0e88dc6a48352f');
2 changes: 1 addition & 1 deletion dist/force-ui.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/force-ui.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bsf/force-ui",
"version": "1.0.0",
"version": "0.0.2",
"description": "Library of components for the BSF project",
"main": "dist/force-ui.js",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions src/components/editor-input/editor-input-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export const editorCommonClassNames = 'border border-solid focus-within:ring-2 f
export const editorDisabledClassNames = 'bg-field-secondary-background border-field-border-disabled hover:border-field-border-disabled [&_p]:text-badge-color-disabled cursor-not-allowed';

export const editorInputClassNames = {
sm: 'py-1.5 px-2 rounded [&_.editor-content>p]:text-xs [&_.editor-content>p]:font-medium [&_.editor-content>p]:leading-[1.625rem]',
md: 'py-2 px-2.5 rounded-md [&_.editor-content>p]:text-xs [&_.editor-content>p]:font-medium [&_.editor-content>p]:leading-[1.625rem]',
sm: 'py-1.5 px-2 rounded [&_.editor-content>p]:text-sm [&_.editor-content>p]:font-medium [&_.editor-content>p]:leading-[1.625rem]',
md: 'py-2 px-2.5 rounded-md [&_.editor-content>p]:text-sm [&_.editor-content>p]:font-medium [&_.editor-content>p]:leading-[1.625rem]',
lg: 'py-2.5 px-3 rounded-md [&_.editor-content>p]:text-sm [&_.editor-content>p]:font-medium [&_.editor-content>p]:leading-[1.6875rem]',
};

Expand Down
2 changes: 2 additions & 0 deletions src/components/editor-input/editor-input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const EditorInputComponent = ( {
autoFocus = false,
options = [],
by = 'name',
trigger = '@',
menuComponent,
menuItemComponent,
className,
Expand Down Expand Up @@ -118,6 +119,7 @@ const EditorInputComponent = ( {
size={ size }
by={ by }
optionsArray={ options }
trigger={ trigger }
/>
<OnChangePlugin
onChange={ handleOnChange }
Expand Down
134 changes: 58 additions & 76 deletions src/components/editor-input/mention-plugin/mention-plugin.jsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,77 @@
import { useCallback, useState, useMemo } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import {
LexicalTypeaheadMenuPlugin,
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin';
import { $createMentionNode } from './mention-node';
import OptionItem from './mention-option-item';
import useMentionLookupService from './mention-hooks';
import EditorCombobox from './mention-combobox';

const PUNCTUATION =
'\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';

const TRIGGERS = [ '@' ].join( '' );

const VALID_CHARS = '[^' + TRIGGERS + PUNCTUATION + '\\s]';

const VALID_JOINS =
'(?:' +
'\\.[ |$]|' + // E.g. "r. " in "Mr. Smith"
' |' + // E.g. " " in "Josh Duck"
'[' +
PUNCTUATION +
']|' + // E.g. "-' in "Salier-Hellendag"
')';

const LENGTH_LIMIT = 75;

const AtSignMentionsRegex = new RegExp(
'(^|\\s|\\()(' +
'[' +
TRIGGERS +
']' +
'((?:' +
VALID_CHARS +
VALID_JOINS +
'){0,' +
LENGTH_LIMIT +
'})' +
')$'
);

// 50 is the longest alias length limit.
const ALIAS_LENGTH_LIMIT = 50;

// Regex used to match alias.
const AtSignMentionsRegexAliasRegex = new RegExp(
'(^|\\s|\\()(' +
'[' +
TRIGGERS +
']' +
'((?:' +
VALID_CHARS +
'){0,' +
ALIAS_LENGTH_LIMIT +
'})' +
')$'
);

function checkForAtSignMentions( text ) {
let match = AtSignMentionsRegex.exec( text );

if ( match === null ) {
match = AtSignMentionsRegexAliasRegex.exec( text );
}
if ( match !== null ) {
// The strategy ignores leading whitespace but we need to know it's
// length to add it to the leadOffset
const maybeLeadingWhitespace = match[ 1 ];

const matchingString = match[ 3 ];
if ( matchingString.length >= 0 ) {
return {
leadOffset: match.index + maybeLeadingWhitespace.length,
matchingString,
replaceableString: match[ 2 ],
};
}
}
return null;
}

const MentionPlugin = ( {
optionsArray,
by = 'name',
size = 'md',
trigger = '@', // Default trigger value
menuComponent: MenuComponent = EditorCombobox,
menuItemComponent: MenuItemComponent = EditorCombobox.Item,
} ) => {
// Define PUNCTUATION and other necessary variables inside the component
const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';

const TRIGGERS = [ trigger ].join( '' ); // Use the trigger prop dynamically

const VALID_CHARS = '[^' + TRIGGERS + PUNCTUATION + '\\s]';

const VALID_JOINS =
'(?:' +
'\\.[ |$]|' + // E.g. "r. " in "Mr. Smith"
' |' + // E.g. " " in "Josh Duck"
'[' +
PUNCTUATION +
']|' + // E.g. "-' in "Salier-Hellendag"
')';

const LENGTH_LIMIT = 75;

const AtSignMentionsRegex = new RegExp(
`(^|\\s|\\()([${ TRIGGERS }]((?:${ VALID_CHARS }${ VALID_JOINS }){0,${ LENGTH_LIMIT }}))$`
);

// 50 is the longest alias length limit
const ALIAS_LENGTH_LIMIT = 50;

// Regex used to match alias
const AtSignMentionsRegexAliasRegex = new RegExp(
`(^|\\s|\\()([${ TRIGGERS }]((?:${ VALID_CHARS }){0,${ ALIAS_LENGTH_LIMIT }}))$`
);

// Define checkForAtSignMentions function inside the component where it has access to the regex
const checkForAtSignMentions = ( text ) => {
let match = AtSignMentionsRegex.exec( text );

if ( match === null ) {
match = AtSignMentionsRegexAliasRegex.exec( text );
}
if ( match !== null ) {
// The strategy ignores leading whitespace but we need to know its
// length to add it to the leadOffset
const maybeLeadingWhitespace = match[ 1 ];

const matchingString = match[ 3 ];
if ( matchingString.length >= 0 ) {
return {
leadOffset: match.index + maybeLeadingWhitespace.length,
matchingString,
replaceableString: match[ 2 ],
};
}
}
return null;
};

const [ editor ] = useLexicalComposerContext();
const [ queryString, setQueryString ] = useState( null );

// Use the hook to get lookup results
const results = useMentionLookupService( optionsArray, queryString, by );

const onSelectOption = useCallback(
Expand Down Expand Up @@ -117,7 +99,7 @@ const MentionPlugin = ( {
<LexicalTypeaheadMenuPlugin
onQueryChange={ setQueryString }
onSelectOption={ onSelectOption }
triggerFn={ checkForAtSignMentions }
triggerFn={ checkForAtSignMentions } // Use the locally defined function
options={ options }
menuRenderFn={ (
anchorElementRef,
Expand Down
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export { default as Alert } from './alert/index';
export { default as EditorInput } from './editor-input/index';
export { default as ProgressSteps } from './progress-steps/index';
export { default as Skeleton } from './skeleton/index';
export { default as Menu } from './menu-item/index';
1 change: 1 addition & 0 deletions src/components/menu-item/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './menu-item.jsx';
158 changes: 158 additions & 0 deletions src/components/menu-item/menu-item.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { useState, createContext, useContext } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronDown } from 'lucide-react';
import { cn } from '@/utilities/functions';

const MenuContext = createContext();
const useMenuContext = () => useContext( MenuContext );

const Menu = ( { size = 'md', children, className } ) => {
const baseClasses = 'w-64 flex flex-col bg-background-primary p-2';

return (
<MenuContext.Provider value={ { size } }>
<div className={ cn( baseClasses, className ) }>
{ children }
</div>
</MenuContext.Provider>
);
};

const MenuList = ( {
heading,
arrow = false,
open: initialOpen = false,
onClick,
children,
className,
} ) => {
const [ isOpen, setIsOpen ] = useState( initialOpen );
const { size } = useMenuContext();

const baseClasses = 'text-text-primary bg-transparent cursor-pointer flex justify-between items-center p-1 gap-1';

const sizeClasses = {
sm: 'text-xs',
md: 'text-sm',
}?.[ size ];
const iconSizeClasses = {
sm: '[&>svg]:size-4',
md: '[&>svg]:size-5',
}?.[ size ];

const handleToggle = () => {
setIsOpen( ! isOpen );
if ( onClick ) {
onClick( ! isOpen );
}
};

const arrowAnimationVariants = {
open: { rotate: 180 },
closed: { rotate: 0 },
};

const listAnimationVariants = {
open: { height: 'auto', opacity: 1 },
closed: { height: 0, opacity: 0 },
};

return (
<div className="p-2">
<div
role="button"
tabIndex="0"
onClick={ handleToggle }
onKeyPress={ ( event ) => {
if ( event.key === 'Enter' || event.key === ' ' ) {
handleToggle();
}
} }
className={ cn( baseClasses, sizeClasses, className ) }
aria-expanded={ isOpen }
>
<span className="text-text-tertiary">{ heading }</span>
{ arrow && (
<motion.span
variants={ arrowAnimationVariants }
animate={ isOpen ? 'open' : 'closed' }
transition={ { duration: 0.15 } }
className={ cn( 'flex items-center text-border-strong', iconSizeClasses ) }
>
<ChevronDown />
</motion.span>
) }
</div>

<AnimatePresence initial={ false }>
{ isOpen && (
<motion.ul
variants={ listAnimationVariants }
initial="closed"
animate="open"
exit="closed"
transition={ { duration: 0.3, ease: 'easeInOut' } }
className="overflow-hidden flex gap-0.5 flex-col m-0 bg-white rounded-md"
>
{ children }
</motion.ul>
) }
</AnimatePresence>
</div>
);
};

const MenuItem = ( { disabled = false, active, onClick, children, className } ) => {
const { size } = useMenuContext();

const baseClasses = 'flex p-1 gap-1 items-center bg-transparent w-full border-none rounded text-text-secondary cursor-pointer m-0';
const sizeClasses = {
sm: '[&>svg]:size-4 [&>svg]:m-1 [&>*:not(svg)]:mx-1 [&>*:not(svg)]:my-0.5 text-sm',
md: '[&>svg]:size-5 [&>svg]:m-1.5 [&>*:not(svg)]:m-1 text-base',
}?.[ size ];
const hoverClasses = 'hover:bg-background-secondary hover:text-text-primary';
const disabledClasses = disabled ? 'text-text-disabled hover:text-text-disabled cursor-not-allowed hover:bg-transparent' : '';
const activeClasses = active ? 'text-icon-primary [&>svg]:text-icon-interactive bg-background-secondary' : '';
const transitionClasses = 'transition-colors duration-300 ease-in-out';

return (
<li
role="menuitem"
tabIndex="0"
onClick={ onClick }
onKeyPress={ ( event ) => {
if ( event.key === 'Enter' || event.key === ' ' ) {
onClick();
}
} }
className={ cn( baseClasses, sizeClasses, hoverClasses, disabledClasses, activeClasses, transitionClasses, className ) }
>
{ children }
</li>
);
};

const MenuSeparator = ( { variant = 'solid', className } ) => {
const variantClasses = {
solid: 'border-solid',
dashed: 'border-dashed',
dotted: 'border-dotted',
double: 'border-double',
hidden: 'border-hidden',
none: 'border-none',
}?.[ variant ];

return (
<>
<hr
className={ cn( 'w-full border-0 border-t border-border-subtle', variantClasses, className ) }
/>
</>
);
};

Menu.List = MenuList;
Menu.Item = MenuItem;
Menu.Separator = MenuSeparator;

export default Menu;
Loading

0 comments on commit f07932c

Please sign in to comment.