@mrshmllw/smores-react 16.5.0 → 16.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,7 @@ export const Alert = ({ type = 'info', title, message, isDismissible = false, ct
41
41
  const backgroundColor = resolveToThemeColor(typeConfig[type].backgroundColor, theme);
42
42
  const accentColor = resolveToThemeColor(typeConfig[type].accentColor, theme);
43
43
  const [alertClosed, setAlertClosed] = useState(false);
44
- return (_jsxs(StyledAlert, { $backgroundColor: backgroundColor, $accentColor: accentColor, $alertClosed: alertClosed, $maxWidth: maxWidth, ...marginProps, children: [_jsx(IconContainer, { $size: 16, style: { margin: '2px' }, children: _jsx(FontAwesomeIcon, { icon: typeConfig[type].icon, color: theme.color.icon.base }) }), _jsxs(Box, { flex: true, direction: "row", justifyContent: "space-between", alignItems: "flex-start", width: "100%", gap: "space.100", children: [_jsxs(Box, { maxWidth: "512px", children: [title && (_jsx(Text, { mb: "space.050", typo: "headline-regular", children: title })), typeof message === 'string' ? (_jsx(Text, { typo: "body-small", mb: ctaType && '8px', children: message })) : (message), ctaType === 'button' && typeof ctaAction === 'function' && (_jsx(Button, { smallButton: true, fallbackStyle: true, onClick: () => ctaAction(), children: ctaLabel })), ctaType === 'link' && typeof ctaAction === 'string' && (_jsx(Link, { href: ctaAction, children: ctaLabel }))] }), isDismissible && (_jsx(IconContainer, { as: "button", type: "button", title: "icon-button", $size: 16, style: { cursor: 'pointer' }, onClick: () => setAlertClosed(true), children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.icon.base }) }))] })] }));
44
+ return (_jsxs(StyledAlert, { $backgroundColor: backgroundColor, $accentColor: accentColor, $alertClosed: alertClosed, $maxWidth: maxWidth, ...marginProps, children: [_jsx(IconContainer, { $size: 16, style: { margin: '2px' }, children: _jsx(FontAwesomeIcon, { icon: typeConfig[type].icon, color: theme.color.icon.base }) }), _jsxs(Box, { flex: true, direction: "row", justifyContent: "space-between", alignItems: "flex-start", width: "100%", gap: "space.100", children: [_jsxs(Box, { maxWidth: "512px", children: [title && (_jsx(Text, { mb: "space.050", typo: "headline-regular", children: title })), typeof message === 'string' ? (_jsx(Text, { typo: "body-small", mb: ctaType && '8px', children: message })) : (message), ctaType === 'button' && typeof ctaAction === 'function' && (_jsx(Button, { size: "small", variant: "neutral", onClick: () => ctaAction(), children: ctaLabel })), ctaType === 'link' && typeof ctaAction === 'string' && (_jsx(Link, { href: ctaAction, children: ctaLabel }))] }), isDismissible && (_jsx(IconContainer, { as: "button", type: "button", title: "icon-button", $size: 16, style: { cursor: 'pointer' }, onClick: () => setAlertClosed(true), children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.icon.base }) }))] })] }));
45
45
  };
