diff --git a/lerna.json b/lerna.json index b2c0e088d..877ae451b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "packages": ["packages/*"], - "version": "2.6.2", + "version": "2.8.0", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/package-lock.json b/package-lock.json index e75dc226c..61d6b37f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25957,10 +25957,10 @@ } }, "packages/example-react": { - "version": "2.6.2", + "version": "2.8.0", "dependencies": { - "@livechat/design-system-icons": "^2.5.3", - "@livechat/design-system-react-components": "^2.6.2", + "@livechat/design-system-icons": "^2.8.0", + "@livechat/design-system-react-components": "^2.8.0", "react": "^18.3.0", "react-dom": "^18.3.0" }, @@ -25974,7 +25974,7 @@ }, "packages/icons": { "name": "@livechat/design-system-icons", - "version": "2.5.3", + "version": "2.8.0", "devDependencies": { "@svgr/cli": "^8.1.0", "glob": "^10.3.10", @@ -25996,12 +25996,12 @@ }, "packages/react-components": { "name": "@livechat/design-system-react-components", - "version": "2.6.2", + "version": "2.8.0", "license": "ISC", "dependencies": { "@floating-ui/react": "^0.26.25", "@livechat/data-utils": "^0.2.16", - "@livechat/design-system-icons": "^2.5.3", + "@livechat/design-system-icons": "^2.8.0", "clsx": "^1.1.1", "date-fns": "^2.28.0", "lodash.debounce": "^4.0.8", diff --git a/packages/example-react/package.json b/packages/example-react/package.json index 76e02bcc3..7941755f7 100644 --- a/packages/example-react/package.json +++ b/packages/example-react/package.json @@ -1,15 +1,15 @@ { "name": "example-react", "private": true, - "version": "2.6.2", + "version": "2.8.0", "scripts": { "start": "vite --open", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { - "@livechat/design-system-icons": "^2.5.3", - "@livechat/design-system-react-components": "^2.6.2", + "@livechat/design-system-icons": "^2.8.0", + "@livechat/design-system-react-components": "^2.8.0", "react": "^18.3.0", "react-dom": "^18.3.0" }, diff --git a/packages/icons/package.json b/packages/icons/package.json index 945db6892..250cd02bc 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@livechat/design-system-icons", - "version": "2.5.3", + "version": "2.8.0", "description": "", "publishConfig": { "access": "public" diff --git a/packages/icons/svg/arrows_diagonal.svg b/packages/icons/svg/arrows_diagonal.svg new file mode 100644 index 000000000..94ae1979f --- /dev/null +++ b/packages/icons/svg/arrows_diagonal.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/packages/icons/svg/arrows_diagonal_minimize.svg b/packages/icons/svg/arrows_diagonal_minimize.svg new file mode 100644 index 000000000..7815a9c0c --- /dev/null +++ b/packages/icons/svg/arrows_diagonal_minimize.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/packages/icons/svg/caret_down-filled.svg b/packages/icons/svg/caret_down-filled.svg new file mode 100644 index 000000000..6b6d02a09 --- /dev/null +++ b/packages/icons/svg/caret_down-filled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/icons/svg/caret_down.svg b/packages/icons/svg/caret_down.svg new file mode 100644 index 000000000..38f6607d5 --- /dev/null +++ b/packages/icons/svg/caret_down.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/icons/svg/caret_up-filled.svg b/packages/icons/svg/caret_up-filled.svg new file mode 100644 index 000000000..d60cd50b5 --- /dev/null +++ b/packages/icons/svg/caret_up-filled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/icons/svg/caret_up.svg b/packages/icons/svg/caret_up.svg new file mode 100644 index 000000000..0812218ce --- /dev/null +++ b/packages/icons/svg/caret_up.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/icons/svg/multi_choice-filled.svg b/packages/icons/svg/multi_choice-filled.svg new file mode 100644 index 000000000..3450c0aa2 --- /dev/null +++ b/packages/icons/svg/multi_choice-filled.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/icons/svg/multi_choice.svg b/packages/icons/svg/multi_choice.svg new file mode 100644 index 000000000..11acebefc --- /dev/null +++ b/packages/icons/svg/multi_choice.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/react-components/package.json b/packages/react-components/package.json index d090053a5..3053eeedc 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@livechat/design-system-react-components", - "version": "2.6.2", + "version": "2.8.0", "description": "", "publishConfig": { "access": "public" @@ -76,7 +76,7 @@ "dependencies": { "@floating-ui/react": "^0.26.25", "@livechat/data-utils": "^0.2.16", - "@livechat/design-system-icons": "^2.5.3", + "@livechat/design-system-icons": "^2.8.0", "clsx": "^1.1.1", "date-fns": "^2.28.0", "lodash.debounce": "^4.0.8", diff --git a/packages/react-components/src/components/Accordion/Accordion.mdx b/packages/react-components/src/components/Accordion/Accordion.mdx index a1349372e..8df5c122c 100644 --- a/packages/react-components/src/components/Accordion/Accordion.mdx +++ b/packages/react-components/src/components/Accordion/Accordion.mdx @@ -6,12 +6,14 @@ import * as AccordionStories from './Accordion.stories'; Accordion -[Intro](#Intro) | [Component API](#ComponentAPI) +[Intro](#Intro) | [Default Accordion](#Default) | [Promo Accordion](#Promo) | [Component API](#ComponentAPI) ## Intro Accordion is a simple component for building expandable tiles that display provided content when opened. +### Default Accordion + #### Example implementation @@ -19,11 +21,39 @@ Accordion is a simple component for building expandable tiles that display provi ```jsx import { Accordion } from '@livechat/design-system-react-components'; - + Label for closed accordion), + open: (
Label for open accordion
), + }} + multilineElement={
Multiline element
} + footer={
Footer element
} + > Default accordion content
``` +### Promo Accordion
+ + + +#### Example implementation + +```jsx +import { AccordionPromo } from '@livechat/design-system-react-components'; + + Label for closed accordion), + open: (
Label for open accordion
), + }} + multilineElement={
Multiline element
} + footer={
Footer element
} + > + Default accordion content +
+``` + ## Component API
\ No newline at end of file diff --git a/packages/react-components/src/components/Accordion/Accordion.module.scss b/packages/react-components/src/components/Accordion/Accordion.module.scss index ff4d83cc2..598302138 100644 --- a/packages/react-components/src/components/Accordion/Accordion.module.scss +++ b/packages/react-components/src/components/Accordion/Accordion.module.scss @@ -39,6 +39,11 @@ $base-class: 'accordion'; } } + &--promo { + border: 1px solid var(--border-basic-secondary); + background-color: var(--surface-primary-default); + } + &--open { border: 1px solid var(--action-primary-default); box-shadow: var(--shadow-float); @@ -59,6 +64,10 @@ $base-class: 'accordion'; &--open { transform: rotate(180deg); } + + &--promo { + top: 26px; + } } &__label { @@ -69,6 +78,11 @@ $base-class: 'accordion'; &:hover { cursor: pointer; } + + &--promo { + padding: var(--spacing-6) var(--spacing-12) var(--spacing-6) + var(--spacing-6); + } } &__content { @@ -84,6 +98,19 @@ $base-class: 'accordion'; &--open { opacity: 1; } + + &--promo { + padding: 0 var(--spacing-12) var(--spacing-6) var(--spacing-6); + } + } + } + + &__footer { + border-top: 1px solid var(--border-basic-secondary); + padding: var(--spacing-5); + + &--promo { + padding: var(--spacing-6); } } } diff --git a/packages/react-components/src/components/Accordion/Accordion.spec.tsx b/packages/react-components/src/components/Accordion/Accordion.spec.tsx index 5f4573b86..36b9525b9 100644 --- a/packages/react-components/src/components/Accordion/Accordion.spec.tsx +++ b/packages/react-components/src/components/Accordion/Accordion.spec.tsx @@ -44,26 +44,26 @@ describe(' component', () => { it('should call onClose and onOpen handlers on label click', async () => { const onClose = vi.fn(); const onOpen = vi.fn(); - const { getByRole } = renderComponent({ + const { getByText } = renderComponent({ ...DEFAULT_PROPS, onClose, onOpen, }); - expect(getByRole('button')).toHaveAttribute('aria-expanded', 'false'); - userEvent.click(getByRole('button')); + expect(getByText('Label')).toHaveAttribute('aria-expanded', 'false'); + userEvent.click(getByText('Label')); expect(onOpen).toHaveBeenCalledTimes(1); await waitFor(() => { - expect(getByRole('button')).toHaveAttribute('aria-expanded', 'false'); + expect(getByText('Label')).toHaveAttribute('aria-expanded', 'false'); }); - userEvent.click(getByRole('button')); + userEvent.click(getByText('Label')); expect(onClose).toHaveBeenCalledTimes(1); }); it('should show different label content when open and closed', async () => { - const { getByText, getByRole } = renderComponent({ + const { getByText } = renderComponent({ ...DEFAULT_PROPS, label: { open:
Open label
, @@ -72,7 +72,7 @@ describe(' component', () => { }); expect(getByText('Closed label')).toBeInTheDocument(); - userEvent.click(getByRole('button')); + userEvent.click(getByText('Closed label')); await waitFor(() => expect(getByText('Open label')).toBeInTheDocument()); }); @@ -84,4 +84,15 @@ describe(' component', () => { expect(getByText('Multi')).toBeInTheDocument(); }); + + it('should show footer element if open', async () => { + const { queryByText, getByText } = renderComponent({ + ...DEFAULT_PROPS, + footer:
Footer
, + }); + + expect(queryByText('Footer')).not.toBeInTheDocument(); + userEvent.click(getByText('Label')); + await waitFor(() => expect(queryByText('Footer')).toBeInTheDocument()); + }); }); diff --git a/packages/react-components/src/components/Accordion/Accordion.stories.css b/packages/react-components/src/components/Accordion/Accordion.stories.css index 312b64290..6926bc95b 100644 --- a/packages/react-components/src/components/Accordion/Accordion.stories.css +++ b/packages/react-components/src/components/Accordion/Accordion.stories.css @@ -29,3 +29,11 @@ } } } + +.accordion-promo-closed-heading { + margin-bottom: 4px; +} + +.accordion-promo-closed-description { + color: var(--content-basic-secondary); +} diff --git a/packages/react-components/src/components/Accordion/Accordion.stories.tsx b/packages/react-components/src/components/Accordion/Accordion.stories.tsx index 12b8383fe..96ba3272e 100644 --- a/packages/react-components/src/components/Accordion/Accordion.stories.tsx +++ b/packages/react-components/src/components/Accordion/Accordion.stories.tsx @@ -7,8 +7,12 @@ import { StoryDescriptor } from '../../stories/components/StoryDescriptor'; import { Icon } from '../Icon'; import { IPickerListItem, Picker } from '../Picker'; import { Tag } from '../Tag'; +import { Heading, Text } from '../Typography'; -import { Accordion } from './Accordion'; +import { + Accordion, + AccordionPromo as AccordionPromoComponent, +} from './Accordion'; import { RULE_PICKER_OPTIONS, TAGS_PICKER_OPTIONS } from './stories-helpers'; import './Accordion.stories.css'; @@ -16,6 +20,9 @@ import './Accordion.stories.css'; export default { title: 'Components/Accordion', component: Accordion, + subcomponents: { + AccordionPromo: AccordionPromoComponent, + }, } as Meta; export const Default = (): React.ReactElement => { @@ -108,6 +115,83 @@ export const Examples = (): React.ReactElement => { Default accordion content
+ + Example footer element}> + Default accordion content + + + + ); +}; + +export const AccordionPromo = (): React.ReactElement => { + return ( + + Default accordion content + + ); +}; + +export const AccordionPromoExamples = (): React.ReactElement => { + return ( +
+ + + + This example headline has 40 characters + + + A description with a maximum of 100 characters. That usually + means only one or two sentences + +
+ ), + open: ( + + Example headline for open state + + ), + }} + > + Default accordion content + + + + +

{`Hello {{ticket.requesterName}},`}

+

+ We haven't heard back from you for some time. If you need any + further help, please follow up on this email. +

+

Thank you.

+ + } + > + Default accordion content +
+
+ + Example footer element} + > + Default accordion content + + ); }; diff --git a/packages/react-components/src/components/Accordion/Accordion.tsx b/packages/react-components/src/components/Accordion/Accordion.tsx index ec1541860..a68b86a7d 100644 --- a/packages/react-components/src/components/Accordion/Accordion.tsx +++ b/packages/react-components/src/components/Accordion/Accordion.tsx @@ -5,24 +5,29 @@ import cx from 'clsx'; import { useAnimations, useHeightResizer } from '../../hooks'; import { Icon } from '../Icon'; -import { Text } from '../Typography'; +import { Heading, Text, TTextSize } from '../Typography'; import { AccordionMultilineElement } from './components/AccordionMultilineElement'; import { getLabel } from './helpers'; -import { IAccordionProps } from './types'; +import { useAccordion } from './hooks'; +import { + IAccordionProps, + IAccordionPromoProps, + IAccordionComponentProps, +} from './types'; import styles from './Accordion.module.scss'; -const baseClass = 'accordion'; - -export const Accordion: React.FC = ({ +const AccordionComponent: React.FC = ({ className, + mainClassName, children, label, multilineElement, - kind = 'default', openOnInit = false, isOpen, + isPromo, + footer, onClose, onOpen, ...props @@ -38,34 +43,36 @@ export const Accordion: React.FC = ({ isVisible: currentlyOpen, elementRef: contentRef, }); + const { size, handleResizeRef } = useHeightResizer(); + const { handleExpandChange, handleKeyDown } = useAccordion({ + isControlled, + isExpanded, + setShouldBeVisible, + onOpen, + onClose, + }); const mergedClassName = cx( - styles[baseClass], - styles[`${baseClass}--${kind}`], + mainClassName, { [styles[`${baseClass}--open`]]: isExpanded, }, className ); - const { size, handleResizeRef } = useHeightResizer(); - - const handleExpandChange = (isExpanded: boolean) => { - if (isExpanded) { - onClose?.(); - !isControlled && setShouldBeVisible(false); - } else { - onOpen?.(); - !isControlled && setShouldBeVisible(true); - } - }; - const handleKeyDown = (event: React.KeyboardEvent) => { - if (!isExpanded && (event.key === 'Enter' || event.key === ' ')) { - handleExpandChange(isExpanded); - } + const buildHeader = (isPromo?: boolean) => { + const Component = isPromo ? Heading : Text; + const props = { + 'aria-expanded': isExpanded, + as: 'div', + className: cx(styles[`${baseClass}__label`], { + [styles[`${baseClass}__label--promo`]]: isPromo, + }), + onClick: () => handleExpandChange(isExpanded), + bold: !isPromo ? true : undefined, + ...(isPromo ? { size: 'xs' as TTextSize } : {}), + }; - if (isExpanded && event.key === 'Escape') { - handleExpandChange(isExpanded); - } + return {getLabel(label, isExpanded)}; }; return ( @@ -80,18 +87,10 @@ export const Accordion: React.FC = ({ source={ChevronDown} className={cx(styles[`${baseClass}__chevron`], { [styles[`${baseClass}__chevron--open`]]: isExpanded, + [styles[`${baseClass}__chevron--promo`]]: isPromo, })} /> - handleExpandChange(isExpanded)} - > - {getLabel(label, isExpanded)} - + {buildHeader(isPromo)} {multilineElement && ( {multilineElement} @@ -104,17 +103,50 @@ export const Accordion: React.FC = ({ >
{isMounted && ( - - {children} - + <> + + {children} + + {footer && ( + + {footer} + + )} + )}
); }; + +const baseClass = 'accordion'; + +export const Accordion: React.FC = ({ kind, ...props }) => { + const mainClassName = cx(styles[baseClass], styles[`${baseClass}--${kind}`]); + + return ; +}; + +const promoBaseClass = `${baseClass}--promo`; + +export const AccordionPromo: React.FC = (props) => { + const mainClassName = cx(styles[baseClass], styles[promoBaseClass]); + + return ( + + ); +}; diff --git a/packages/react-components/src/components/Accordion/hooks.ts b/packages/react-components/src/components/Accordion/hooks.ts new file mode 100644 index 000000000..79fea3690 --- /dev/null +++ b/packages/react-components/src/components/Accordion/hooks.ts @@ -0,0 +1,48 @@ +import * as React from 'react'; + +interface UseAccordionProps { + isControlled: boolean; + isExpanded: boolean; + setShouldBeVisible: (isVisible: boolean) => void; + onOpen?: () => void; + onClose?: () => void; +} + +interface IUseAccordion { + handleExpandChange: (isExpanded: boolean) => void; + handleKeyDown: (event: React.KeyboardEvent) => void; +} + +export const useAccordion = ({ + isControlled, + isExpanded, + setShouldBeVisible, + onOpen, + onClose, +}: UseAccordionProps): IUseAccordion => { + const handleExpandChange = (isExpanded: boolean) => { + if (isExpanded) { + onClose?.(); + } else { + onOpen?.(); + } + !isControlled && setShouldBeVisible(!isExpanded); + }; + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if ( + (!isExpanded && (event.key === 'Enter' || event.key === ' ')) || + (isExpanded && event.key === 'Escape') + ) { + handleExpandChange(isExpanded); + } + }, + [isExpanded, handleExpandChange] + ); + + return { + handleExpandChange, + handleKeyDown, + }; +}; diff --git a/packages/react-components/src/components/Accordion/index.ts b/packages/react-components/src/components/Accordion/index.ts index feee0c4f0..bbca88aab 100644 --- a/packages/react-components/src/components/Accordion/index.ts +++ b/packages/react-components/src/components/Accordion/index.ts @@ -1 +1 @@ -export { Accordion } from './Accordion'; +export { Accordion, AccordionPromo } from './Accordion'; diff --git a/packages/react-components/src/components/Accordion/types.ts b/packages/react-components/src/components/Accordion/types.ts index 3d57601a0..d87cff4d7 100644 --- a/packages/react-components/src/components/Accordion/types.ts +++ b/packages/react-components/src/components/Accordion/types.ts @@ -6,7 +6,7 @@ export type AccordionLabel = | React.ReactNode | { open: React.ReactNode; closed: React.ReactNode }; -export interface IAccordionProps extends ComponentCoreProps { +export interface IAccordionGlobalProps extends ComponentCoreProps { /** * Specify the content of the accordion */ @@ -20,9 +20,9 @@ export interface IAccordionProps extends ComponentCoreProps { */ multilineElement?: React.ReactNode; /** - * Specify the kind of the accordion + * Specify the footer element, which will be displayed at the bottom */ - kind?: 'default' | 'warning' | 'error'; + footer?: React.ReactNode; /** * Specify if the accordion should be open on init */ @@ -41,6 +41,26 @@ export interface IAccordionProps extends ComponentCoreProps { onOpen?: () => void; } +export interface IAccordionProps extends IAccordionGlobalProps { + /** + * Specify the kind of the accordion + */ + kind?: 'default' | 'warning' | 'error'; +} + +export interface IAccordionPromoProps extends IAccordionGlobalProps {} + +export interface IAccordionComponentProps extends IAccordionGlobalProps { + /** + * CSS class name for the main accordion wrapper + */ + mainClassName: string; + /** + * Set to display promo accordion + */ + isPromo?: boolean; +} + export interface IAccordionAnimatedLabelProps { open: React.ReactNode; closed: React.ReactNode; diff --git a/packages/react-components/src/components/AppFrame/AppFrame.module.scss b/packages/react-components/src/components/AppFrame/AppFrame.module.scss index 2b1ca7bc5..fca4b4229 100644 --- a/packages/react-components/src/components/AppFrame/AppFrame.module.scss +++ b/packages/react-components/src/components/AppFrame/AppFrame.module.scss @@ -5,7 +5,7 @@ $base-class: 'app-frame'; flex-direction: row; background-color: var(--navbar-background); width: 100%; - height: 100vh; + height: 100dvh; color: var(--content-invert-default); &__page-content-container { diff --git a/packages/react-components/src/components/AppFrame/components/NavigationTopBar/NavigationTopBar.tsx b/packages/react-components/src/components/AppFrame/components/NavigationTopBar/NavigationTopBar.tsx index 9918116e8..b2c188449 100644 --- a/packages/react-components/src/components/AppFrame/components/NavigationTopBar/NavigationTopBar.tsx +++ b/packages/react-components/src/components/AppFrame/components/NavigationTopBar/NavigationTopBar.tsx @@ -101,6 +101,7 @@ export const NavigationTopBarAlert: React.FC = ({ const { isMounted, isOpen } = useAnimations({ isVisible, elementRef: alertRef, + includeSleepWakeScenario: true, }); const handleResizeRef = React.useCallback( (node: NODE) => @@ -110,8 +111,7 @@ export const NavigationTopBarAlert: React.FC = ({ [] ); const mobileCtaKind = kind === 'warning' ? 'link-inverted' : 'text'; - const desktopCtaKind = - kind === 'warning' ? 'plain-lock-black' : 'high-contrast'; + const desktopCtaKind = kind === 'warning' ? 'secondary' : 'high-contrast'; const customPrimaryCtaKind = primaryCta?.kind; const customSecondaryCtaKind = secondaryCta?.kind; diff --git a/packages/react-components/src/components/Badge/Badge.module.scss b/packages/react-components/src/components/Badge/Badge.module.scss index adb3032cf..c8d357587 100644 --- a/packages/react-components/src/components/Badge/Badge.module.scss +++ b/packages/react-components/src/components/Badge/Badge.module.scss @@ -69,8 +69,8 @@ $base-class: 'badge'; } &--secondary { - background-color: var(--surface-accent-emphasis-high-info); - color: var(--content-locked-white); + background-color: var(--surface-invert-secondary); + color: var(--content-invert-primary); .#{$base-class}__dot { background-color: var(--content-locked-white); diff --git a/packages/react-components/src/components/Badge/Badge.stories.tsx b/packages/react-components/src/components/Badge/Badge.stories.tsx index 9b45b9450..39c9fb4f6 100644 --- a/packages/react-components/src/components/Badge/Badge.stories.tsx +++ b/packages/react-components/src/components/Badge/Badge.stories.tsx @@ -39,7 +39,7 @@ export const Kinds = (): React.ReactElement => ( - + diff --git a/packages/react-components/src/components/Badge/Badge.tsx b/packages/react-components/src/components/Badge/Badge.tsx index 8489f6309..da28234f3 100644 --- a/packages/react-components/src/components/Badge/Badge.tsx +++ b/packages/react-components/src/components/Badge/Badge.tsx @@ -15,7 +15,6 @@ export interface BadgeProps extends React.HTMLAttributes { count?: number; /** * Specify the badge kind - * @param secondary - is deprecated, use "primary" or "tertiary" instead */ kind?: 'primary' | 'secondary' | 'tertiary'; /** diff --git a/packages/react-components/src/components/Icon/IconsShowcase/constans.ts b/packages/react-components/src/components/Icon/IconsShowcase/constans.ts index c8701cca1..198a3714f 100644 --- a/packages/react-components/src/components/Icon/IconsShowcase/constans.ts +++ b/packages/react-components/src/components/Icon/IconsShowcase/constans.ts @@ -38,6 +38,10 @@ export const IconsData: Record = { ArrowsShuffle: IconGroup.Arrows, ArrowsSort: IconGroup.Arrows, ArrowsMerge: IconGroup.Arrows, + CaretDown: IconGroup.Arrows, + CaretDownFilled: IconGroup.Arrows, + CaretUp: IconGroup.Arrows, + CaretUpFilled: IconGroup.Arrows, //FileType FiletypeExe: IconGroup.FileType, @@ -307,6 +311,8 @@ export const IconsData: Record = { MoreHoriz: IconGroup.General, MoveToFilled: IconGroup.General, MoveTo: IconGroup.General, + MultiChoiceFilled: IconGroup.General, + MultiChoice: IconGroup.General, NightModeFilled: IconGroup.General, NightMode: IconGroup.General, NoteAddFilled: IconGroup.General, @@ -523,4 +529,6 @@ export const IconsData: Record = { TextPlatform: IconGroup.Brands, SquareRoundedPlusFilled: IconGroup.General, SquareRoundedPlus: IconGroup.General, + ArrowsDiagonalMinimize: IconGroup.Arrows, + ArrowsDiagonal: IconGroup.Arrows, }; diff --git a/packages/react-components/src/components/Typography/Text.tsx b/packages/react-components/src/components/Typography/Text.tsx index f5740c68e..37982f254 100644 --- a/packages/react-components/src/components/Typography/Text.tsx +++ b/packages/react-components/src/components/Typography/Text.tsx @@ -22,6 +22,8 @@ interface IProps extends React.HTMLAttributes { uppercase?: boolean; /** Optional prop to set the bold */ bold?: boolean; + /** Optional prop to set the semi-bold */ + semiBold?: boolean; /** Optional prop to set the underline */ underline?: boolean; /** Optional prop to set the strike */ @@ -40,6 +42,7 @@ export const Text: React.FC> = ({ caps = false, uppercase = false, bold = false, + semiBold = false, underline = false, strike = false, children, @@ -57,7 +60,8 @@ export const Text: React.FC> = ({ className: cx( { [styles[`${baseClassPrefix}-${size}`]]: true, - [styles[`${baseClassPrefix}--bold`]]: bold, + [styles[`${baseClassPrefix}--semi-bold`]]: semiBold && !bold, + [styles[`${baseClassPrefix}--bold`]]: bold && !semiBold, [styles[`${baseClassPrefix}--strike`]]: strike, [styles[`${baseClassPrefix}--underline`]]: underline, [styles[`${baseClassPrefix}--uppercase`]]: uppercase || caps, diff --git a/packages/react-components/src/components/Typography/Typography.module.scss b/packages/react-components/src/components/Typography/Typography.module.scss index f4a2d636d..dcf210815 100644 --- a/packages/react-components/src/components/Typography/Typography.module.scss +++ b/packages/react-components/src/components/Typography/Typography.module.scss @@ -162,6 +162,10 @@ } .paragraph { + &--semi-bold { + font-weight: 500; + } + &--bold { font-weight: 600; } diff --git a/packages/react-components/src/hooks/useAnimations.ts b/packages/react-components/src/hooks/useAnimations.ts index 0104dae56..803518376 100644 --- a/packages/react-components/src/hooks/useAnimations.ts +++ b/packages/react-components/src/hooks/useAnimations.ts @@ -1,8 +1,11 @@ import * as React from 'react'; +import { useSleepWakeSync } from './useSleepWakeSync'; + interface UseAnimationsProps { isVisible: boolean; elementRef: React.RefObject; + includeSleepWakeScenario?: boolean; } interface IUseAnimations { @@ -14,6 +17,7 @@ interface IUseAnimations { export const useAnimations = ({ isVisible, elementRef, + includeSleepWakeScenario = false, }: UseAnimationsProps): IUseAnimations => { const [isMounted, setIsMounted] = React.useState(isVisible); const [isOpen, setIsOpen] = React.useState(isVisible); @@ -55,27 +59,17 @@ export const useAnimations = ({ } }, [shouldBeVisible]); - // Effect to listen for visibility changes (detecting sleep/wake scenarios) - React.useEffect(() => { - const handleVisibilityChange = () => { - if (document.visibilityState === 'visible') { - // Reset the animation state when returning to the visible state - setShouldBeVisible(isVisible); - } - }; - - document.addEventListener('visibilitychange', handleVisibilityChange); - - return () => { - document.removeEventListener('visibilitychange', handleVisibilityChange); - }; - }, [isVisible]); - // Synchronize shouldBeVisible with the isVisible prop React.useEffect(() => { setShouldBeVisible(isVisible); }, [isVisible]); + // Effect to listen for visibility changes (detecting sleep/wake scenarios) + useSleepWakeSync( + () => setShouldBeVisible(isVisible), + includeSleepWakeScenario + ); + return { isOpen, isMounted, diff --git a/packages/react-components/src/hooks/useSleepWakeSync.ts b/packages/react-components/src/hooks/useSleepWakeSync.ts new file mode 100644 index 000000000..d26c30641 --- /dev/null +++ b/packages/react-components/src/hooks/useSleepWakeSync.ts @@ -0,0 +1,29 @@ +import * as React from 'react'; + +/** + * Syncs the wake event with the visibility change event. + * @param onWake The function to call when the wake event is triggered. + * @param enabled Whether the sync should be enabled. + */ +export const useSleepWakeSync = (onWake: () => void, enabled: boolean) => { + React.useEffect(() => { + const handleVisibilityChange = () => { + if (document.visibilityState === 'visible') { + onWake(); + } + }; + + if (enabled) { + document.addEventListener('visibilitychange', handleVisibilityChange); + } + + return () => { + if (enabled) { + document.removeEventListener( + 'visibilitychange', + handleVisibilityChange + ); + } + }; + }, [onWake, enabled]); +}; diff --git a/packages/react-components/src/stories/foundations/Typography/components/TextExamples.tsx b/packages/react-components/src/stories/foundations/Typography/components/TextExamples.tsx index 72e382b6f..51872e838 100644 --- a/packages/react-components/src/stories/foundations/Typography/components/TextExamples.tsx +++ b/packages/react-components/src/stories/foundations/Typography/components/TextExamples.tsx @@ -12,6 +12,9 @@ export const TextExamples: React.FC = () => { Paragraph {size.toUpperCase()} with bold + + Paragraph {size.toUpperCase()} with semi-bold + Paragraph {size.toUpperCase()} with underline