46
46
  const StyledAlert = styled(Box)(({ $backgroundColor, $accentColor, $alertClosed, $maxWidth }) => css `
47
47
  display: flex;
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../src/Alert/Alert.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactElement, useState } from 'react'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { MarginProps } from '../utils/space'\nimport { NewColor, resolveToThemeColor } from '../ThemeProvider/utils/colourMap'\n\nimport { Box } from '../Box'\nimport { Text } from '../Text'\nimport { Button } from '../Button'\nimport { Link } from '../Link'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faCircleInfo,\n faTriangleExclamation,\n faCircleExclamation,\n faCircleCheck,\n faXmark,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nimport { IconDefinition } from '@fortawesome/fontawesome-svg-core'\nimport { IconContainer } from '../sharedStyles/shared.styles'\n\ntype AlertType = 'info' | 'fallback' | 'notice' | 'negative' | 'positive'\n\ntype CtaProps =\n | {\n ctaType: 'button'\n ctaAction: () => void\n ctaLabel: string\n }\n | {\n ctaType: 'link'\n ctaAction: string\n ctaLabel: string\n }\n | {\n ctaType?: undefined\n ctaAction?: undefined\n ctaLabel?: undefined\n }\n\nexport type AlertProps = {\n type?: AlertType\n title?: string\n message: string | ReactElement<unknown>\n isDismissible?: boolean\n maxWidth?: string\n} & CtaProps &\n MarginProps\n\ntype AlertTypeConfig = {\n backgroundColor: NewColor\n accentColor: NewColor\n icon: IconDefinition\n}\n\nconst typeConfig: Record<AlertType, AlertTypeConfig> = {\n info: {\n backgroundColor: 'color.feedback.informative.100',\n accentColor: 'color.feedback.informative.200',\n icon: faCircleInfo,\n },\n fallback: {\n backgroundColor: 'color.surface.base.100',\n accentColor: 'color.surface.base.400',\n icon: faCircleInfo,\n },\n notice: {\n backgroundColor: 'color.feedback.notice.100',\n accentColor: 'color.feedback.notice.200',\n icon: faTriangleExclamation,\n },\n negative: {\n backgroundColor: 'color.feedback.negative.100',\n accentColor: 'color.feedback.negative.200',\n icon: faCircleExclamation,\n },\n positive: {\n backgroundColor: 'color.feedback.positive.100',\n accentColor: 'color.feedback.positive.200',\n icon: faCircleCheck,\n },\n}\n\nexport const Alert: FC<AlertProps> = ({\n type = 'info',\n title,\n message,\n isDismissible = false,\n ctaType,\n ctaAction,\n ctaLabel,\n maxWidth = '512px',\n ...marginProps\n}) => {\n const theme = useTheme()\n\n const backgroundColor = resolveToThemeColor(\n typeConfig[type].backgroundColor,\n theme,\n )\n const accentColor = resolveToThemeColor(typeConfig[type].accentColor, theme)\n const [alertClosed, setAlertClosed] = useState(false)\n\n return (\n <StyledAlert\n $backgroundColor={backgroundColor}\n $accentColor={accentColor}\n $alertClosed={alertClosed}\n $maxWidth={maxWidth}\n {...marginProps}\n >\n <IconContainer $size={16} style={{ margin: '2px' }}>\n <FontAwesomeIcon\n icon={typeConfig[type].icon}\n color={theme.color.icon.base}\n />\n </IconContainer>\n <Box\n flex\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"flex-start\"\n width=\"100%\"\n gap=\"space.100\"\n >\n <Box maxWidth=\"512px\">\n {title && (\n <Text mb=\"space.050\" typo=\"headline-regular\">\n {title}\n </Text>\n )}\n {typeof message === 'string' ? (\n <Text typo=\"body-small\" mb={ctaType && '8px'}>\n {message}\n </Text>\n ) : (\n message\n )}\n {ctaType === 'button' && typeof ctaAction === 'function' && (\n <Button smallButton fallbackStyle onClick={() => ctaAction()}>\n {ctaLabel}\n </Button>\n )}\n {ctaType === 'link' && typeof ctaAction === 'string' && (\n <Link href={ctaAction}>{ctaLabel}</Link>\n )}\n </Box>\n {isDismissible && (\n <IconContainer\n as=\"button\"\n type=\"button\"\n title=\"icon-button\"\n $size={16}\n style={{ cursor: 'pointer' }}\n onClick={() => setAlertClosed(true)}\n >\n <FontAwesomeIcon icon={faXmark} color={theme.color.icon.base} />\n </IconContainer>\n )}\n </Box>\n </StyledAlert>\n )\n}\n\ninterface IStyledAlert {\n $backgroundColor: string\n $accentColor: string\n $alertClosed: boolean\n $maxWidth?: string\n}\n\nconst StyledAlert = styled(Box)<IStyledAlert>(\n ({ $backgroundColor, $accentColor, $alertClosed, $maxWidth }) => css`\n display: flex;\n width: 100%;\n max-width: ${$maxWidth};\n min-width: min-content;\n padding: 12px 12px 12px 20px;\n align-items: flex-start;\n gap: ${({ theme }) => theme.space[100]};\n border-radius: 8px 16px 16px 8px;\n background: ${$backgroundColor};\n position: relative;\n display: ${$alertClosed ? 'none' : 'flex'};\n\n &:before {\n width: 8px;\n height: 100%;\n position: absolute;\n background: ${$accentColor};\n content: '';\n top: 0;\n left: 0;\n border-radius: 16px 0 0 16px;\n }\n `,\n)\n"]}
1
+ {"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../src/Alert/Alert.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactElement, useState } from 'react'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { MarginProps } from '../utils/space'\nimport { NewColor, resolveToThemeColor } from '../ThemeProvider/utils/colourMap'\n\nimport { Box } from '../Box'\nimport { Text } from '../Text'\nimport { Button } from '../Button'\nimport { Link } from '../Link'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faCircleInfo,\n faTriangleExclamation,\n faCircleExclamation,\n faCircleCheck,\n faXmark,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nimport { IconDefinition } from '@fortawesome/fontawesome-svg-core'\nimport { IconContainer } from '../sharedStyles/shared.styles'\n\ntype AlertType = 'info' | 'fallback' | 'notice' | 'negative' | 'positive'\n\ntype CtaProps =\n | {\n ctaType: 'button'\n ctaAction: () => void\n ctaLabel: string\n }\n | {\n ctaType: 'link'\n ctaAction: string\n ctaLabel: string\n }\n | {\n ctaType?: undefined\n ctaAction?: undefined\n ctaLabel?: undefined\n }\n\nexport type AlertProps = {\n type?: AlertType\n title?: string\n message: string | ReactElement<unknown>\n isDismissible?: boolean\n maxWidth?: string\n} & CtaProps &\n MarginProps\n\ntype AlertTypeConfig = {\n backgroundColor: NewColor\n accentColor: NewColor\n icon: IconDefinition\n}\n\nconst typeConfig: Record<AlertType, AlertTypeConfig> = {\n info: {\n backgroundColor: 'color.feedback.informative.100',\n accentColor: 'color.feedback.informative.200',\n icon: faCircleInfo,\n },\n fallback: {\n backgroundColor: 'color.surface.base.100',\n accentColor: 'color.surface.base.400',\n icon: faCircleInfo,\n },\n notice: {\n backgroundColor: 'color.feedback.notice.100',\n accentColor: 'color.feedback.notice.200',\n icon: faTriangleExclamation,\n },\n negative: {\n backgroundColor: 'color.feedback.negative.100',\n accentColor: 'color.feedback.negative.200',\n icon: faCircleExclamation,\n },\n positive: {\n backgroundColor: 'color.feedback.positive.100',\n accentColor: 'color.feedback.positive.200',\n icon: faCircleCheck,\n },\n}\n\nexport const Alert: FC<AlertProps> = ({\n type = 'info',\n title,\n message,\n isDismissible = false,\n ctaType,\n ctaAction,\n ctaLabel,\n maxWidth = '512px',\n ...marginProps\n}) => {\n const theme = useTheme()\n\n const backgroundColor = resolveToThemeColor(\n typeConfig[type].backgroundColor,\n theme,\n )\n const accentColor = resolveToThemeColor(typeConfig[type].accentColor, theme)\n const [alertClosed, setAlertClosed] = useState(false)\n\n return (\n <StyledAlert\n $backgroundColor={backgroundColor}\n $accentColor={accentColor}\n $alertClosed={alertClosed}\n $maxWidth={maxWidth}\n {...marginProps}\n >\n <IconContainer $size={16} style={{ margin: '2px' }}>\n <FontAwesomeIcon\n icon={typeConfig[type].icon}\n color={theme.color.icon.base}\n />\n </IconContainer>\n <Box\n flex\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"flex-start\"\n width=\"100%\"\n gap=\"space.100\"\n >\n <Box maxWidth=\"512px\">\n {title && (\n <Text mb=\"space.050\" typo=\"headline-regular\">\n {title}\n </Text>\n )}\n {typeof message === 'string' ? (\n <Text typo=\"body-small\" mb={ctaType && '8px'}>\n {message}\n </Text>\n ) : (\n message\n )}\n {ctaType === 'button' && typeof ctaAction === 'function' && (\n <Button size=\"small\" variant=\"neutral\" onClick={() => ctaAction()}>\n {ctaLabel}\n </Button>\n )}\n {ctaType === 'link' && typeof ctaAction === 'string' && (\n <Link href={ctaAction}>{ctaLabel}</Link>\n )}\n </Box>\n {isDismissible && (\n <IconContainer\n as=\"button\"\n type=\"button\"\n title=\"icon-button\"\n $size={16}\n style={{ cursor: 'pointer' }}\n onClick={() => setAlertClosed(true)}\n >\n <FontAwesomeIcon icon={faXmark} color={theme.color.icon.base} />\n </IconContainer>\n )}\n </Box>\n </StyledAlert>\n )\n}\n\ninterface IStyledAlert {\n $backgroundColor: string\n $accentColor: string\n $alertClosed: boolean\n $maxWidth?: string\n}\n\nconst StyledAlert = styled(Box)<IStyledAlert>(\n ({ $backgroundColor, $accentColor, $alertClosed, $maxWidth }) => css`\n display: flex;\n width: 100%;\n max-width: ${$maxWidth};\n min-width: min-content;\n padding: 12px 12px 12px 20px;\n align-items: flex-start;\n gap: ${({ theme }) => theme.space[100]};\n border-radius: 8px 16px 16px 8px;\n background: ${$backgroundColor};\n position: relative;\n display: ${$alertClosed ? 'none' : 'flex'};\n\n &:before {\n width: 8px;\n height: 100%;\n position: absolute;\n background: ${$accentColor};\n content: '';\n top: 0;\n left: 0;\n border-radius: 16px 0 0 16px;\n }\n `,\n)\n"]}
@@ -1,6 +1,8 @@
1
1
  import { ButtonHTMLAttributes, FC, FormEvent, ReactNode } from 'react';
2
2
  import { Icons } from '../Icon';
3
3
  import { MarginProps } from '../utils/space';
4
+ export type ButtonVariant = 'primary' | 'secondary' | 'neutral' | 'tertiary';
5
+ export type ButtonSize = 'regular' | 'small';
4
6
  type Props = {
5
7
  children: ReactNode;
6
8
  id?: string;
@@ -8,13 +10,26 @@ type Props = {
8
10
  disabled?: boolean;
9
11
  handleClick?: (e: FormEvent<HTMLButtonElement>) => void;
10
12
  loading?: boolean;
13
+ /**
14
+ * Visual style of the button:
15
+ * - `primary`: the main action on a screen
16
+ * - `secondary`: supporting or alternative actions
17
+ * - `neutral`: as secondary, for when it lacks contrast with the background
18
+ * - `tertiary`: low-emphasis, text-only actions, e.g. "Skip" or "Cancel"
19
+ */
20
+ variant?: ButtonVariant;
21
+ size?: ButtonSize;
22
+ /** @deprecated Use `variant="primary"` */
11
23
  primary?: boolean;
24
+ /** @deprecated Use `variant="secondary"` */
12
25
  secondary?: boolean;
13
- /** Low-emphasis, text-only action, e.g. "Skip" or "Cancel" */
26
+ /** @deprecated Use `variant="tertiary"` */
14
27
  tertiary?: boolean;
28
+ /** @deprecated Use `variant="neutral"` */
15
29
  fallbackStyle?: boolean;
16
- /** Legacy underlined text style. Prefer `tertiary` for new low-emphasis actions */
30
+ /** Legacy underlined text style. Prefer `variant="tertiary"` for new low-emphasis actions */
17
31
  textBtn?: boolean;
32
+ /** @deprecated Use `size="small"` */
18
33
  smallButton?: boolean;
19
34
  icon?: Icons;
20
35
  iconComponent?: ReactNode;
@@ -5,20 +5,40 @@ import { Box } from '../Box';
5
5
  import { Icon as IconComponent } from '../Icon';
6
6
  import { Loader } from '../Loader';
7
7
  import { focusOutlineStyle } from '../utils/focusOutline';
8
+ import { useDeprecatedWarning } from '../utils/deprecated';
8
9
  export const Button = forwardRef((props, ref) => {
9
- const { children, id, className = '', disabled = false, handleClick, loading = false, primary = false, secondary = false, tertiary = false, fallbackStyle = false, textBtn = false, smallButton = false, icon, iconComponent, trailingIcon = false, forcedWidth = '', form, type, ...otherProps } = props;
10
- // Primary is the default type, so an untyped button gets Primary's hover and pressed states too
11
- const isPrimary = primary || !(secondary || tertiary || fallbackStyle || textBtn);
10
+ const { children, id, className = '', disabled = false, handleClick, loading = false, variant, size, primary = false, secondary = false, tertiary = false, fallbackStyle = false, textBtn = false, smallButton = false, icon, iconComponent, trailingIcon = false, forcedWidth = '', form, type, ...otherProps } = props;
11
+ useDeprecatedWarning({
12
+ enabled: primary || secondary || tertiary || fallbackStyle || smallButton,
13
+ title: 'Button',
14
+ message: 'The primary, secondary, tertiary, fallbackStyle and smallButton props are deprecated. Use variant="primary" | "secondary" | "neutral" | "tertiary" and size="regular" | "small" instead.',
15
+ });
16
+ // Legacy props resolve in the order their styles used to override each other.
17
+ // Primary is the default, except for the legacy text button which has no variant
18
+ const resolvedVariant = variant ??
19
+ (fallbackStyle
20
+ ? 'neutral'
21
+ : tertiary
22
+ ? 'tertiary'
23
+ : secondary
24
+ ? 'secondary'
25
+ : primary || !textBtn
26
+ ? 'primary'
27
+ : undefined);
28
+ const isSmall = size ? size === 'small' : smallButton;
29
+ const isTertiary = resolvedVariant === 'tertiary';
12
30
  // Icon-only buttons drop the gap so the icon stays centred
13
31
  // React renders nothing for null, undefined, booleans and empty strings
14
32
  const hasLabel = children != null && typeof children !== 'boolean' && children !== '';
15
33
  const iconToRender = iconComponent ? (_jsx(CustomIconContainer, { children: iconComponent })) : icon ? (_jsx(IconComponent, { render: icon, size: iconSize,
16
34
  // Matches the label, which tertiary dims by colour rather than opacity when disabled
17
- color: tertiary && disabled ? 'color.icon.nonEssential' : 'color.icon.contrast' })) : null;
18
- return (_jsxs(Container, { forwardedAs: "button", id: id, className: className, disabled: disabled || loading, onClick: handleClick, $loading: loading, $primary: isPrimary, $secondary: secondary, $tertiary: tertiary, $fallbackStyle: fallbackStyle, $textBtn: textBtn, $smallButton: smallButton, $trailingIcon: trailingIcon, $forcedWidth: forcedWidth, ...(form ? { form } : {}), type: type, ...otherProps, ref: ref, children: [loading && (_jsx(LoaderContainer, { children: _jsx(Loader, { color: "color.icon.base", height: "16" }) })), _jsxs(ContentContainer, { $loading: loading, $hasLabel: hasLabel, children: [!trailingIcon && iconToRender ? iconToRender : null, _jsx(ChildrenContainer, { className: "childrenContainer", children: children }), trailingIcon && iconToRender ? iconToRender : null] })] }));
35
+ color: isTertiary && disabled
36
+ ? 'color.icon.nonEssential'
37
+ : 'color.icon.contrast' })) : null;
38
+ return (_jsxs(Container, { forwardedAs: "button", id: id, className: className, disabled: disabled || loading, onClick: handleClick, $loading: loading, $primary: resolvedVariant === 'primary', $secondary: resolvedVariant === 'secondary', $tertiary: isTertiary, $neutral: resolvedVariant === 'neutral', $textBtn: textBtn, $smallButton: isSmall, $trailingIcon: trailingIcon, $forcedWidth: forcedWidth, ...(form ? { form } : {}), type: type, ...otherProps, ref: ref, children: [loading && (_jsx(LoaderContainer, { children: _jsx(Loader, { color: "color.icon.base", height: "16" }) })), _jsxs(ContentContainer, { $loading: loading, $hasLabel: hasLabel, children: [!trailingIcon && iconToRender ? iconToRender : null, _jsx(ChildrenContainer, { className: "childrenContainer", children: children }), trailingIcon && iconToRender ? iconToRender : null] })] }));
19
39
  });
20
40
  Button.displayName = 'Button';
21
- const Container = styled(Box)(({ disabled, $loading, $primary, $secondary, $tertiary, $forcedWidth, $fallbackStyle, $textBtn, $smallButton, }) => css `
41
+ const Container = styled(Box)(({ disabled, $loading, $primary, $secondary, $tertiary, $forcedWidth, $neutral, $textBtn, $smallButton, }) => css `
22
42
  position: relative;
23
43
  background-color: ${({ theme }) => theme.color.interactive.primary.base};
24
44
  box-shadow: none;
@@ -75,7 +95,7 @@ const Container = styled(Box)(({ disabled, $loading, $primary, $secondary, $tert
75
95
  color: ${({ theme }) => theme.color.text.nonEssential};
76
96
  `}
77
97
  `}
78
- ${$fallbackStyle &&
98
+ ${$neutral &&
79
99
  css `
80
100
  background-color: ${({ theme }) => theme.color.interactive.neutral.subtle.base};
81
101
 
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n ButtonHTMLAttributes,\n FC,\n FormEvent,\n ReactNode,\n forwardRef,\n} from 'react'\nimport styled, { css } from 'styled-components'\n\nimport { TransientProps } from '../utils/utilTypes'\nimport { Box } from '../Box'\nimport { Icon as IconComponent, Icons } from '../Icon'\n\nimport { Loader } from '../Loader'\nimport { focusOutlineStyle } from '../utils/focusOutline'\nimport { MarginProps } from '../utils/space'\n\ntype Props = {\n children: ReactNode\n id?: string\n className?: string\n disabled?: boolean\n handleClick?: (e: FormEvent<HTMLButtonElement>) => void\n loading?: boolean\n primary?: boolean\n secondary?: boolean\n /** Low-emphasis, text-only action, e.g. \"Skip\" or \"Cancel\" */\n tertiary?: boolean\n fallbackStyle?: boolean\n /** Legacy underlined text style. Prefer `tertiary` for new low-emphasis actions */\n textBtn?: boolean\n smallButton?: boolean\n icon?: Icons\n iconComponent?: ReactNode\n trailingIcon?: boolean\n forcedWidth?: string\n form?: string\n}\n\nexport type ButtonProps = Props &\n MarginProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'>\n\nexport const Button: FC<ButtonProps> = forwardRef<\n HTMLButtonElement,\n ButtonProps\n>((props, ref) => {\n const {\n children,\n id,\n className = '',\n disabled = false,\n handleClick,\n loading = false,\n primary = false,\n secondary = false,\n tertiary = false,\n fallbackStyle = false,\n textBtn = false,\n smallButton = false,\n icon,\n iconComponent,\n trailingIcon = false,\n forcedWidth = '',\n form,\n type,\n ...otherProps\n } = props\n\n // Primary is the default type, so an untyped button gets Primary's hover and pressed states too\n const isPrimary =\n primary || !(secondary || tertiary || fallbackStyle || textBtn)\n\n // Icon-only buttons drop the gap so the icon stays centred\n // React renders nothing for null, undefined, booleans and empty strings\n const hasLabel =\n children != null && typeof children !== 'boolean' && children !== ''\n\n const iconToRender = iconComponent ? (\n <CustomIconContainer>{iconComponent}</CustomIconContainer>\n ) : icon ? (\n <IconComponent\n render={icon}\n size={iconSize}\n // Matches the label, which tertiary dims by colour rather than opacity when disabled\n color={\n tertiary && disabled ? 'color.icon.nonEssential' : 'color.icon.contrast'\n }\n />\n ) : null\n\n return (\n <Container\n forwardedAs=\"button\"\n id={id}\n className={className}\n disabled={disabled || loading}\n onClick={handleClick}\n $loading={loading}\n $primary={isPrimary}\n $secondary={secondary}\n $tertiary={tertiary}\n $fallbackStyle={fallbackStyle}\n $textBtn={textBtn}\n $smallButton={smallButton}\n $trailingIcon={trailingIcon}\n $forcedWidth={forcedWidth}\n {...(form ? { form } : {})}\n type={type}\n {...otherProps}\n ref={ref}\n >\n {loading && (\n <LoaderContainer>\n <Loader color=\"color.icon.base\" height=\"16\" />\n </LoaderContainer>\n )}\n <ContentContainer $loading={loading} $hasLabel={hasLabel}>\n {!trailingIcon && iconToRender ? iconToRender : null}\n <ChildrenContainer className=\"childrenContainer\">\n {children}\n </ChildrenContainer>\n {trailingIcon && iconToRender ? iconToRender : null}\n </ContentContainer>\n </Container>\n )\n})\n\nButton.displayName = 'Button'\n\ntype IButton = TransientProps<\n Required<\n Pick<\n ButtonProps,\n | 'primary'\n | 'secondary'\n | 'tertiary'\n | 'forcedWidth'\n | 'fallbackStyle'\n | 'textBtn'\n | 'trailingIcon'\n | 'smallButton'\n >\n >\n> & {\n $loading: NonNullable<ButtonProps['loading']>\n disabled: boolean\n}\n\nconst Container = styled(Box)<IButton>(\n ({\n disabled,\n $loading,\n $primary,\n $secondary,\n $tertiary,\n $forcedWidth,\n $fallbackStyle,\n $textBtn,\n $smallButton,\n }) => css`\n position: relative;\n background-color: ${({ theme }) => theme.color.interactive.primary.base};\n box-shadow: none;\n color: ${({ theme }) => theme.color.text.contrast};\n padding: 0 20px;\n outline: none;\n border-radius: 10000px;\n font-weight: ${({ theme }) => theme.fontWeight[500]};\n cursor: ${disabled || $loading ? 'not-allowed' : 'pointer'};\n line-height: 100%;\n font-size: 16px;\n opacity: ${disabled ? '0.5' : '1'};\n width: ${$forcedWidth ? $forcedWidth : 'auto'};\n\n ${focusOutlineStyle}\n\n ${\n $primary &&\n css`\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.primary.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.primary.selected};\n }\n `\n }\n\n ${\n $secondary &&\n css`\n background-color: ${({ theme }) =>\n theme.color.interactive.secondary.base};\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.secondary.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.secondary.selected};\n }\n `\n }\n ${\n $tertiary &&\n css`\n background-color: transparent;\n opacity: 1;\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.selected};\n }\n\n ${\n disabled &&\n css`\n color: ${({ theme }) => theme.color.text.nonEssential};\n `\n }\n `\n }\n ${\n $fallbackStyle &&\n css`\n background-color: ${({ theme }) => theme.color.interactive.neutral.subtle.base};\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.neutral.subtle.selected};\n }\n `\n }\n ${\n $smallButton &&\n css`\n padding: 0 12px;\n min-width: 54px;\n\n .childrenContainer {\n padding: 8px 0;\n }\n `\n }\n ${\n $textBtn &&\n css`\n background-color: transparent;\n padding: 0;\n border-radius: 0;\n text-decoration: underline;\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.primary.hover};\n }\n &:active {\n background-color: transparent;\n color: ${({ theme }) => theme.color.text.subtle};\n }\n\n ${\n $smallButton &&\n css`\n font-size: 14px;\n\n .childrenContainer {\n padding: 9px 0;\n }\n `\n }\n `\n }\n `,\n)\n\nconst LoaderContainer = styled.div`\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n`\n\nconst ContentContainer = styled.div<{ $loading: boolean; $hasLabel: boolean }>`\n display: flex;\n align-items: center;\n justify-content: center;\n gap: ${({ $hasLabel }) => ($hasLabel ? '4px' : '0')};\n opacity: ${({ $loading }) => ($loading ? '0' : '1')};\n`\n\nconst iconSize = 20\n\n// Figma icons use Font Awesome's square bounding box, in which the glyph's em height\n// takes up 80% of the box, so custom (Font Awesome) icons are drawn at that size\nconst CustomIconContainer = styled.span`\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: ${iconSize}px;\n height: ${iconSize}px;\n\n svg {\n width: 100%;\n height: 80%;\n overflow: visible;\n }\n`\n\nconst ChildrenContainer = styled.div`\n padding: 16px 0;\n`\n"]}
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n ButtonHTMLAttributes,\n FC,\n FormEvent,\n ReactNode,\n forwardRef,\n} from 'react'\nimport styled, { css } from 'styled-components'\n\nimport { TransientProps } from '../utils/utilTypes'\nimport { Box } from '../Box'\nimport { Icon as IconComponent, Icons } from '../Icon'\n\nimport { Loader } from '../Loader'\nimport { focusOutlineStyle } from '../utils/focusOutline'\nimport { MarginProps } from '../utils/space'\nimport { useDeprecatedWarning } from '../utils/deprecated'\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'neutral' | 'tertiary'\nexport type ButtonSize = 'regular' | 'small'\n\ntype Props = {\n children: ReactNode\n id?: string\n className?: string\n disabled?: boolean\n handleClick?: (e: FormEvent<HTMLButtonElement>) => void\n loading?: boolean\n /**\n * Visual style of the button:\n * - `primary`: the main action on a screen\n * - `secondary`: supporting or alternative actions\n * - `neutral`: as secondary, for when it lacks contrast with the background\n * - `tertiary`: low-emphasis, text-only actions, e.g. \"Skip\" or \"Cancel\"\n */\n variant?: ButtonVariant\n size?: ButtonSize\n /** @deprecated Use `variant=\"primary\"` */\n primary?: boolean\n /** @deprecated Use `variant=\"secondary\"` */\n secondary?: boolean\n /** @deprecated Use `variant=\"tertiary\"` */\n tertiary?: boolean\n /** @deprecated Use `variant=\"neutral\"` */\n fallbackStyle?: boolean\n /** Legacy underlined text style. Prefer `variant=\"tertiary\"` for new low-emphasis actions */\n textBtn?: boolean\n /** @deprecated Use `size=\"small\"` */\n smallButton?: boolean\n icon?: Icons\n iconComponent?: ReactNode\n trailingIcon?: boolean\n forcedWidth?: string\n form?: string\n}\n\nexport type ButtonProps = Props &\n MarginProps &\n Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'>\n\nexport const Button: FC<ButtonProps> = forwardRef<\n HTMLButtonElement,\n ButtonProps\n>((props, ref) => {\n const {\n children,\n id,\n className = '',\n disabled = false,\n handleClick,\n loading = false,\n variant,\n size,\n primary = false,\n secondary = false,\n tertiary = false,\n fallbackStyle = false,\n textBtn = false,\n smallButton = false,\n icon,\n iconComponent,\n trailingIcon = false,\n forcedWidth = '',\n form,\n type,\n ...otherProps\n } = props\n\n useDeprecatedWarning({\n enabled: primary || secondary || tertiary || fallbackStyle || smallButton,\n title: 'Button',\n message:\n 'The primary, secondary, tertiary, fallbackStyle and smallButton props are deprecated. Use variant=\"primary\" | \"secondary\" | \"neutral\" | \"tertiary\" and size=\"regular\" | \"small\" instead.',\n })\n\n // Legacy props resolve in the order their styles used to override each other.\n // Primary is the default, except for the legacy text button which has no variant\n const resolvedVariant: ButtonVariant | undefined =\n variant ??\n (fallbackStyle\n ? 'neutral'\n : tertiary\n ? 'tertiary'\n : secondary\n ? 'secondary'\n : primary || !textBtn\n ? 'primary'\n : undefined)\n const isSmall = size ? size === 'small' : smallButton\n const isTertiary = resolvedVariant === 'tertiary'\n\n // Icon-only buttons drop the gap so the icon stays centred\n // React renders nothing for null, undefined, booleans and empty strings\n const hasLabel =\n children != null && typeof children !== 'boolean' && children !== ''\n\n const iconToRender = iconComponent ? (\n <CustomIconContainer>{iconComponent}</CustomIconContainer>\n ) : icon ? (\n <IconComponent\n render={icon}\n size={iconSize}\n // Matches the label, which tertiary dims by colour rather than opacity when disabled\n color={\n isTertiary && disabled\n ? 'color.icon.nonEssential'\n : 'color.icon.contrast'\n }\n />\n ) : null\n\n return (\n <Container\n forwardedAs=\"button\"\n id={id}\n className={className}\n disabled={disabled || loading}\n onClick={handleClick}\n $loading={loading}\n $primary={resolvedVariant === 'primary'}\n $secondary={resolvedVariant === 'secondary'}\n $tertiary={isTertiary}\n $neutral={resolvedVariant === 'neutral'}\n $textBtn={textBtn}\n $smallButton={isSmall}\n $trailingIcon={trailingIcon}\n $forcedWidth={forcedWidth}\n {...(form ? { form } : {})}\n type={type}\n {...otherProps}\n ref={ref}\n >\n {loading && (\n <LoaderContainer>\n <Loader color=\"color.icon.base\" height=\"16\" />\n </LoaderContainer>\n )}\n <ContentContainer $loading={loading} $hasLabel={hasLabel}>\n {!trailingIcon && iconToRender ? iconToRender : null}\n <ChildrenContainer className=\"childrenContainer\">\n {children}\n </ChildrenContainer>\n {trailingIcon && iconToRender ? iconToRender : null}\n </ContentContainer>\n </Container>\n )\n})\n\nButton.displayName = 'Button'\n\ntype IButton = TransientProps<\n Required<\n Pick<\n ButtonProps,\n | 'primary'\n | 'secondary'\n | 'tertiary'\n | 'forcedWidth'\n | 'textBtn'\n | 'trailingIcon'\n | 'smallButton'\n >\n >\n> & {\n $neutral: boolean\n $loading: NonNullable<ButtonProps['loading']>\n disabled: boolean\n}\n\nconst Container = styled(Box)<IButton>(\n ({\n disabled,\n $loading,\n $primary,\n $secondary,\n $tertiary,\n $forcedWidth,\n $neutral,\n $textBtn,\n $smallButton,\n }) => css`\n position: relative;\n background-color: ${({ theme }) => theme.color.interactive.primary.base};\n box-shadow: none;\n color: ${({ theme }) => theme.color.text.contrast};\n padding: 0 20px;\n outline: none;\n border-radius: 10000px;\n font-weight: ${({ theme }) => theme.fontWeight[500]};\n cursor: ${disabled || $loading ? 'not-allowed' : 'pointer'};\n line-height: 100%;\n font-size: 16px;\n opacity: ${disabled ? '0.5' : '1'};\n width: ${$forcedWidth ? $forcedWidth : 'auto'};\n\n ${focusOutlineStyle}\n\n ${\n $primary &&\n css`\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.primary.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.primary.selected};\n }\n `\n }\n\n ${\n $secondary &&\n css`\n background-color: ${({ theme }) =>\n theme.color.interactive.secondary.base};\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.secondary.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.secondary.selected};\n }\n `\n }\n ${\n $tertiary &&\n css`\n background-color: transparent;\n opacity: 1;\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.selected};\n }\n\n ${\n disabled &&\n css`\n color: ${({ theme }) => theme.color.text.nonEssential};\n `\n }\n `\n }\n ${\n $neutral &&\n css`\n background-color: ${({ theme }) => theme.color.interactive.neutral.subtle.base};\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) &&\n theme.color.interactive.neutral.subtle.hover};\n }\n &:active {\n background-color: ${({ theme }) =>\n theme.color.interactive.neutral.subtle.selected};\n }\n `\n }\n ${\n $smallButton &&\n css`\n padding: 0 12px;\n min-width: 54px;\n\n .childrenContainer {\n padding: 8px 0;\n }\n `\n }\n ${\n $textBtn &&\n css`\n background-color: transparent;\n padding: 0;\n border-radius: 0;\n text-decoration: underline;\n\n &:hover {\n background-color: ${({ theme }) =>\n !(disabled || $loading) && theme.color.interactive.primary.hover};\n }\n &:active {\n background-color: transparent;\n color: ${({ theme }) => theme.color.text.subtle};\n }\n\n ${\n $smallButton &&\n css`\n font-size: 14px;\n\n .childrenContainer {\n padding: 9px 0;\n }\n `\n }\n `\n }\n `,\n)\n\nconst LoaderContainer = styled.div`\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n`\n\nconst ContentContainer = styled.div<{ $loading: boolean; $hasLabel: boolean }>`\n display: flex;\n align-items: center;\n justify-content: center;\n gap: ${({ $hasLabel }) => ($hasLabel ? '4px' : '0')};\n opacity: ${({ $loading }) => ($loading ? '0' : '1')};\n`\n\nconst iconSize = 20\n\n// Figma icons use Font Awesome's square bounding box, in which the glyph's em height\n// takes up 80% of the box, so custom (Font Awesome) icons are drawn at that size\nconst CustomIconContainer = styled.span`\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: ${iconSize}px;\n height: ${iconSize}px;\n\n svg {\n width: 100%;\n height: 80%;\n overflow: visible;\n }\n`\n\nconst ChildrenContainer = styled.div`\n padding: 16px 0;\n`\n"]}
@@ -1 +1,2 @@
1
1
  export { Button } from './Button';
2
+ export type { ButtonProps, ButtonSize, ButtonVariant } from './Button';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/Button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA","sourcesContent":["export { Button } from './Button'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/Button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA","sourcesContent":["export { Button } from './Button'\nexport type { ButtonProps, ButtonSize, ButtonVariant } from './Button'\n"]}
@@ -27,6 +27,21 @@ export type ModalProps = {
27
27
  containerClass?: string;
28
28
  portalContainer?: Element | DocumentFragment;
29
29
  closeOnOverlayClick?: boolean;
30
+ /**
31
+ * Keeps the title and close button visible while the content scrolls.
32
+ * @default false
33
+ */
34
+ stickyHeader?: boolean;
35
+ /**
36
+ * Rendered below the content, e.g. one or two buttons, which share the
37
+ * width equally. Put the primary action last.
38
+ */
39
+ footer?: ReactNode;
40
+ /**
41
+ * Keeps `footer` visible while the content scrolls.
42
+ * @default false
43
+ */
44
+ stickyFooter?: boolean;
30
45
  };
31
46
  export type TitleProps = TextProps;
32
47
  export declare const Modal: FC<ModalProps>;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from 'react';
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import styled, { css, useTheme } from 'styled-components';
5
5
  import { Box } from '../Box';
@@ -14,22 +14,80 @@ const getDefaultTitleProps = (title) => ({
14
14
  typo: 'heading-small',
15
15
  align: 'left',
16
16
  });
17
- export const Modal = ({ title = '', children, rightPanel, showModal = false, handleClick, drawer = true, cross = true, width, containerClass, portalContainer = document.body, closeOnOverlayClick = true, }) => {
18
- const modalRef = useRef(null);
17
+ const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
18
+ export const Modal = ({ title = '', children, rightPanel, showModal = false, handleClick, drawer = true, cross = true, width, containerClass, portalContainer = document.body, closeOnOverlayClick = true, stickyHeader = false, footer, stickyFooter = false, }) => {
19
+ const [modalNode, setModalNode] = useState(null);
20
+ const containerRef = useRef(null);
21
+ const titleId = useId();
19
22
  const theme = useTheme();
20
- // oxlint-disable-next-line react/react-compiler
21
- useBodyScrollLock({ node: modalRef.current, showModal });
23
+ useBodyScrollLock({ node: modalNode, showModal });
24
+ // Record the opener while rendering the open state, before any child can take focus.
25
+ const [wasOpen, setWasOpen] = useState(false);
26
+ const [opener, setOpener] = useState(null);
27
+ if (showModal !== wasOpen) {
28
+ setWasOpen(showModal);
29
+ if (showModal)
30
+ setOpener(document.activeElement);
31
+ }
32
+ const latestRef = useRef({ handleClick, closeOnOverlayClick });
33
+ useEffect(() => {
34
+ latestRef.current = { handleClick, closeOnOverlayClick };
35
+ });
36
+ useEffect(() => {
37
+ const container = containerRef.current;
38
+ if (!showModal || !container)
39
+ return;
40
+ // A child with autoFocus may already have focus, so leave it there.
41
+ if (!container.contains(document.activeElement))
42
+ container.focus();
43
+ const handleKeyDown = (event) => {
44
+ const active = document.activeElement;
45
+ // Only the modal that has focus responds, so stacked modals close one at a time.
46
+ if (!container.contains(active))
47
+ return;
48
+ if (event.key === 'Escape') {
49
+ if (event.defaultPrevented || event.isComposing)
50
+ return;
51
+ if (latestRef.current.closeOnOverlayClick) {
52
+ latestRef.current.handleClick();
53
+ }
54
+ return;
55
+ }
56
+ if (event.key !== 'Tab')
57
+ return;
58
+ const focusable = container.querySelectorAll(FOCUSABLE_SELECTOR);
59
+ const first = focusable[0];
60
+ const last = focusable[focusable.length - 1];
61
+ if (!first) {
62
+ event.preventDefault();
63
+ }
64
+ else if (event.shiftKey && (active === first || active === container)) {
65
+ event.preventDefault();
66
+ last.focus();
67
+ }
68
+ else if (!event.shiftKey && active === last) {
69
+ event.preventDefault();
70
+ first.focus();
71
+ }
72
+ };
73
+ document.addEventListener('keydown', handleKeyDown);
74
+ return () => {
75
+ document.removeEventListener('keydown', handleKeyDown);
76
+ opener?.focus();
77
+ };
78
+ }, [showModal, opener]);
22
79
  const isTitleString = typeof title === 'string';
23
80
  const titleProps = isTitleString ? getDefaultTitleProps(title) : title;
81
+ const labelId = titleProps.id ?? titleId;
24
82
  if (!showModal)
25
83
  return null;
26
- return createPortal(_jsxs(Wrapper, { ref: modalRef, children: [_jsx(Overlay, { onClick: () => closeOnOverlayClick && handleClick(), $closeOnOverlayClick: closeOnOverlayClick }), _jsxs(Container, { $drawer: drawer, $width: width || '460px', className: containerClass, children: [_jsxs(Box, { flex: true, alignItems: "flex-start", justifyContent: "space-between", mb: "space.100", children: [_jsx(TitleElements, { flex: true, direction: "column", children: _jsx(Text, { ...titleProps }) }), _jsxs(Box, { flex: true, alignItems: "center", gap: 'space.100', children: [rightPanel, cross && (_jsx(IconContainer, { as: "button", onClick: handleClick, role: "button", title: "Close modal", $size: 32, style: {
27
- background: theme.color.illustration.neutral[300],
28
- borderRadius: '100%',
29
- padding: '6px',
30
- border: 'none',
31
- cursor: 'pointer',
32
- }, children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.icon.base }) }))] })] }), _jsx(Box, { flex: true, direction: "column", children: children })] })] }), portalContainer);
84
+ return createPortal(_jsxs(Wrapper, { ref: setModalNode, children: [_jsx(Overlay, { onClick: () => closeOnOverlayClick && handleClick(), $closeOnOverlayClick: closeOnOverlayClick }), _jsx(Container, { ref: containerRef, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? labelId : undefined, $drawer: drawer, $width: width || '460px', className: containerClass, children: _jsxs(ScrollArea, { children: [_jsxs(Header, { flex: true, alignItems: "flex-start", justifyContent: "space-between", $sticky: stickyHeader, children: [_jsx(TitleElements, { flex: true, direction: "column", children: _jsx(Text, { ...titleProps, id: labelId }) }), _jsxs(Box, { flex: true, alignItems: "center", gap: 'space.100', children: [rightPanel, cross && (_jsx(IconContainer, { as: "button", onClick: handleClick, role: "button", title: "Close modal", $size: 32, style: {
85
+ background: theme.color.illustration.neutral[300],
86
+ borderRadius: '100%',
87
+ padding: '6px',
88
+ border: 'none',
89
+ cursor: 'pointer',
90
+ }, children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.icon.base }) }))] })] }), _jsx(ContentArea, { flex: true, direction: "column", $hasFooter: !!footer, children: children }), footer ? _jsx(Footer, { $sticky: stickyFooter, children: footer }) : null] }) })] }), portalContainer);
33
91
  };
34
92
  const Wrapper = styled(Box) `
35
93
  display: flex;
@@ -53,15 +111,17 @@ const Overlay = styled.div `
53
111
  right: 0;
54
112
  `;
55
113
  const Container = styled.div(({ $drawer, $width }) => css `
114
+ display: flex;
115
+ flex-direction: column;
56
116
  background: ${({ theme }) => theme.color.background[100]};
57
117
  box-sizing: border-box;
58
118
  border-radius: 16px;
59
- padding: ${({ theme }) => theme.space[300]};
60
119
  width: 100%;
61
120
  max-width: ${$width};
62
121
  position: fixed;
63
122
  max-height: calc(100vh - 64px);
64
- overflow: auto;
123
+ overflow: hidden;
124
+ outline: none;
65
125
  transition: all 0.3s ease-in-out;
66
126
 
67
127
  ${$drawer === true &&
@@ -69,7 +129,6 @@ const Container = styled.div(({ $drawer, $width }) => css `
69
129
  @media (max-width: 768px) {
70
130
  max-width: none;
71
131
  border-radius: 16px 16px 0px 0px;
72
- padding: 10% 24px;
73
132
  max-height: 90vh;
74
133
 
75
134
  position: fixed;
@@ -79,6 +138,44 @@ const Container = styled.div(({ $drawer, $width }) => css `
79
138
  }
80
139
  `}
81
140
  `);
141
+ // The scrollbar lives here so Container's border-radius can clip it.
142
+ const ScrollArea = styled.div `
143
+ flex: 1;
144
+ min-height: 0;
145
+ overflow-y: auto;
146
+ overscroll-behavior-y: none;
147
+ `;
148
+ const Header = styled(Box)(({ $sticky, theme }) => css `
149
+ padding: ${theme.space[300]} ${theme.space[200]} ${theme.space[200]};
150
+
151
+ ${$sticky &&
152
+ css `
153
+ position: sticky;
154
+ top: 0;
155
+ z-index: 1;
156
+ background: ${theme.color.background[100]};
157
+ `}
158
+ `);
159
+ const ContentArea = styled(Box)(({ $hasFooter, theme }) => css `
160
+ padding: 0 ${theme.space[200]} ${$hasFooter ? '0' : theme.space[300]};
161
+ `);
162
+ const Footer = styled.div(({ $sticky, theme }) => css `
163
+ display: flex;
164
+ gap: ${theme.space[100]};
165
+ padding: ${theme.space[300]} ${theme.space[200]};
166
+
167
+ & > * {
168
+ flex: 1;
169
+ }
170
+
171
+ ${$sticky &&
172
+ css `
173
+ position: sticky;
174
+ bottom: 0;
175
+ z-index: 1;
176
+ background: ${theme.color.background[100]};
177
+ `}
178
+ `);
82
179
  const TitleElements = styled(Box) `
83
180
  align-self: center;
84
181
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, useRef } from 'react'\nimport { createPortal } from 'react-dom'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Text, type TextProps } from '../Text'\nimport { useBodyScrollLock } from '../hooks/useBodyScrollLock'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faXmark } from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\ninterface IModalContainer {\n // drawer state\n $drawer: boolean\n // modal width\n $width: string\n}\n\nexport type ModalProps = {\n /**\n * Title of the modal\n * @default \"\" (empty string)\n *\n * @example\n * ```tsx\n * <Modal title=\"MultiCar Account\" />\n * ```\n *\n * @example\n * ```tsx\n * <Modal title={{ typo: 'hero', children: 'MultiCar Account' }} />\n * ```\n */\n title?: string | TitleProps\n icon?: string\n children?: ReactNode\n rightPanel?: ReactNode\n showModal?: boolean\n handleClick: () => void\n drawer?: boolean\n cross?: boolean\n width?: string\n containerClass?: string\n portalContainer?: Element | DocumentFragment\n closeOnOverlayClick?: boolean\n}\n\nexport type TitleProps = TextProps\n\nconst getDefaultTitleProps = (title: string): TitleProps => ({\n children: title,\n tag: 'h2',\n typo: 'heading-small',\n align: 'left',\n})\n\nexport const Modal: FC<ModalProps> = ({\n title = '',\n children,\n rightPanel,\n showModal = false,\n handleClick,\n drawer = true,\n cross = true,\n width,\n containerClass,\n portalContainer = document.body,\n closeOnOverlayClick = true,\n}) => {\n const modalRef = useRef<HTMLDivElement>(null)\n const theme = useTheme()\n\n // oxlint-disable-next-line react/react-compiler\n useBodyScrollLock({ node: modalRef.current, showModal })\n\n const isTitleString = typeof title === 'string'\n const titleProps = isTitleString ? getDefaultTitleProps(title) : title\n\n if (!showModal) return null\n\n return createPortal(\n <Wrapper ref={modalRef}>\n <Overlay\n onClick={() => closeOnOverlayClick && handleClick()}\n $closeOnOverlayClick={closeOnOverlayClick}\n />\n <Container\n $drawer={drawer}\n $width={width || '460px'}\n className={containerClass}\n >\n <Box\n flex\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n mb=\"space.100\"\n >\n <TitleElements flex direction=\"column\">\n <Text {...titleProps} />\n </TitleElements>\n <Box flex alignItems=\"center\" gap={'space.100'}>\n {rightPanel}\n {cross && (\n <IconContainer\n as=\"button\"\n onClick={handleClick}\n role=\"button\"\n title=\"Close modal\"\n $size={32}\n style={{\n background: theme.color.illustration.neutral[300],\n borderRadius: '100%',\n padding: '6px',\n border: 'none',\n cursor: 'pointer',\n }}\n >\n <FontAwesomeIcon icon={faXmark} color={theme.color.icon.base} />\n </IconContainer>\n )}\n </Box>\n </Box>\n <Box flex direction=\"column\">\n {children}\n </Box>\n </Container>\n </Wrapper>,\n portalContainer,\n )\n}\n\nconst Wrapper = styled(Box)`\n display: flex;\n position: absolute;\n z-index: 999;\n top: 0;\n left: 0;\n height: 100vh;\n width: 100%;\n justify-content: center;\n align-items: center;\n`\n\nconst Overlay = styled.div<{ $closeOnOverlayClick: boolean }>`\n position: fixed;\n background: ${({ theme }) => theme.color.surface.base[900]};\n cursor: ${(props) => (props.$closeOnOverlayClick ? 'pointer' : 'default')};\n opacity: 0.4;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n`\n\nconst Container = styled.div<IModalContainer>(\n ({ $drawer, $width }) => css`\n background: ${({ theme }) => theme.color.background[100]};\n box-sizing: border-box;\n border-radius: 16px;\n padding: ${({ theme }) => theme.space[300]};\n width: 100%;\n max-width: ${$width};\n position: fixed;\n max-height: calc(100vh - 64px);\n overflow: auto;\n transition: all 0.3s ease-in-out;\n\n ${\n $drawer === true &&\n css`\n @media (max-width: 768px) {\n max-width: none;\n border-radius: 16px 16px 0px 0px;\n padding: 10% 24px;\n max-height: 90vh;\n\n position: fixed;\n right: 0;\n left: 0;\n bottom: 0;\n }\n `\n }\n `,\n)\n\nconst TitleElements = styled(Box)`\n align-self: center;\n`\n"]}
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, useEffect, useId, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Text, type TextProps } from '../Text'\nimport { useBodyScrollLock } from '../hooks/useBodyScrollLock'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faXmark } from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\ninterface IModalContainer {\n // drawer state\n $drawer: boolean\n // modal width\n $width: string\n}\n\nexport type ModalProps = {\n /**\n * Title of the modal\n * @default \"\" (empty string)\n *\n * @example\n * ```tsx\n * <Modal title=\"MultiCar Account\" />\n * ```\n *\n * @example\n * ```tsx\n * <Modal title={{ typo: 'hero', children: 'MultiCar Account' }} />\n * ```\n */\n title?: string | TitleProps\n icon?: string\n children?: ReactNode\n rightPanel?: ReactNode\n showModal?: boolean\n handleClick: () => void\n drawer?: boolean\n cross?: boolean\n width?: string\n containerClass?: string\n portalContainer?: Element | DocumentFragment\n closeOnOverlayClick?: boolean\n /**\n * Keeps the title and close button visible while the content scrolls.\n * @default false\n */\n stickyHeader?: boolean\n /**\n * Rendered below the content, e.g. one or two buttons, which share the\n * width equally. Put the primary action last.\n */\n footer?: ReactNode\n /**\n * Keeps `footer` visible while the content scrolls.\n * @default false\n */\n stickyFooter?: boolean\n}\n\nexport type TitleProps = TextProps\n\nconst getDefaultTitleProps = (title: string): TitleProps => ({\n children: title,\n tag: 'h2',\n typo: 'heading-small',\n align: 'left',\n})\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nexport const Modal: FC<ModalProps> = ({\n title = '',\n children,\n rightPanel,\n showModal = false,\n handleClick,\n drawer = true,\n cross = true,\n width,\n containerClass,\n portalContainer = document.body,\n closeOnOverlayClick = true,\n stickyHeader = false,\n footer,\n stickyFooter = false,\n}) => {\n const [modalNode, setModalNode] = useState<HTMLDivElement | null>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const titleId = useId()\n const theme = useTheme()\n\n useBodyScrollLock({ node: modalNode, showModal })\n\n // Record the opener while rendering the open state, before any child can take focus.\n const [wasOpen, setWasOpen] = useState(false)\n const [opener, setOpener] = useState<HTMLElement | null>(null)\n if (showModal !== wasOpen) {\n setWasOpen(showModal)\n if (showModal) setOpener(document.activeElement as HTMLElement | null)\n }\n\n const latestRef = useRef({ handleClick, closeOnOverlayClick })\n useEffect(() => {\n latestRef.current = { handleClick, closeOnOverlayClick }\n })\n\n useEffect(() => {\n const container = containerRef.current\n if (!showModal || !container) return\n\n // A child with autoFocus may already have focus, so leave it there.\n if (!container.contains(document.activeElement)) container.focus()\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const active = document.activeElement\n // Only the modal that has focus responds, so stacked modals close one at a time.\n if (!container.contains(active)) return\n\n if (event.key === 'Escape') {\n if (event.defaultPrevented || event.isComposing) return\n if (latestRef.current.closeOnOverlayClick) {\n latestRef.current.handleClick()\n }\n return\n }\n\n if (event.key !== 'Tab') return\n\n const focusable =\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (!first) {\n event.preventDefault()\n } else if (event.shiftKey && (active === first || active === container)) {\n event.preventDefault()\n last.focus()\n } else if (!event.shiftKey && active === last) {\n event.preventDefault()\n first.focus()\n }\n }\n\n document.addEventListener('keydown', handleKeyDown)\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n opener?.focus()\n }\n }, [showModal, opener])\n\n const isTitleString = typeof title === 'string'\n const titleProps = isTitleString ? getDefaultTitleProps(title) : title\n const labelId = titleProps.id ?? titleId\n\n if (!showModal) return null\n\n return createPortal(\n <Wrapper ref={setModalNode}>\n <Overlay\n onClick={() => closeOnOverlayClick && handleClick()}\n $closeOnOverlayClick={closeOnOverlayClick}\n />\n <Container\n ref={containerRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? labelId : undefined}\n $drawer={drawer}\n $width={width || '460px'}\n className={containerClass}\n >\n <ScrollArea>\n <Header\n flex\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n $sticky={stickyHeader}\n >\n <TitleElements flex direction=\"column\">\n <Text {...titleProps} id={labelId} />\n </TitleElements>\n <Box flex alignItems=\"center\" gap={'space.100'}>\n {rightPanel}\n {cross && (\n <IconContainer\n as=\"button\"\n onClick={handleClick}\n role=\"button\"\n title=\"Close modal\"\n $size={32}\n style={{\n background: theme.color.illustration.neutral[300],\n borderRadius: '100%',\n padding: '6px',\n border: 'none',\n cursor: 'pointer',\n }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.icon.base}\n />\n </IconContainer>\n )}\n </Box>\n </Header>\n <ContentArea flex direction=\"column\" $hasFooter={!!footer}>\n {children}\n </ContentArea>\n {footer ? <Footer $sticky={stickyFooter}>{footer}</Footer> : null}\n </ScrollArea>\n </Container>\n </Wrapper>,\n portalContainer,\n )\n}\n\nconst Wrapper = styled(Box)`\n display: flex;\n position: absolute;\n z-index: 999;\n top: 0;\n left: 0;\n height: 100vh;\n width: 100%;\n justify-content: center;\n align-items: center;\n`\n\nconst Overlay = styled.div<{ $closeOnOverlayClick: boolean }>`\n position: fixed;\n background: ${({ theme }) => theme.color.surface.base[900]};\n cursor: ${(props) => (props.$closeOnOverlayClick ? 'pointer' : 'default')};\n opacity: 0.4;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n`\n\nconst Container = styled.div<IModalContainer>(\n ({ $drawer, $width }) => css`\n display: flex;\n flex-direction: column;\n background: ${({ theme }) => theme.color.background[100]};\n box-sizing: border-box;\n border-radius: 16px;\n width: 100%;\n max-width: ${$width};\n position: fixed;\n max-height: calc(100vh - 64px);\n overflow: hidden;\n outline: none;\n transition: all 0.3s ease-in-out;\n\n ${\n $drawer === true &&\n css`\n @media (max-width: 768px) {\n max-width: none;\n border-radius: 16px 16px 0px 0px;\n max-height: 90vh;\n\n position: fixed;\n right: 0;\n left: 0;\n bottom: 0;\n }\n `\n }\n `,\n)\n\n// The scrollbar lives here so Container's border-radius can clip it.\nconst ScrollArea = styled.div`\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior-y: none;\n`\n\nconst Header = styled(Box)<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n padding: ${theme.space[300]} ${theme.space[200]} ${theme.space[200]};\n\n ${\n $sticky &&\n css`\n position: sticky;\n top: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst ContentArea = styled(Box)<{ $hasFooter: boolean }>(\n ({ $hasFooter, theme }) => css`\n padding: 0 ${theme.space[200]} ${$hasFooter ? '0' : theme.space[300]};\n `,\n)\n\nconst Footer = styled.div<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n display: flex;\n gap: ${theme.space[100]};\n padding: ${theme.space[300]} ${theme.space[200]};\n\n & > * {\n flex: 1;\n }\n\n ${\n $sticky &&\n css`\n position: sticky;\n bottom: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst TitleElements = styled(Box)`\n align-self: center;\n`\n"]}
@@ -6,12 +6,13 @@ import { Button } from '../Button';
6
6
  import { Text } from '../Text';
7
7
  const toButtonProps = ({ variant, disabled, loading }, defaultVariant) => {
8
8
  const resolved = variant ?? defaultVariant;
9
+ // PromoCard's tertiary is the legacy underlined text button
10
+ if (resolved === 'tertiary')
11
+ return { disabled, loading, textBtn: true };
9
12
  return {
10
13
  disabled,
11
14
  loading,
12
- secondary: resolved === 'secondary',
13
- fallbackStyle: resolved === 'fallbackStyle',
14
- textBtn: resolved === 'tertiary',
15
+ variant: resolved === 'fallbackStyle' ? 'neutral' : 'secondary',
15
16
  };
16
17
  };
17
18
  /**
@@ -28,7 +29,7 @@ const toButtonProps = ({ variant, disabled, loading }, defaultVariant) => {
28
29
  *
29
30
  * @see https://www.figma.com/design/FqSaizGOyOzXFZNmZ4X0LGMn/%F0%9F%A7%B1-S-mores-components?node-id=33734-3198
30
31
  */
31
- export const PromoCard = ({ title, body, imagePosition = 'left', backgroundColor = 'custard', illustration, primaryButton, secondaryButton, className, ...marginProps }) => (_jsxs(Container, { $imagePosition: imagePosition, $backgroundColor: backgroundColor, className: className, ...marginProps, children: [illustration && _jsx(IllustrationWrapper, { children: illustration }), _jsxs(Content, { children: [_jsxs(Box, { flex: true, direction: "column", gap: "space.050", children: [_jsx(Text, { typo: "heading-small", children: title }), body && _jsx(Text, { typo: "body-regular", children: body })] }), (primaryButton ?? secondaryButton) && (_jsxs(Box, { flex: true, direction: "row", gap: "space.100", children: [primaryButton && (_jsx(Button, { smallButton: true, handleClick: primaryButton.onClick, ...toButtonProps(primaryButton, 'secondary'), children: primaryButton.label })), secondaryButton && (_jsx(Button, { smallButton: true, handleClick: secondaryButton.onClick, ...toButtonProps(secondaryButton, 'fallbackStyle'), children: secondaryButton.label }))] }))] })] }));
32
+ export const PromoCard = ({ title, body, imagePosition = 'left', backgroundColor = 'custard', illustration, primaryButton, secondaryButton, className, ...marginProps }) => (_jsxs(Container, { $imagePosition: imagePosition, $backgroundColor: backgroundColor, className: className, ...marginProps, children: [illustration && _jsx(IllustrationWrapper, { children: illustration }), _jsxs(Content, { children: [_jsxs(Box, { flex: true, direction: "column", gap: "space.050", children: [_jsx(Text, { typo: "heading-small", children: title }), body && _jsx(Text, { typo: "body-regular", children: body })] }), (primaryButton ?? secondaryButton) && (_jsxs(Box, { flex: true, direction: "row", gap: "space.100", children: [primaryButton && (_jsx(Button, { size: "small", handleClick: primaryButton.onClick, ...toButtonProps(primaryButton, 'secondary'), children: primaryButton.label })), secondaryButton && (_jsx(Button, { size: "small", handleClick: secondaryButton.onClick, ...toButtonProps(secondaryButton, 'fallbackStyle'), children: secondaryButton.label }))] }))] })] }));
32
33
  PromoCard.displayName = 'PromoCard';
33
34
  const Container = styled(Box) `
34
35
  display: flex;
@@ -1 +1 @@
1
- {"version":3,"file":"PromoCard.js","sourceRoot":"","sources":["../../src/PromoCard/PromoCard.tsx"],"names":[],"mappings":";AACA,OAAO,MAAM,MAAM,mBAAmB,CAAA;AAItC,OAAO,EAEL,mBAAmB,GACpB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAwB9B,MAAM,aAAa,GAAG,CACpB,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAmB,EAC/C,cAAsC,EACtC,EAAE;IACF,MAAM,QAAQ,GAAG,OAAO,IAAI,cAAc,CAAA;IAC1C,OAAO;QACL,QAAQ;QACR,OAAO;QACP,SAAS,EAAE,QAAQ,KAAK,WAAW;QACnC,aAAa,EAAE,QAAQ,KAAK,eAAe;QAC3C,OAAO,EAAE,QAAQ,KAAK,UAAU;KACjC,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,SAAS,GAAuB,CAAC,EAC5C,KAAK,EACL,IAAI,EACJ,aAAa,GAAG,MAAM,EACtB,eAAe,GAAG,SAAS,EAC3B,YAAY,EACZ,aAAa,EACb,eAAe,EACf,SAAS,EACT,GAAG,WAAW,EACf,EAAE,EAAE,CAAC,CACJ,MAAC,SAAS,IACR,cAAc,EAAE,aAAa,EAC7B,gBAAgB,EAAE,eAAe,EACjC,SAAS,EAAE,SAAS,KAChB,WAAW,aAEd,YAAY,IAAI,KAAC,mBAAmB,cAAE,YAAY,GAAuB,EAC1E,MAAC,OAAO,eACN,MAAC,GAAG,IAAC,IAAI,QAAC,SAAS,EAAC,QAAQ,EAAC,GAAG,EAAC,WAAW,aAC1C,KAAC,IAAI,IAAC,IAAI,EAAC,eAAe,YAAE,KAAK,GAAQ,EACxC,IAAI,IAAI,KAAC,IAAI,IAAC,IAAI,EAAC,cAAc,YAAE,IAAI,GAAQ,IAC5C,EACL,CAAC,aAAa,IAAI,eAAe,CAAC,IAAI,CACrC,MAAC,GAAG,IAAC,IAAI,QAAC,SAAS,EAAC,KAAK,EAAC,GAAG,EAAC,WAAW,aACtC,aAAa,IAAI,CAChB,KAAC,MAAM,IACL,WAAW,QACX,WAAW,EAAE,aAAa,CAAC,OAAO,KAC9B,aAAa,CAAC,aAAa,EAAE,WAAW,CAAC,YAE5C,aAAa,CAAC,KAAK,GACb,CACV,EACA,eAAe,IAAI,CAClB,KAAC,MAAM,IACL,WAAW,QACX,WAAW,EAAE,eAAe,CAAC,OAAO,KAChC,aAAa,CAAC,eAAe,EAAE,eAAe,CAAC,YAElD,eAAe,CAAC,KAAK,GACf,CACV,IACG,CACP,IACO,IACA,CACb,CAAA;AAED,SAAS,CAAC,WAAW,GAAG,WAAW,CAAA;AAMnC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAY;;oBAErB,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,CACvC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK;;SAE7C,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;aAC3B,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;gBAC5B,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAC5C,mBAAmB,CAAC,gBAAgB,EAAE,KAAK,CAAC;;;CAG/C,CAAA;AAED,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;;SAGlB,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;CAGvC,CAAA;AAED,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;;;;;;CAOtC,CAAA","sourcesContent":["import { FC, ReactNode } from 'react'\nimport styled from 'styled-components'\n\nimport { MarginProps } from '../utils/space'\nimport { TransientProps } from '../utils/utilTypes'\nimport {\n ColorTypes,\n resolveToThemeColor,\n} from '../ThemeProvider/utils/colourMap'\nimport { Box } from '../Box'\nimport { Button } from '../Button'\nimport { Text } from '../Text'\n\ntype PromoCardButtonVariant = 'secondary' | 'fallbackStyle' | 'tertiary'\n\nexport type PromoCardAction = {\n label: string\n onClick: () => void\n variant?: PromoCardButtonVariant\n disabled?: boolean\n loading?: boolean\n}\n\nexport type PromoCardProps = {\n title: string\n body?: string\n imagePosition?: 'left' | 'right'\n /** Card background, as a theme colour. Defaults to `custard`. */\n backgroundColor?: ColorTypes\n illustration?: ReactNode\n primaryButton?: PromoCardAction\n secondaryButton?: PromoCardAction\n className?: string\n} & MarginProps\n\nconst toButtonProps = (\n { variant, disabled, loading }: PromoCardAction,\n defaultVariant: PromoCardButtonVariant,\n) => {\n const resolved = variant ?? defaultVariant\n return {\n disabled,\n loading,\n secondary: resolved === 'secondary',\n fallbackStyle: resolved === 'fallbackStyle',\n textBtn: resolved === 'tertiary',\n }\n}\n\n/**\n * A small promotional card following the Figma \"PromoCardSmall\" design\n * (S-mores components, node 33734-3198).\n *\n * The card is a horizontal layout containing an optional illustration slot\n * (positioned left or right via `imagePosition`), a heading + body text\n * group, and an optional pair of CTA buttons whose variant, disabled, and\n * loading states are fully controlled by the consumer.\n *\n * **Scope:** this component implements the *small* iteration of PromoCard\n * only. A larger variant (if required) would need to be added separately.\n *\n * @see https://www.figma.com/design/FqSaizGOyOzXFZNmZ4X0LGMn/%F0%9F%A7%B1-S-mores-components?node-id=33734-3198\n */\nexport const PromoCard: FC<PromoCardProps> = ({\n title,\n body,\n imagePosition = 'left',\n backgroundColor = 'custard',\n illustration,\n primaryButton,\n secondaryButton,\n className,\n ...marginProps\n}) => (\n <Container\n $imagePosition={imagePosition}\n $backgroundColor={backgroundColor}\n className={className}\n {...marginProps}\n >\n {illustration && <IllustrationWrapper>{illustration}</IllustrationWrapper>}\n <Content>\n <Box flex direction=\"column\" gap=\"space.050\">\n <Text typo=\"heading-small\">{title}</Text>\n {body && <Text typo=\"body-regular\">{body}</Text>}\n </Box>\n {(primaryButton ?? secondaryButton) && (\n <Box flex direction=\"row\" gap=\"space.100\">\n {primaryButton && (\n <Button\n smallButton\n handleClick={primaryButton.onClick}\n {...toButtonProps(primaryButton, 'secondary')}\n >\n {primaryButton.label}\n </Button>\n )}\n {secondaryButton && (\n <Button\n smallButton\n handleClick={secondaryButton.onClick}\n {...toButtonProps(secondaryButton, 'fallbackStyle')}\n >\n {secondaryButton.label}\n </Button>\n )}\n </Box>\n )}\n </Content>\n </Container>\n)\n\nPromoCard.displayName = 'PromoCard'\n\ntype IContainer = TransientProps<\n Required<Pick<PromoCardProps, 'imagePosition' | 'backgroundColor'>>\n>\n\nconst Container = styled(Box)<IContainer>`\n display: flex;\n flex-direction: ${({ $imagePosition }) =>\n $imagePosition === 'right' ? 'row-reverse' : 'row'};\n align-items: flex-end;\n gap: ${({ theme }) => theme.space[200]};\n padding: ${({ theme }) => theme.space[200]};\n background: ${({ theme, $backgroundColor }) =>\n resolveToThemeColor($backgroundColor, theme)};\n border-radius: 24px;\n box-sizing: border-box;\n`\n\nconst Content = styled(Box)`\n display: flex;\n flex-direction: column;\n gap: ${({ theme }) => theme.space[150]};\n flex: 1;\n overflow: hidden;\n`\n\nconst IllustrationWrapper = styled(Box)`\n flex-shrink: 0;\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n`\n"]}
1
+ {"version":3,"file":"PromoCard.js","sourceRoot":"","sources":["../../src/PromoCard/PromoCard.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode } from 'react'\nimport styled from 'styled-components'\n\nimport { MarginProps } from '../utils/space'\nimport { TransientProps } from '../utils/utilTypes'\nimport {\n ColorTypes,\n resolveToThemeColor,\n} from '../ThemeProvider/utils/colourMap'\nimport { Box } from '../Box'\nimport { Button, ButtonProps } from '../Button'\nimport { Text } from '../Text'\n\ntype PromoCardButtonVariant = 'secondary' | 'fallbackStyle' | 'tertiary'\n\nexport type PromoCardAction = {\n label: string\n onClick: () => void\n variant?: PromoCardButtonVariant\n disabled?: boolean\n loading?: boolean\n}\n\nexport type PromoCardProps = {\n title: string\n body?: string\n imagePosition?: 'left' | 'right'\n /** Card background, as a theme colour. Defaults to `custard`. */\n backgroundColor?: ColorTypes\n illustration?: ReactNode\n primaryButton?: PromoCardAction\n secondaryButton?: PromoCardAction\n className?: string\n} & MarginProps\n\nconst toButtonProps = (\n { variant, disabled, loading }: PromoCardAction,\n defaultVariant: PromoCardButtonVariant,\n): Pick<ButtonProps, 'disabled' | 'loading' | 'variant' | 'textBtn'> => {\n const resolved = variant ?? defaultVariant\n // PromoCard's tertiary is the legacy underlined text button\n if (resolved === 'tertiary') return { disabled, loading, textBtn: true }\n return {\n disabled,\n loading,\n variant: resolved === 'fallbackStyle' ? 'neutral' : 'secondary',\n }\n}\n\n/**\n * A small promotional card following the Figma \"PromoCardSmall\" design\n * (S-mores components, node 33734-3198).\n *\n * The card is a horizontal layout containing an optional illustration slot\n * (positioned left or right via `imagePosition`), a heading + body text\n * group, and an optional pair of CTA buttons whose variant, disabled, and\n * loading states are fully controlled by the consumer.\n *\n * **Scope:** this component implements the *small* iteration of PromoCard\n * only. A larger variant (if required) would need to be added separately.\n *\n * @see https://www.figma.com/design/FqSaizGOyOzXFZNmZ4X0LGMn/%F0%9F%A7%B1-S-mores-components?node-id=33734-3198\n */\nexport const PromoCard: FC<PromoCardProps> = ({\n title,\n body,\n imagePosition = 'left',\n backgroundColor = 'custard',\n illustration,\n primaryButton,\n secondaryButton,\n className,\n ...marginProps\n}) => (\n <Container\n $imagePosition={imagePosition}\n $backgroundColor={backgroundColor}\n className={className}\n {...marginProps}\n >\n {illustration && <IllustrationWrapper>{illustration}</IllustrationWrapper>}\n <Content>\n <Box flex direction=\"column\" gap=\"space.050\">\n <Text typo=\"heading-small\">{title}</Text>\n {body && <Text typo=\"body-regular\">{body}</Text>}\n </Box>\n {(primaryButton ?? secondaryButton) && (\n <Box flex direction=\"row\" gap=\"space.100\">\n {primaryButton && (\n <Button\n size=\"small\"\n handleClick={primaryButton.onClick}\n {...toButtonProps(primaryButton, 'secondary')}\n >\n {primaryButton.label}\n </Button>\n )}\n {secondaryButton && (\n <Button\n size=\"small\"\n handleClick={secondaryButton.onClick}\n {...toButtonProps(secondaryButton, 'fallbackStyle')}\n >\n {secondaryButton.label}\n </Button>\n )}\n </Box>\n )}\n </Content>\n </Container>\n)\n\nPromoCard.displayName = 'PromoCard'\n\ntype IContainer = TransientProps<\n Required<Pick<PromoCardProps, 'imagePosition' | 'backgroundColor'>>\n>\n\nconst Container = styled(Box)<IContainer>`\n display: flex;\n flex-direction: ${({ $imagePosition }) =>\n $imagePosition === 'right' ? 'row-reverse' : 'row'};\n align-items: flex-end;\n gap: ${({ theme }) => theme.space[200]};\n padding: ${({ theme }) => theme.space[200]};\n background: ${({ theme, $backgroundColor }) =>\n resolveToThemeColor($backgroundColor, theme)};\n border-radius: 24px;\n box-sizing: border-box;\n`\n\nconst Content = styled(Box)`\n display: flex;\n flex-direction: column;\n gap: ${({ theme }) => theme.space[150]};\n flex: 1;\n overflow: hidden;\n`\n\nconst IllustrationWrapper = styled(Box)`\n flex-shrink: 0;\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n`\n"]}
@@ -34,7 +34,7 @@ export type RowActionButtonDefault<T> = {
34
34
  iconButton?: Pick<IconStrictProps, 'size' | 'render' | 'iconColor' | 'backgroundColor' | 'id' | 'title'> & {
35
35
  tooltipText?: string;
36
36
  };
37
- genericButton?: Pick<ButtonProps, 'children' | 'loading' | 'disabled' | 'primary' | 'secondary' | 'fallbackStyle' | 'textBtn' | 'smallButton' | 'id'>;
37
+ genericButton?: Pick<ButtonProps, 'children' | 'loading' | 'disabled' | 'variant' | 'size' | 'primary' | 'secondary' | 'fallbackStyle' | 'textBtn' | 'smallButton' | 'id'>;
38
38
  } & BaseRowAction<T>;
39
39
  export type RowActionElementOverride<T> = {
40
40
  element: ReactElement<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/Table/types.ts"],"names":[],"mappings":"","sourcesContent":["import { ReactElement, ReactNode } from 'react'\nimport { ButtonProps } from '../Button/Button'\nimport { IconStrictProps } from '../IconStrict'\nimport type { BoxSpacing, SingleSpacing } from './helper.types'\nimport { ColorTypes } from '../ThemeProvider/utils/colourMap'\n\nexport type TableStylesProps = {\n hasKeyline?: boolean\n fixedHeader?: boolean\n headerHeight?: string\n stripedColor?: string\n stickyCell?: boolean\n headerColor?: string\n rowColor?: string\n rowBorderColor?: string\n rowActionsBgColor?: string\n width?: string\n minWidth?: string\n maxWidth?: string\n noWrapContent?: boolean\n truncateContent?: boolean\n rowPadding?: string\n columnPadding?: string\n hideOverflow?: boolean\n clickableRow?: boolean\n noRowBorderColor?: boolean\n}\n\nexport type Primitive = string | number | boolean | bigint\n\nexport type BaseRowAction<T> = {\n onClick: (rowData: T) => void\n showCondition?: (rowData: T) => boolean\n}\n\nexport type RowAction<T> =\n RowActionButtonDefault<T> | RowActionElementOverride<T>\n\nexport type RowActionButtonDefault<T> = {\n iconButton?: Pick<\n IconStrictProps,\n 'size' | 'render' | 'iconColor' | 'backgroundColor' | 'id' | 'title'\n > & { tooltipText?: string }\n genericButton?: Pick<\n ButtonProps,\n | 'children'\n | 'loading'\n | 'disabled'\n | 'primary'\n | 'secondary'\n | 'fallbackStyle'\n | 'textBtn'\n | 'smallButton'\n | 'id'\n >\n} & BaseRowAction<T>\n\nexport type RowActionElementOverride<T> = {\n element: ReactElement<any>\n} & BaseRowAction<T>\n\nexport type RowActions<T> = {\n actions: RowAction<T>[]\n minWidth?: string\n bgColor?: ColorTypes\n}\n\ntype RowCellRenderer<T> = (\n row: T,\n rowIndex: number,\n column: TableColumn<T>,\n id: string | number,\n) => ReactNode\n\nexport interface TableColumn<T> {\n name?: string | number | ReactNode\n minWidth?: string\n maxWidth?: string\n noWrapContent?: boolean\n truncateContent?: boolean\n hideOverflow?: boolean\n cell?: RowCellRenderer<T>\n}\n\ntype SubElementProps = {\n bgColor?: ColorTypes\n padding?: BoxSpacing\n margin?: BoxSpacing\n}\n\n/** @template T - The type of data the table displays. */\ninterface CommonTableProps<T, ColorT = ColorTypes> {\n /** Array of columns to display in the table. */\n columns: TableColumn<T>[]\n /** Sets the height of the header. */\n headerHeight?: string\n /** If true, the table header will be fixed/sticky. */\n fixedHeader?: boolean\n /** If true, the table header will have a key line. */\n hasKeyline?: boolean\n /** If present, the table rows will have alternating colors. */\n stripedColor?: ColorT\n /** A function to determine if a row is expandable. */\n expandable?: (rowData: T) => boolean\n /** The color for the table header. */\n headerColor?: ColorT\n /** The default color for each table row. */\n rowColor?: ColorT\n /** The default color for each table row border. */\n rowBorderColor?: ColorT\n /** If true, the table will have rounded corners */\n roundedTable?: true\n /** A React element to show when a row is expanded. */\n subTable?: SubElementProps & {\n table: (rowData: T) => ReactElement<any>\n }\n\n /** Settings for sub rows. */\n subRows?: SubElementProps & {\n /** Function that returns a React element for the sub row. */\n rows: (rowData: T) => ReactElement<any> | ReactElement<any>[]\n }\n /** Function to apply to a row, to make the entire row clickable, useful for navigation. */\n clickableRow?: (rowData: T) => void\n /** Array of actions that can be performed on each row. */\n rowActions?: RowActions<T>\n /** The Y padding for each row. */\n rowPadding?: SingleSpacing\n /** The X padding for each row. */\n columnPadding?: SingleSpacing\n /** Hides the table header. Table defaults to always showing the header. */\n hideTableHeader?: boolean\n}\n\n/**\n * Type for each table row's data object.\n **/\nexport type TableRowData = {\n /**\n * Optionally include a unique `rowKey` for stable React keys.\n * If omitted, row index is used, which may cause issues if rows move.\n */\n rowKey?: React.Key\n}\n\nexport interface TableProps<T, K = undefined> extends CommonTableProps<T> {\n /** Array of data to be displayed in the table. */\n data: T[]\n\n /** Callback to derive a stable React key for each row. Falls back to row index if not provided. */\n getRowKey?: (row: T, index: number) => React.Key\n\n /** If true, the sub table columns will be aligned with the main table columns, by using the widths of the main table columns. */\n alignSubTableColumns?: boolean\n\n /** The widths of the table columns to be set to, usually passed down to a subtable to align to the main table columns.\n * The array length should match the number of columns in the table.\n * @example ['100px', '200px', '150px']\n */\n columnWidths?: string[]\n\n footer?: TableFooter<K>\n\n /** The text to show when there is no available data to map through. */\n noDataContent?: ReactNode\n}\n\n/**\n * The type of the footer prop in the Table component.\n * It can either be an array of columns and data, or a React element.\n *\n * For columns, the columns and data will be passed to each column.cell component to generate the footer.\n * For a React element, it will render that as the table footer.\n */\nexport type TableFooter<K> =\n TableFooterColumnsProps<K> | TableFooterElementProps<K>\n\nexport interface TableRowProps<T> extends CommonTableProps<T> {\n rowData: T\n rowIndex: number\n showActions?: boolean\n hideBorder?: boolean\n\n renderSubTable?: (rowData: T) => ReactElement<any>\n}\n\nexport interface RowActionsProps<T> extends Pick<\n CommonTableProps<T, string>,\n 'expandable' | 'rowActions'\n> {\n rowData: T\n width?: string\n canExpandRow: boolean\n toggleExpansion: () => void\n isExpanded?: boolean\n}\n\ntype TableFooterElementProps<K> = {\n element: ReactElement<K>\n}\n\ntype TableFooterColumnsProps<K> = {\n /**\n * Row color of the footer.\n *\n * @default 'custard'\n */\n rowColor?: ColorTypes\n rowPadding?: SingleSpacing\n columnPadding?: SingleSpacing\n columns: TableColumn<K>[]\n data: K\n}\n\nexport type TableHeaderProps<T> = CommonTableProps<T, string>\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/Table/types.ts"],"names":[],"mappings":"","sourcesContent":["import { ReactElement, ReactNode } from 'react'\nimport { ButtonProps } from '../Button/Button'\nimport { IconStrictProps } from '../IconStrict'\nimport type { BoxSpacing, SingleSpacing } from './helper.types'\nimport { ColorTypes } from '../ThemeProvider/utils/colourMap'\n\nexport type TableStylesProps = {\n hasKeyline?: boolean\n fixedHeader?: boolean\n headerHeight?: string\n stripedColor?: string\n stickyCell?: boolean\n headerColor?: string\n rowColor?: string\n rowBorderColor?: string\n rowActionsBgColor?: string\n width?: string\n minWidth?: string\n maxWidth?: string\n noWrapContent?: boolean\n truncateContent?: boolean\n rowPadding?: string\n columnPadding?: string\n hideOverflow?: boolean\n clickableRow?: boolean\n noRowBorderColor?: boolean\n}\n\nexport type Primitive = string | number | boolean | bigint\n\nexport type BaseRowAction<T> = {\n onClick: (rowData: T) => void\n showCondition?: (rowData: T) => boolean\n}\n\nexport type RowAction<T> =\n RowActionButtonDefault<T> | RowActionElementOverride<T>\n\nexport type RowActionButtonDefault<T> = {\n iconButton?: Pick<\n IconStrictProps,\n 'size' | 'render' | 'iconColor' | 'backgroundColor' | 'id' | 'title'\n > & { tooltipText?: string }\n genericButton?: Pick<\n ButtonProps,\n | 'children'\n | 'loading'\n | 'disabled'\n | 'variant'\n | 'size'\n | 'primary'\n | 'secondary'\n | 'fallbackStyle'\n | 'textBtn'\n | 'smallButton'\n | 'id'\n >\n} & BaseRowAction<T>\n\nexport type RowActionElementOverride<T> = {\n element: ReactElement<any>\n} & BaseRowAction<T>\n\nexport type RowActions<T> = {\n actions: RowAction<T>[]\n minWidth?: string\n bgColor?: ColorTypes\n}\n\ntype RowCellRenderer<T> = (\n row: T,\n rowIndex: number,\n column: TableColumn<T>,\n id: string | number,\n) => ReactNode\n\nexport interface TableColumn<T> {\n name?: string | number | ReactNode\n minWidth?: string\n maxWidth?: string\n noWrapContent?: boolean\n truncateContent?: boolean\n hideOverflow?: boolean\n cell?: RowCellRenderer<T>\n}\n\ntype SubElementProps = {\n bgColor?: ColorTypes\n padding?: BoxSpacing\n margin?: BoxSpacing\n}\n\n/** @template T - The type of data the table displays. */\ninterface CommonTableProps<T, ColorT = ColorTypes> {\n /** Array of columns to display in the table. */\n columns: TableColumn<T>[]\n /** Sets the height of the header. */\n headerHeight?: string\n /** If true, the table header will be fixed/sticky. */\n fixedHeader?: boolean\n /** If true, the table header will have a key line. */\n hasKeyline?: boolean\n /** If present, the table rows will have alternating colors. */\n stripedColor?: ColorT\n /** A function to determine if a row is expandable. */\n expandable?: (rowData: T) => boolean\n /** The color for the table header. */\n headerColor?: ColorT\n /** The default color for each table row. */\n rowColor?: ColorT\n /** The default color for each table row border. */\n rowBorderColor?: ColorT\n /** If true, the table will have rounded corners */\n roundedTable?: true\n /** A React element to show when a row is expanded. */\n subTable?: SubElementProps & {\n table: (rowData: T) => ReactElement<any>\n }\n\n /** Settings for sub rows. */\n subRows?: SubElementProps & {\n /** Function that returns a React element for the sub row. */\n rows: (rowData: T) => ReactElement<any> | ReactElement<any>[]\n }\n /** Function to apply to a row, to make the entire row clickable, useful for navigation. */\n clickableRow?: (rowData: T) => void\n /** Array of actions that can be performed on each row. */\n rowActions?: RowActions<T>\n /** The Y padding for each row. */\n rowPadding?: SingleSpacing\n /** The X padding for each row. */\n columnPadding?: SingleSpacing\n /** Hides the table header. Table defaults to always showing the header. */\n hideTableHeader?: boolean\n}\n\n/**\n * Type for each table row's data object.\n **/\nexport type TableRowData = {\n /**\n * Optionally include a unique `rowKey` for stable React keys.\n * If omitted, row index is used, which may cause issues if rows move.\n */\n rowKey?: React.Key\n}\n\nexport interface TableProps<T, K = undefined> extends CommonTableProps<T> {\n /** Array of data to be displayed in the table. */\n data: T[]\n\n /** Callback to derive a stable React key for each row. Falls back to row index if not provided. */\n getRowKey?: (row: T, index: number) => React.Key\n\n /** If true, the sub table columns will be aligned with the main table columns, by using the widths of the main table columns. */\n alignSubTableColumns?: boolean\n\n /** The widths of the table columns to be set to, usually passed down to a subtable to align to the main table columns.\n * The array length should match the number of columns in the table.\n * @example ['100px', '200px', '150px']\n */\n columnWidths?: string[]\n\n footer?: TableFooter<K>\n\n /** The text to show when there is no available data to map through. */\n noDataContent?: ReactNode\n}\n\n/**\n * The type of the footer prop in the Table component.\n * It can either be an array of columns and data, or a React element.\n *\n * For columns, the columns and data will be passed to each column.cell component to generate the footer.\n * For a React element, it will render that as the table footer.\n */\nexport type TableFooter<K> =\n TableFooterColumnsProps<K> | TableFooterElementProps<K>\n\nexport interface TableRowProps<T> extends CommonTableProps<T> {\n rowData: T\n rowIndex: number\n showActions?: boolean\n hideBorder?: boolean\n\n renderSubTable?: (rowData: T) => ReactElement<any>\n}\n\nexport interface RowActionsProps<T> extends Pick<\n CommonTableProps<T, string>,\n 'expandable' | 'rowActions'\n> {\n rowData: T\n width?: string\n canExpandRow: boolean\n toggleExpansion: () => void\n isExpanded?: boolean\n}\n\ntype TableFooterElementProps<K> = {\n element: ReactElement<K>\n}\n\ntype TableFooterColumnsProps<K> = {\n /**\n * Row color of the footer.\n *\n * @default 'custard'\n */\n rowColor?: ColorTypes\n rowPadding?: SingleSpacing\n columnPadding?: SingleSpacing\n columns: TableColumn<K>[]\n data: K\n}\n\nexport type TableHeaderProps<T> = CommonTableProps<T, string>\n"]}
@@ -1,25 +1,30 @@
1
1
  import { useEffect } from 'react';
2
- import { clearAllBodyScrollLocks, disableBodyScroll, enableBodyScroll, } from 'body-scroll-lock';
2
+ import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock';
3
+ // body-scroll-lock restores the body on every unlock, even while other locks
4
+ // remain, so count open locks and only release the body when the last closes.
5
+ let openLocks = 0;
3
6
  const enhancedDisabeBodyScroll = (node) => {
4
7
  disableBodyScroll(node, {
5
8
  reserveScrollBarGap: true,
6
9
  allowTouchMove: () => true,
7
10
  });
8
- document.body.style.top = `-${window.scrollY}px`;
11
+ if (openLocks === 0)
12
+ document.body.style.top = `-${window.scrollY}px`;
13
+ openLocks++;
14
+ };
15
+ const enhancedEnableBodyScroll = () => {
16
+ openLocks--;
17
+ if (openLocks > 0)
18
+ return;
19
+ clearAllBodyScrollLocks();
20
+ document.body.style.top = '';
9
21
  };
10
22
  export function useBodyScrollLock({ node, showModal, }) {
11
23
  useEffect(() => {
12
- return () => clearAllBodyScrollLocks();
13
- }, []);
14
- useEffect(() => {
15
- if (node === null)
24
+ if (node === null || !showModal)
16
25
  return;
17
- if (showModal) {
18
- enhancedDisabeBodyScroll(node);
19
- }
20
- else {
21
- enableBodyScroll(node);
22
- }
26
+ enhancedDisabeBodyScroll(node);
27
+ return enhancedEnableBodyScroll;
23
28
  }, [node, showModal]);
24
29
  }
25
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/useBodyScrollLock/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,wBAAwB,GAAG,CAAC,IAA2B,EAAE,EAAE;IAC/D,iBAAiB,CAAC,IAAI,EAAE;QACtB,mBAAmB,EAAE,IAAI;QACzB,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;KAC3B,CAAC,CAAA;IAEF,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,OAAO,IAAI,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,UAAU,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,GAIV;IACC,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAA;IACxC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,KAAK,IAAI;YAAE,OAAM;QAEzB,IAAI,SAAS,EAAE,CAAC;YACd,wBAAwB,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC;aAAM,CAAC;YACN,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;AACvB,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport {\n clearAllBodyScrollLocks,\n disableBodyScroll,\n enableBodyScroll,\n} from 'body-scroll-lock'\n\nconst enhancedDisabeBodyScroll = (node: HTMLElement | Element) => {\n disableBodyScroll(node, {\n reserveScrollBarGap: true,\n allowTouchMove: () => true,\n })\n\n document.body.style.top = `-${window.scrollY}px`\n}\n\nexport function useBodyScrollLock({\n node,\n showModal,\n}: {\n node: HTMLDivElement | null\n showModal: boolean\n}) {\n useEffect(() => {\n return () => clearAllBodyScrollLocks()\n }, [])\n\n useEffect(() => {\n if (node === null) return\n\n if (showModal) {\n enhancedDisabeBodyScroll(node)\n } else {\n enableBodyScroll(node)\n }\n }, [node, showModal])\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/useBodyScrollLock/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE7E,6EAA6E;AAC7E,8EAA8E;AAC9E,IAAI,SAAS,GAAG,CAAC,CAAA;AAEjB,MAAM,wBAAwB,GAAG,CAAC,IAA2B,EAAE,EAAE;IAC/D,iBAAiB,CAAC,IAAI,EAAE;QACtB,mBAAmB,EAAE,IAAI;QACzB,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;KAC3B,CAAC,CAAA;IAEF,IAAI,SAAS,KAAK,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,OAAO,IAAI,CAAA;IACrE,SAAS,EAAE,CAAA;AACb,CAAC,CAAA;AAED,MAAM,wBAAwB,GAAG,GAAG,EAAE;IACpC,SAAS,EAAE,CAAA;IACX,IAAI,SAAS,GAAG,CAAC;QAAE,OAAM;IAEzB,uBAAuB,EAAE,CAAA;IACzB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,CAAA;AAC9B,CAAC,CAAA;AAED,MAAM,UAAU,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,GAIV;IACC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAEvC,wBAAwB,CAAC,IAAI,CAAC,CAAA;QAE9B,OAAO,wBAAwB,CAAA;IACjC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;AACvB,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock'\n\n// body-scroll-lock restores the body on every unlock, even while other locks\n// remain, so count open locks and only release the body when the last closes.\nlet openLocks = 0\n\nconst enhancedDisabeBodyScroll = (node: HTMLElement | Element) => {\n disableBodyScroll(node, {\n reserveScrollBarGap: true,\n allowTouchMove: () => true,\n })\n\n if (openLocks === 0) document.body.style.top = `-${window.scrollY}px`\n openLocks++\n}\n\nconst enhancedEnableBodyScroll = () => {\n openLocks--\n if (openLocks > 0) return\n\n clearAllBodyScrollLocks()\n document.body.style.top = ''\n}\n\nexport function useBodyScrollLock({\n node,\n showModal,\n}: {\n node: HTMLDivElement | null\n showModal: boolean\n}) {\n useEffect(() => {\n if (node === null || !showModal) return\n\n enhancedDisabeBodyScroll(node)\n\n return enhancedEnableBodyScroll\n }, [node, showModal])\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrshmllw/smores-react",
3
- "version": "16.5.0",
3
+ "version": "16.7.0",
4
4
  "description": "Collection of React components used by Marshmallow Technology",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -74,7 +74,7 @@
74
74
  "@storybook/addon-docs": "^10.6.1",
75
75
  "@storybook/addon-links": "^10.6.1",
76
76
  "@storybook/react-vite": "^10.6.1",
77
- "@storybook/test-runner": "^0.24.5",
77
+ "@storybook/test-runner": "^0.26.0",
78
78
  "@testing-library/jest-dom": "^7.0.1",
79
79
  "@testing-library/react": "^16.3.3",
80
80
  "@types/body-scroll-lock": "^3.1.2",