@grantbii/design-system 1.29.0 → 3.0.3

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.
Files changed (35) hide show
  1. package/README.md +17 -4
  2. package/core/archive/index.d.ts +0 -1
  3. package/core/archive/index.js +0 -1
  4. package/core/archive/index.js.map +1 -1
  5. package/core/molecules/Badge.js +6 -9
  6. package/core/molecules/Badge.js.map +1 -1
  7. package/core/molecules/Toast.d.ts +20 -0
  8. package/core/molecules/Toast.js +239 -0
  9. package/core/molecules/Toast.js.map +1 -0
  10. package/core/molecules/Tooltip.d.ts +20 -0
  11. package/core/molecules/Tooltip.js +182 -0
  12. package/core/molecules/Tooltip.js.map +1 -0
  13. package/core/molecules/index.d.ts +2 -0
  14. package/core/molecules/index.js +2 -0
  15. package/core/molecules/index.js.map +1 -1
  16. package/core/organisms/Dropdown.d.ts +13 -7
  17. package/core/organisms/Dropdown.js +111 -12
  18. package/core/organisms/Dropdown.js.map +1 -1
  19. package/package.json +12 -12
  20. package/stories/molecules/Toast.stories.d.ts +9 -0
  21. package/stories/molecules/Toast.stories.js +106 -0
  22. package/stories/molecules/Toast.stories.js.map +1 -0
  23. package/stories/molecules/Tooltip.stories.d.ts +11 -0
  24. package/stories/molecules/Tooltip.stories.js +33 -0
  25. package/stories/molecules/Tooltip.stories.js.map +1 -0
  26. package/stories/organisms/Dropdown.stories.d.ts +4 -2
  27. package/stories/organisms/Dropdown.stories.js +75 -18
  28. package/stories/organisms/Dropdown.stories.js.map +1 -1
  29. package/tsconfig.tsbuildinfo +1 -1
  30. package/core/archive/TallyModal.d.ts +0 -9
  31. package/core/archive/TallyModal.js +0 -46
  32. package/core/archive/TallyModal.js.map +0 -1
  33. package/stories/archive/TallyModal.stories.d.ts +0 -12
  34. package/stories/archive/TallyModal.stories.js +0 -20
  35. package/stories/archive/TallyModal.stories.js.map +0 -1
package/README.md CHANGED
@@ -35,6 +35,12 @@ The pages auto-update as you edit the files.
35
35
 
36
36
  ## Develop
37
37
 
38
+ ### Structure
39
+
40
+ The actual UI components live in `core/`.
41
+
42
+ Storybook stories live in `stories/`.
43
+
38
44
  ### Versioning
39
45
 
40
46
  Bump version number in `package.json`
@@ -55,10 +61,17 @@ cwebp -exact -lossless -progress your_image.png -o your_image.webp
55
61
 
56
62
  Bump version in `package.json`
57
63
 
58
- Check which files would be published with `npm publish --dry-run`
64
+ Check which files would be published:
59
65
 
60
- The package is published to npm automatically with [GitHub Actions](.github/workflows/continuous-delivery.yaml).
66
+ ```bash
67
+ npm run build
68
+ cd dist/
69
+ npm publish --dry-run
70
+ ```
61
71
 
62
- The corresponding documentation is published to GitHub pages automatically with [GitHub Actions](.github/workflows/continuous-delivery.yaml).
72
+ When a commit is pushed to the `prod` branch, the GitHub Actions workflow `deploy-prod.yaml` will automatically:
63
73
 
64
- Once a commit has been made on the `prod` branch, a build would be triggered.
74
+ - build the package with `npm run build`
75
+ - publish the package to npm from `dist/`
76
+ - build Storybook with `npm run build-storybook`
77
+ - publish Storybook to GitHub Pages
@@ -1,4 +1,3 @@
1
1
  export { default as LocationIcon } from "./LocationIcon";
2
2
  export { default as RadioButtons, type RadioOption } from "./RadioButtons";
3
- export { default as TallyModal } from "./TallyModal";
4
3
  export { default as YesNoOptions } from "./YesNoOptions";
@@ -1,5 +1,4 @@
1
1
  export { default as LocationIcon } from "./LocationIcon";
2
2
  export { default as RadioButtons } from "./RadioButtons";
3
- export { default as TallyModal } from "./TallyModal";
4
3
  export { default as YesNoOptions } from "./YesNoOptions";
5
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../core/archive/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { default as LocationIcon } from \"./LocationIcon\";\nexport { default as RadioButtons, type RadioOption } from \"./RadioButtons\";\nexport { default as TallyModal } from \"./TallyModal\";\nexport { default as YesNoOptions } from \"./YesNoOptions\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../core/archive/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { default as LocationIcon } from \"./LocationIcon\";\nexport { default as RadioButtons, type RadioOption } from \"./RadioButtons\";\nexport { default as YesNoOptions } from \"./YesNoOptions\";\n"]}
@@ -44,17 +44,14 @@ const BaseBadge = styled.div `
44
44
  display: flex;
45
45
  align-items: center;
46
46
  justify-content: space-between;
47
- gap: 10px;
47
+ gap: ${Spacing.px12};
48
48
 
49
49
  height: 26px;
50
50
  padding: 5px ${Spacing.px12};
51
- border-radius: 100px;
52
-
53
- font-size: 12px;
54
- font-weight: ${Typography.weight.medium};
55
51
 
56
52
  color: ${(props) => props.$color};
57
53
  background-color: ${(props) => props.$defaultBackgroundColor};
54
+ border-radius: ${Spacing.px100};
58
55
 
59
56
  cursor: ${(props) => (props.$isClickable ? "pointer" : "auto")};
60
57
 
@@ -67,10 +64,10 @@ const BaseBadge = styled.div `
67
64
  const BadgeContent = styled.div `
68
65
  display: flex;
69
66
  align-items: center;
70
- gap: 10px;
67
+ gap: ${Spacing.px4};
71
68
 
72
69
  width: ${({ $widthPixels }) => ($widthPixels ? `${$widthPixels}px` : "auto")};
73
- max-width: ${({ $hasX: $closeable }) => $closeable ? "calc(100% - 20px)" : "auto"};
70
+ max-width: ${({ $hasX: $closeable }) => $closeable ? `calc(100% - ${Spacing.px20})` : "auto"};
74
71
  `;
75
72
  const IconContainer = styled.div `
76
73
  display: flex;
@@ -85,13 +82,13 @@ const BadgeLabel = styled.div `
85
82
  white-space: nowrap;
86
83
  text-overflow: ellipsis;
87
84
 
88
- ${applyTypography(Typography.bodySecondaryMedium)}
85
+ ${applyTypography(Typography.captionMedium)}
89
86
  `;
90
87
  const XButton = ({ onClick }) => (_jsx(BaseCloseButton, { type: "button", onClick: onClick, children: _jsx(SystemIcon.XIcon, { color: Color.typography.blackMedium, size: 12 }) }));
91
88
  const BaseCloseButton = styled.button `
92
89
  display: flex;
93
90
  flex-direction: column;
94
91
 
95
- min-width: 12px;
92
+ min-width: ${Spacing.px12};
96
93
  `;
97
94
  //# sourceMappingURL=Badge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../core/molecules/Badge.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType, MouseEventHandler, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { Color, Spacing, SystemIcon, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\ntype BadgeVariant = \"default\" | \"neutral\" | \"blue\" | \"green\" | \"yellow\" | \"red\";\n\ntype CustomBadgeProps = {\n label: ReactNode;\n Icon?: ComponentType<SystemIcon.IconProps>;\n onClick?: MouseEventHandler<HTMLDivElement>;\n onClickX?: MouseEventHandler<HTMLButtonElement>;\n labelWidthPixels?: number;\n};\n\ntype BadgeProps = {\n variant?: BadgeVariant;\n} & CustomBadgeProps;\n\nconst Badge = ({ variant = \"default\", ...restOfProps }: BadgeProps) => {\n const variantProps = VARIANT_PROPS_MAP[variant];\n return <RawBadge {...variantProps} {...restOfProps} />;\n};\n\nexport default Badge;\n\ntype VariantStyleProps = {\n color: Color.DesignColor;\n defaultBackgroundColor: Color.DesignColor;\n hoverBackgroundColor?: Color.DesignColor;\n};\n\nconst VARIANT_PROPS_MAP: { [variant in BadgeVariant]: VariantStyleProps } = {\n default: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n neutral: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.neutral.grey4,\n hoverBackgroundColor: Color.neutral.grey3,\n },\n blue: {\n color: Color.accent.blue1,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n green: {\n color: Color.accent.green1,\n defaultBackgroundColor: Color.accent.green3,\n hoverBackgroundColor: Color.accent.green4,\n },\n yellow: {\n color: Color.accent.yellow1,\n defaultBackgroundColor: Color.accent.yellow3,\n hoverBackgroundColor: Color.accent.yellow4,\n },\n red: {\n color: Color.accent.red1,\n defaultBackgroundColor: Color.accent.red3,\n hoverBackgroundColor: Color.accent.red4,\n },\n};\n\ntype RawBadgeProps = CustomBadgeProps & VariantStyleProps;\n\nexport const RawBadge = ({\n label,\n Icon,\n onClick,\n onClickX,\n labelWidthPixels,\n defaultBackgroundColor,\n hoverBackgroundColor = defaultBackgroundColor,\n color,\n}: RawBadgeProps) => (\n <BaseBadge\n onClick={onClick}\n $isClickable={!!onClick}\n $defaultBackgroundColor={defaultBackgroundColor}\n $hoverBackgroundColor={hoverBackgroundColor}\n $color={color}\n >\n <BadgeContent $hasX={!!onClickX} $widthPixels={labelWidthPixels}>\n {Icon ? (\n <IconContainer>\n <Icon color={color} size={12} />\n </IconContainer>\n ) : (\n <></>\n )}\n\n <BadgeLabel>{label}</BadgeLabel>\n </BadgeContent>\n\n {onClickX ? <XButton onClick={onClickX} /> : <></>}\n </BaseBadge>\n);\n\nconst BaseBadge = styled.div<{\n $isClickable: boolean;\n $defaultBackgroundColor: Color.DesignColor;\n $hoverBackgroundColor: Color.DesignColor;\n $color: Color.DesignColor;\n}>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n\n height: 26px;\n padding: 5px ${Spacing.px12};\n border-radius: 100px;\n\n font-size: 12px;\n font-weight: ${Typography.weight.medium};\n\n color: ${(props) => props.$color};\n background-color: ${(props) => props.$defaultBackgroundColor};\n\n cursor: ${(props) => (props.$isClickable ? \"pointer\" : \"auto\")};\n\n &:hover {\n background-color: ${(props) =>\n props.$isClickable\n ? props.$hoverBackgroundColor\n : props.$defaultBackgroundColor};\n }\n`;\n\nconst BadgeContent = styled.div<{\n $hasX: boolean;\n $widthPixels?: number;\n}>`\n display: flex;\n align-items: center;\n gap: 10px;\n\n width: ${({ $widthPixels }) => ($widthPixels ? `${$widthPixels}px` : \"auto\")};\n max-width: ${({ $hasX: $closeable }) =>\n $closeable ? \"calc(100% - 20px)\" : \"auto\"};\n`;\n\nconst IconContainer = styled.div<{ $iconSize?: string | number }>`\n display: flex;\n flex-direction: column;\n\n width: ${({ $iconSize = \"auto\" }) => $iconSize};\n min-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n max-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n`;\n\nconst BadgeLabel = styled.div`\n overflow-x: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n ${applyTypography(Typography.bodySecondaryMedium)}\n`;\n\ntype XButtonProps = {\n onClick: MouseEventHandler<HTMLButtonElement>;\n};\n\nconst XButton = ({ onClick }: XButtonProps) => (\n <BaseCloseButton type=\"button\" onClick={onClick}>\n <SystemIcon.XIcon color={Color.typography.blackMedium} size={12} />\n </BaseCloseButton>\n);\n\nconst BaseCloseButton = styled.button`\n display: flex;\n flex-direction: column;\n\n min-width: 12px;\n`;\n"]}
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../core/molecules/Badge.tsx"],"names":[],"mappings":";AACA,OAAO,MAAM,MAAM,mBAAmB,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAgBlD,MAAM,KAAK,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,EAAE,GAAG,WAAW,EAAc,EAAE,EAAE;IACpE,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,KAAC,QAAQ,OAAK,YAAY,KAAM,WAAW,GAAI,CAAC;AACzD,CAAC,CAAC;AAEF,eAAe,KAAK,CAAC;AAQrB,MAAM,iBAAiB,GAAqD;IAC1E,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;QACjC,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QAC1C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;KACzC;IACD,OAAO,EAAE;QACP,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;QACjC,sBAAsB,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;QAC3C,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;KAC1C;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QACzB,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;QAC1C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK;KACzC;IACD,KAAK,EAAE;QACL,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC1B,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC3C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;KAC1C;IACD,MAAM,EAAE;QACN,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC3B,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC5C,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;KAC3C;IACD,GAAG,EAAE;QACH,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACxB,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACzC,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;KACxC;CACF,CAAC;AAIF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GAAG,sBAAsB,EAC7C,KAAK,GACS,EAAE,EAAE,CAAC,CACnB,MAAC,SAAS,IACR,OAAO,EAAE,OAAO,kBACF,CAAC,CAAC,OAAO,6BACE,sBAAsB,2BACxB,oBAAoB,YACnC,KAAK,aAEb,MAAC,YAAY,aAAQ,CAAC,CAAC,QAAQ,kBAAgB,gBAAgB,aAC5D,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,aAAa,cACZ,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,GAAI,GAClB,CACjB,CAAC,CAAC,CAAC,CACF,mBAAK,CACN,EAED,KAAC,UAAU,cAAE,KAAK,GAAc,IACnB,EAEd,QAAQ,CAAC,CAAC,CAAC,KAAC,OAAO,IAAC,OAAO,EAAE,QAAQ,GAAI,CAAC,CAAC,CAAC,mBAAK,IACxC,CACb,CAAC;AAEF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAK1B;;;;SAIO,OAAO,CAAC,IAAI;;;iBAGJ,OAAO,CAAC,IAAI;;WAElB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM;sBACZ,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,uBAAuB;mBAC3C,OAAO,CAAC,KAAK;;YAEpB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;;;wBAGxC,CAAC,KAAK,EAAE,EAAE,CAC5B,KAAK,CAAC,YAAY;IAChB,CAAC,CAAC,KAAK,CAAC,qBAAqB;IAC7B,CAAC,CAAC,KAAK,CAAC,uBAAuB;;CAEtC,CAAC;AAEF,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAG7B;;;SAGO,OAAO,CAAC,GAAG;;WAET,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;eAC/D,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,CACrC,UAAU,CAAC,CAAC,CAAC,eAAe,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM;CACvD,CAAC;AAEF,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAiC;;;;WAItD,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,SAAS;eACjC,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,SAAS;eACrC,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,SAAS;CACnD,CAAC;AAEF,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;IAKzB,eAAe,CAAC,UAAU,CAAC,aAAa,CAAC;CAC5C,CAAC;AAMF,MAAM,OAAO,GAAG,CAAC,EAAE,OAAO,EAAgB,EAAE,EAAE,CAAC,CAC7C,KAAC,eAAe,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,OAAO,YAC7C,KAAC,UAAU,CAAC,KAAK,IAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,GAAI,GACnD,CACnB,CAAC;AAEF,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAA;;;;eAItB,OAAO,CAAC,IAAI;CAC1B,CAAC","sourcesContent":["import type { ComponentType, MouseEventHandler, ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { Color, Spacing, SystemIcon, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\ntype BadgeVariant = \"default\" | \"neutral\" | \"blue\" | \"green\" | \"yellow\" | \"red\";\n\ntype CustomBadgeProps = {\n label: ReactNode;\n Icon?: ComponentType<SystemIcon.IconProps>;\n onClick?: MouseEventHandler<HTMLDivElement>;\n onClickX?: MouseEventHandler<HTMLButtonElement>;\n labelWidthPixels?: number;\n};\n\ntype BadgeProps = {\n variant?: BadgeVariant;\n} & CustomBadgeProps;\n\nconst Badge = ({ variant = \"default\", ...restOfProps }: BadgeProps) => {\n const variantProps = VARIANT_PROPS_MAP[variant];\n return <RawBadge {...variantProps} {...restOfProps} />;\n};\n\nexport default Badge;\n\ntype VariantStyleProps = {\n color: Color.DesignColor;\n defaultBackgroundColor: Color.DesignColor;\n hoverBackgroundColor?: Color.DesignColor;\n};\n\nconst VARIANT_PROPS_MAP: { [variant in BadgeVariant]: VariantStyleProps } = {\n default: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n neutral: {\n color: Color.typography.blackHigh,\n defaultBackgroundColor: Color.neutral.grey4,\n hoverBackgroundColor: Color.neutral.grey3,\n },\n blue: {\n color: Color.accent.blue1,\n defaultBackgroundColor: Color.accent.blue3,\n hoverBackgroundColor: Color.accent.blue4,\n },\n green: {\n color: Color.accent.green1,\n defaultBackgroundColor: Color.accent.green3,\n hoverBackgroundColor: Color.accent.green4,\n },\n yellow: {\n color: Color.accent.yellow1,\n defaultBackgroundColor: Color.accent.yellow3,\n hoverBackgroundColor: Color.accent.yellow4,\n },\n red: {\n color: Color.accent.red1,\n defaultBackgroundColor: Color.accent.red3,\n hoverBackgroundColor: Color.accent.red4,\n },\n};\n\ntype RawBadgeProps = CustomBadgeProps & VariantStyleProps;\n\nexport const RawBadge = ({\n label,\n Icon,\n onClick,\n onClickX,\n labelWidthPixels,\n defaultBackgroundColor,\n hoverBackgroundColor = defaultBackgroundColor,\n color,\n}: RawBadgeProps) => (\n <BaseBadge\n onClick={onClick}\n $isClickable={!!onClick}\n $defaultBackgroundColor={defaultBackgroundColor}\n $hoverBackgroundColor={hoverBackgroundColor}\n $color={color}\n >\n <BadgeContent $hasX={!!onClickX} $widthPixels={labelWidthPixels}>\n {Icon ? (\n <IconContainer>\n <Icon color={color} size={12} />\n </IconContainer>\n ) : (\n <></>\n )}\n\n <BadgeLabel>{label}</BadgeLabel>\n </BadgeContent>\n\n {onClickX ? <XButton onClick={onClickX} /> : <></>}\n </BaseBadge>\n);\n\nconst BaseBadge = styled.div<{\n $isClickable: boolean;\n $defaultBackgroundColor: Color.DesignColor;\n $hoverBackgroundColor: Color.DesignColor;\n $color: Color.DesignColor;\n}>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: ${Spacing.px12};\n\n height: 26px;\n padding: 5px ${Spacing.px12};\n\n color: ${(props) => props.$color};\n background-color: ${(props) => props.$defaultBackgroundColor};\n border-radius: ${Spacing.px100};\n\n cursor: ${(props) => (props.$isClickable ? \"pointer\" : \"auto\")};\n\n &:hover {\n background-color: ${(props) =>\n props.$isClickable\n ? props.$hoverBackgroundColor\n : props.$defaultBackgroundColor};\n }\n`;\n\nconst BadgeContent = styled.div<{\n $hasX: boolean;\n $widthPixels?: number;\n}>`\n display: flex;\n align-items: center;\n gap: ${Spacing.px4};\n\n width: ${({ $widthPixels }) => ($widthPixels ? `${$widthPixels}px` : \"auto\")};\n max-width: ${({ $hasX: $closeable }) =>\n $closeable ? `calc(100% - ${Spacing.px20})` : \"auto\"};\n`;\n\nconst IconContainer = styled.div<{ $iconSize?: string | number }>`\n display: flex;\n flex-direction: column;\n\n width: ${({ $iconSize = \"auto\" }) => $iconSize};\n min-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n max-width: ${({ $iconSize = \"auto\" }) => $iconSize};\n`;\n\nconst BadgeLabel = styled.div`\n overflow-x: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n ${applyTypography(Typography.captionMedium)}\n`;\n\ntype XButtonProps = {\n onClick: MouseEventHandler<HTMLButtonElement>;\n};\n\nconst XButton = ({ onClick }: XButtonProps) => (\n <BaseCloseButton type=\"button\" onClick={onClick}>\n <SystemIcon.XIcon color={Color.typography.blackMedium} size={12} />\n </BaseCloseButton>\n);\n\nconst BaseCloseButton = styled.button`\n display: flex;\n flex-direction: column;\n\n min-width: ${Spacing.px12};\n`;\n"]}
@@ -0,0 +1,20 @@
1
+ import { type HTMLAttributes, type PropsWithChildren, type ReactNode } from "react";
2
+ export type ToastVariant = "neutral" | "success" | "danger";
3
+ export type ToastProps = Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> & {
4
+ title?: ReactNode;
5
+ message?: ReactNode;
6
+ variant?: ToastVariant;
7
+ action?: {
8
+ label: ReactNode;
9
+ onClick: () => void;
10
+ };
11
+ onClose?: () => void;
12
+ };
13
+ declare const Toast: ({ title, message, variant, action, onClose, ...props }: ToastProps) => import("react").JSX.Element;
14
+ export default Toast;
15
+ export type ToastOptions = ToastProps & {
16
+ dedupeKey?: string;
17
+ duration?: number;
18
+ };
19
+ export declare const ToastProvider: ({ children }: PropsWithChildren) => import("react").JSX.Element;
20
+ export declare const useToast: () => (options: ToastOptions) => void;
@@ -0,0 +1,239 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useEffect, useState, } from "react";
3
+ import styled, { keyframes } from "styled-components";
4
+ import { Color, Responsive, Spacing, SystemIcon, Typography } from "../atoms";
5
+ import { applyTypography } from "../integrations";
6
+ const toastVariants = {
7
+ neutral: {
8
+ iconBackground: Color.neutral.grey1,
9
+ border: Color.neutral.grey2,
10
+ background: Color.neutral.grey3,
11
+ Icon: null,
12
+ },
13
+ success: {
14
+ iconBackground: Color.accent.green1,
15
+ border: Color.accent.green2,
16
+ background: Color.accent.green4,
17
+ Icon: SystemIcon.CheckIcon,
18
+ },
19
+ danger: {
20
+ iconBackground: Color.accent.red1,
21
+ border: Color.accent.red2,
22
+ background: Color.accent.red4,
23
+ Icon: SystemIcon.XIcon,
24
+ },
25
+ };
26
+ const Toast = ({ title, message, variant = "neutral", action, onClose, ...props }) => {
27
+ const { iconBackground, border, background, Icon } = toastVariants[variant];
28
+ const isCompact = title == null && !action;
29
+ return (_jsxs(ToastContainer, { ...props, role: variant === "danger" ? "alert" : "status", "$border": border, "$background": background, "$compact": isCompact, children: [_jsxs(ToastLayout, { children: [_jsxs(ToastDetails, { children: [_jsx(ToastIconContainer, { "$background": iconBackground, children: Icon ? (_jsx(Icon, { color: Color.neutral.white, size: 14, weight: "bold" })) : (_jsx(SystemIcon.InfoIcon, { color: Color.neutral.white, size: 14, weight: "bold" })) }), _jsxs(ToastText, { children: [title != null && _jsx(Title, { children: title }), message != null && _jsx(Message, { children: message })] })] }), action && (_jsx(ActionButton, { type: "button", onClick: action.onClick, children: action.label }))] }), onClose && (_jsx(CloseButton, { type: "button", onClick: onClose, children: _jsx(SystemIcon.XIcon, { color: Color.typography.blackMedium, size: 18 }) }))] }));
30
+ };
31
+ export default Toast;
32
+ const ToastContext = createContext(null);
33
+ export const ToastProvider = ({ children }) => {
34
+ const [toasts, setToasts] = useState([]);
35
+ const dismissToast = useCallback((id) => setToasts((currentToasts) => currentToasts.filter((toast) => toast.id !== id)), []);
36
+ const showToast = useCallback((options) => setToasts((currentToasts) => {
37
+ const existingToastIndex = options.dedupeKey
38
+ ? currentToasts.findIndex((toast) => toast.dedupeKey === options.dedupeKey)
39
+ : -1;
40
+ const nextToast = existingToastIndex < 0
41
+ ? { ...options, id: crypto.randomUUID(), revision: 0 }
42
+ : {
43
+ ...currentToasts[existingToastIndex],
44
+ ...options,
45
+ revision: currentToasts[existingToastIndex].revision + 1,
46
+ };
47
+ return [
48
+ nextToast,
49
+ ...currentToasts.filter((_, toastIndex) => toastIndex !== existingToastIndex),
50
+ ].slice(0, 3);
51
+ }), []);
52
+ return (_jsxs(ToastContext.Provider, { value: showToast, children: [children, _jsx(Viewport, { "data-slot": "toast-viewport", "$stackDepth": Math.max(0, toasts.length - 1), children: toasts.map(({ id, revision, dedupeKey, duration = 8000, ...props }, index) => (_jsx(StackItem, { "data-pulse": revision > 0, "$index": index, children: _jsx(TimedToast, { ...props, id: id, duration: duration, dismiss: dismissToast }) }, `${dedupeKey ?? id}-${revision}`))) })] }));
53
+ };
54
+ export const useToast = () => {
55
+ const showToast = useContext(ToastContext);
56
+ if (!showToast)
57
+ throw new Error("useToast must be used within ToastProvider");
58
+ return showToast;
59
+ };
60
+ const TimedToast = ({ id, duration, dismiss, action, onClose, onMouseEnter, onMouseLeave, onFocus, onBlur, ...props }) => {
61
+ const [isTimerPaused, setIsTimerPaused] = useState(false);
62
+ useEffect(() => {
63
+ if (isTimerPaused)
64
+ return;
65
+ const timeoutId = window.setTimeout(() => dismiss(id), duration);
66
+ return () => window.clearTimeout(timeoutId);
67
+ }, [dismiss, duration, id, isTimerPaused]);
68
+ return (_jsx(Toast, { ...props, onMouseEnter: (event) => {
69
+ setIsTimerPaused(true);
70
+ onMouseEnter?.(event);
71
+ }, onMouseLeave: (event) => {
72
+ if (!event.currentTarget.contains(document.activeElement))
73
+ setIsTimerPaused(false);
74
+ onMouseLeave?.(event);
75
+ }, onFocus: (event) => {
76
+ setIsTimerPaused(true);
77
+ onFocus?.(event);
78
+ }, onBlur: (event) => {
79
+ if (!event.currentTarget.contains(event.relatedTarget) &&
80
+ !event.currentTarget.matches(":hover"))
81
+ setIsTimerPaused(false);
82
+ onBlur?.(event);
83
+ }, action: action && {
84
+ label: action.label,
85
+ onClick: () => {
86
+ dismiss(id);
87
+ action.onClick();
88
+ },
89
+ }, onClose: () => {
90
+ onClose?.();
91
+ dismiss(id);
92
+ } }));
93
+ };
94
+ const ToastContainer = styled.div `
95
+ display: flex;
96
+ align-items: center;
97
+ gap: ${Spacing.px16};
98
+
99
+ box-sizing: border-box;
100
+ width: ${({ $compact }) => ($compact ? "300px" : "400px")};
101
+ max-width: calc(100vw - ${Spacing.px40});
102
+ min-height: ${({ $compact }) => ($compact ? "44px" : "64px")};
103
+ padding: ${Spacing.px12};
104
+
105
+ background: ${({ $background }) => $background};
106
+ border: 1px solid ${({ $border }) => $border};
107
+ border-radius: ${Spacing.px8};
108
+ `;
109
+ const ToastLayout = styled.div `
110
+ display: flex;
111
+ align-items: flex-start;
112
+ gap: ${Spacing.px8};
113
+ flex: 1;
114
+
115
+ min-width: 0;
116
+ `;
117
+ const ToastDetails = styled.div `
118
+ display: flex;
119
+ align-items: flex-start;
120
+ gap: ${Spacing.px12};
121
+ flex: 1;
122
+
123
+ min-width: 0;
124
+ `;
125
+ const ToastIconContainer = styled.span `
126
+ display: grid;
127
+ place-items: center;
128
+ flex: 0 0 ${Spacing.px20};
129
+
130
+ height: ${Spacing.px20};
131
+
132
+ background: ${({ $background }) => $background};
133
+ border-radius: ${Spacing.px4};
134
+ `;
135
+ const ToastText = styled.div `
136
+ display: flex;
137
+ flex: 1;
138
+ flex-direction: column;
139
+ justify-content: center;
140
+
141
+ min-width: 0;
142
+ `;
143
+ const Title = styled.div `
144
+ color: ${Color.typography.blackHigh};
145
+
146
+ ${applyTypography(Typography.bodySecondaryBold)}
147
+ `;
148
+ const Message = styled.div `
149
+ color: ${Color.typography.blackMedium};
150
+
151
+ ${applyTypography(Typography.bodySecondaryRegular)}
152
+ `;
153
+ const ActionButton = styled.button `
154
+ align-self: center;
155
+ flex: none;
156
+
157
+ box-sizing: border-box;
158
+ height: ${Spacing.px40};
159
+ padding: 10px 0;
160
+
161
+ color: ${Color.typography.blackHigh};
162
+ background: transparent;
163
+
164
+ text-decoration: underline;
165
+ white-space: nowrap;
166
+
167
+ cursor: pointer;
168
+
169
+ ${applyTypography(Typography.bodySecondaryMedium)}
170
+ `;
171
+ const CloseButton = styled.button `
172
+ display: grid;
173
+ place-items: center;
174
+ align-self: center;
175
+ flex: 0 0 ${Spacing.px24};
176
+
177
+ width: ${Spacing.px24};
178
+ height: ${Spacing.px24};
179
+
180
+ background: transparent;
181
+
182
+ cursor: pointer;
183
+ `;
184
+ const toastPulse = keyframes `
185
+ 0%, 100% { transform: scale(1); }
186
+ 50% { transform: scale(1.03); }
187
+ `;
188
+ const StackItem = styled.div `
189
+ grid-area: 1 / 1;
190
+ z-index: ${({ $index }) => 3 - $index};
191
+
192
+ transform: translate(
193
+ ${({ $index }) => `${-$index * 8}px`},
194
+ ${({ $index }) => `${-$index * 8}px`}
195
+ );
196
+ transition: transform 250ms ease-in-out;
197
+ pointer-events: ${({ $index }) => ($index === 0 ? "auto" : "none")};
198
+
199
+ ${({ $index }) => $index > 0 &&
200
+ `
201
+ > [role] > * {
202
+ visibility: hidden;
203
+ }
204
+ `}
205
+
206
+ &[data-pulse="true"] > [role] {
207
+ animation: ${toastPulse} 250ms ease-in-out;
208
+ }
209
+
210
+ @media (prefers-reduced-motion: reduce) {
211
+ transition: none;
212
+
213
+ &[data-pulse="true"] > [role] {
214
+ animation: none;
215
+ }
216
+ }
217
+ `;
218
+ const Viewport = styled.div `
219
+ display: grid;
220
+ padding: ${({ $stackDepth }) => `${$stackDepth * 8}px 0 0 ${$stackDepth * 8}px`};
221
+
222
+ z-index: 100;
223
+ position: fixed;
224
+ top: 25px;
225
+ right: ${Spacing.px20};
226
+ transition: padding 250ms ease-in-out;
227
+
228
+ @media (prefers-reduced-motion: reduce) {
229
+ transition: none;
230
+ }
231
+
232
+ @media (width < ${Responsive.widthBreakpoint.laptop}) {
233
+ top: auto;
234
+ right: 50%;
235
+ bottom: var(--toast-mobile-bottom-offset, ${Spacing.px88});
236
+ transform: translateX(50%);
237
+ }
238
+ `;
239
+ //# sourceMappingURL=Toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.js","sourceRoot":"","sources":["../../../core/molecules/Toast.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n createContext,\n type HTMLAttributes,\n type PropsWithChildren,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport styled, { keyframes } from \"styled-components\";\nimport { Color, Responsive, Spacing, SystemIcon, Typography } from \"../atoms\";\nimport { applyTypography } from \"../integrations\";\n\nexport type ToastVariant = \"neutral\" | \"success\" | \"danger\";\n\nexport type ToastProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"title\"\n> & {\n title?: ReactNode;\n message?: ReactNode;\n variant?: ToastVariant;\n action?: {\n label: ReactNode;\n onClick: () => void;\n };\n onClose?: () => void;\n};\n\nconst toastVariants = {\n neutral: {\n iconBackground: Color.neutral.grey1,\n border: Color.neutral.grey2,\n background: Color.neutral.grey3,\n Icon: null,\n },\n success: {\n iconBackground: Color.accent.green1,\n border: Color.accent.green2,\n background: Color.accent.green4,\n Icon: SystemIcon.CheckIcon,\n },\n danger: {\n iconBackground: Color.accent.red1,\n border: Color.accent.red2,\n background: Color.accent.red4,\n Icon: SystemIcon.XIcon,\n },\n} as const;\n\nconst Toast = ({\n title,\n message,\n variant = \"neutral\",\n action,\n onClose,\n ...props\n}: ToastProps) => {\n const { iconBackground, border, background, Icon } = toastVariants[variant];\n const isCompact = title == null && !action;\n\n return (\n <ToastContainer\n {...props}\n role={variant === \"danger\" ? \"alert\" : \"status\"}\n $border={border}\n $background={background}\n $compact={isCompact}\n >\n <ToastLayout>\n <ToastDetails>\n <ToastIconContainer $background={iconBackground}>\n {Icon ? (\n <Icon color={Color.neutral.white} size={14} weight=\"bold\" />\n ) : (\n <SystemIcon.InfoIcon\n color={Color.neutral.white}\n size={14}\n weight=\"bold\"\n />\n )}\n </ToastIconContainer>\n <ToastText>\n {title != null && <Title>{title}</Title>}\n {message != null && <Message>{message}</Message>}\n </ToastText>\n </ToastDetails>\n {action && (\n <ActionButton type=\"button\" onClick={action.onClick}>\n {action.label}\n </ActionButton>\n )}\n </ToastLayout>\n {onClose && (\n <CloseButton type=\"button\" onClick={onClose}>\n <SystemIcon.XIcon color={Color.typography.blackMedium} size={18} />\n </CloseButton>\n )}\n </ToastContainer>\n );\n};\n\nexport default Toast;\n\nexport type ToastOptions = ToastProps & {\n dedupeKey?: string;\n duration?: number;\n};\n\ntype ToastItem = ToastOptions & { id: string; revision: number };\n\nconst ToastContext = createContext<((options: ToastOptions) => void) | null>(\n null,\n);\n\nexport const ToastProvider = ({ children }: PropsWithChildren) => {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const dismissToast = useCallback(\n (id: string) =>\n setToasts((currentToasts) =>\n currentToasts.filter((toast) => toast.id !== id),\n ),\n [],\n );\n const showToast = useCallback(\n (options: ToastOptions) =>\n setToasts((currentToasts) => {\n const existingToastIndex = options.dedupeKey\n ? currentToasts.findIndex(\n (toast) => toast.dedupeKey === options.dedupeKey,\n )\n : -1;\n const nextToast =\n existingToastIndex < 0\n ? { ...options, id: crypto.randomUUID(), revision: 0 }\n : {\n ...currentToasts[existingToastIndex],\n ...options,\n revision: currentToasts[existingToastIndex].revision + 1,\n };\n\n return [\n nextToast,\n ...currentToasts.filter(\n (_, toastIndex) => toastIndex !== existingToastIndex,\n ),\n ].slice(0, 3);\n }),\n [],\n );\n\n return (\n <ToastContext.Provider value={showToast}>\n {children}\n <Viewport\n data-slot=\"toast-viewport\"\n $stackDepth={Math.max(0, toasts.length - 1)}\n >\n {toasts.map(\n ({ id, revision, dedupeKey, duration = 8000, ...props }, index) => (\n <StackItem\n key={`${dedupeKey ?? id}-${revision}`}\n data-pulse={revision > 0}\n $index={index}\n >\n <TimedToast\n {...props}\n id={id}\n duration={duration}\n dismiss={dismissToast}\n />\n </StackItem>\n ),\n )}\n </Viewport>\n </ToastContext.Provider>\n );\n};\n\nexport const useToast = () => {\n const showToast = useContext(ToastContext);\n if (!showToast) throw new Error(\"useToast must be used within ToastProvider\");\n return showToast;\n};\n\nconst TimedToast = ({\n id,\n duration,\n dismiss,\n action,\n onClose,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...props\n}: ToastOptions & { id: string; dismiss: (id: string) => void }) => {\n const [isTimerPaused, setIsTimerPaused] = useState(false);\n\n useEffect(() => {\n if (isTimerPaused) return;\n const timeoutId = window.setTimeout(() => dismiss(id), duration);\n return () => window.clearTimeout(timeoutId);\n }, [dismiss, duration, id, isTimerPaused]);\n\n return (\n <Toast\n {...props}\n onMouseEnter={(event) => {\n setIsTimerPaused(true);\n onMouseEnter?.(event);\n }}\n onMouseLeave={(event) => {\n if (!event.currentTarget.contains(document.activeElement))\n setIsTimerPaused(false);\n onMouseLeave?.(event);\n }}\n onFocus={(event) => {\n setIsTimerPaused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n if (\n !event.currentTarget.contains(event.relatedTarget) &&\n !event.currentTarget.matches(\":hover\")\n )\n setIsTimerPaused(false);\n onBlur?.(event);\n }}\n action={\n action && {\n label: action.label,\n onClick: () => {\n dismiss(id);\n action.onClick();\n },\n }\n }\n onClose={() => {\n onClose?.();\n dismiss(id);\n }}\n />\n );\n};\n\nconst ToastContainer = styled.div<{\n $border: Color.DesignColor;\n $background: Color.DesignColor;\n $compact: boolean;\n}>`\n display: flex;\n align-items: center;\n gap: ${Spacing.px16};\n\n box-sizing: border-box;\n width: ${({ $compact }) => ($compact ? \"300px\" : \"400px\")};\n max-width: calc(100vw - ${Spacing.px40});\n min-height: ${({ $compact }) => ($compact ? \"44px\" : \"64px\")};\n padding: ${Spacing.px12};\n\n background: ${({ $background }) => $background};\n border: 1px solid ${({ $border }) => $border};\n border-radius: ${Spacing.px8};\n`;\n\nconst ToastLayout = styled.div`\n display: flex;\n align-items: flex-start;\n gap: ${Spacing.px8};\n flex: 1;\n\n min-width: 0;\n`;\n\nconst ToastDetails = styled.div`\n display: flex;\n align-items: flex-start;\n gap: ${Spacing.px12};\n flex: 1;\n\n min-width: 0;\n`;\n\nconst ToastIconContainer = styled.span<{\n $background: Color.DesignColor;\n}>`\n display: grid;\n place-items: center;\n flex: 0 0 ${Spacing.px20};\n\n height: ${Spacing.px20};\n\n background: ${({ $background }) => $background};\n border-radius: ${Spacing.px4};\n`;\n\nconst ToastText = styled.div`\n display: flex;\n flex: 1;\n flex-direction: column;\n justify-content: center;\n\n min-width: 0;\n`;\n\nconst Title = styled.div`\n color: ${Color.typography.blackHigh};\n\n ${applyTypography(Typography.bodySecondaryBold)}\n`;\n\nconst Message = styled.div`\n color: ${Color.typography.blackMedium};\n\n ${applyTypography(Typography.bodySecondaryRegular)}\n`;\n\nconst ActionButton = styled.button`\n align-self: center;\n flex: none;\n\n box-sizing: border-box;\n height: ${Spacing.px40};\n padding: 10px 0;\n\n color: ${Color.typography.blackHigh};\n background: transparent;\n\n text-decoration: underline;\n white-space: nowrap;\n\n cursor: pointer;\n\n ${applyTypography(Typography.bodySecondaryMedium)}\n`;\n\nconst CloseButton = styled.button`\n display: grid;\n place-items: center;\n align-self: center;\n flex: 0 0 ${Spacing.px24};\n\n width: ${Spacing.px24};\n height: ${Spacing.px24};\n\n background: transparent;\n\n cursor: pointer;\n`;\n\nconst toastPulse = keyframes`\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.03); }\n`;\n\nconst StackItem = styled.div<{ $index: number }>`\n grid-area: 1 / 1;\n z-index: ${({ $index }) => 3 - $index};\n\n transform: translate(\n ${({ $index }) => `${-$index * 8}px`},\n ${({ $index }) => `${-$index * 8}px`}\n );\n transition: transform 250ms ease-in-out;\n pointer-events: ${({ $index }) => ($index === 0 ? \"auto\" : \"none\")};\n\n ${({ $index }) =>\n $index > 0 &&\n `\n > [role] > * {\n visibility: hidden;\n }\n `}\n\n &[data-pulse=\"true\"] > [role] {\n animation: ${toastPulse} 250ms ease-in-out;\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n\n &[data-pulse=\"true\"] > [role] {\n animation: none;\n }\n }\n`;\n\nconst Viewport = styled.div<{ $stackDepth: number }>`\n display: grid;\n padding: ${({ $stackDepth }) =>\n `${$stackDepth * 8}px 0 0 ${$stackDepth * 8}px`};\n\n z-index: 100;\n position: fixed;\n top: 25px;\n right: ${Spacing.px20};\n transition: padding 250ms ease-in-out;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n\n @media (width < ${Responsive.widthBreakpoint.laptop}) {\n top: auto;\n right: 50%;\n bottom: var(--toast-mobile-bottom-offset, ${Spacing.px88});\n transform: translateX(50%);\n }\n`;\n"]}
@@ -0,0 +1,20 @@
1
+ import { type HTMLAttributes, type PropsWithChildren, type ReactElement } from "react";
2
+ export type TooltipSide = "top" | "right" | "bottom" | "left";
3
+ export type TooltipProps = PropsWithChildren<{
4
+ open?: boolean;
5
+ defaultOpen?: boolean;
6
+ onOpenChange?: (open: boolean) => void;
7
+ }>;
8
+ declare const Tooltip: ({ children, open: isControlledOpen, defaultOpen, onOpenChange, }: TooltipProps) => import("react").JSX.Element;
9
+ export default Tooltip;
10
+ export type TooltipTriggerProps = {
11
+ children: ReactElement<{
12
+ "aria-describedby"?: string;
13
+ }>;
14
+ };
15
+ export declare const TooltipTrigger: ({ children }: TooltipTriggerProps) => import("react").JSX.Element;
16
+ export type TooltipContentProps = PropsWithChildren<Omit<HTMLAttributes<HTMLDivElement>, "content"> & {
17
+ side?: TooltipSide;
18
+ sideOffset?: number;
19
+ }>;
20
+ export declare const TooltipContent: ({ children, side, sideOffset, ...props }: TooltipContentProps) => import("react").ReactPortal | null;
@@ -0,0 +1,182 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cloneElement, createContext, useContext, useId, useLayoutEffect, useRef, useState, } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import styled, { keyframes } from "styled-components";
6
+ import { Color, Spacing, Typography } from "../atoms";
7
+ import { applyTypography } from "../integrations";
8
+ const TooltipContext = createContext(null);
9
+ const useTooltip = () => {
10
+ const tooltipContext = useContext(TooltipContext);
11
+ if (!tooltipContext)
12
+ throw new Error("Tooltip components must be used within Tooltip");
13
+ return tooltipContext;
14
+ };
15
+ const Tooltip = ({ children, open: isControlledOpen, defaultOpen = false, onOpenChange, }) => {
16
+ const [isUncontrolledOpen, setIsUncontrolledOpen] = useState(defaultOpen);
17
+ const tooltipTriggerContainerRef = useRef(null);
18
+ const tooltipContentId = useId();
19
+ const isOpen = isControlledOpen ?? isUncontrolledOpen;
20
+ const setIsOpen = (nextIsOpen) => {
21
+ if (isControlledOpen === undefined)
22
+ setIsUncontrolledOpen(nextIsOpen);
23
+ onOpenChange?.(nextIsOpen);
24
+ };
25
+ return (_jsx(TooltipContext.Provider, { value: {
26
+ tooltipContentId,
27
+ isOpen,
28
+ setIsOpen,
29
+ tooltipTriggerContainerRef,
30
+ }, children: children }));
31
+ };
32
+ export default Tooltip;
33
+ export const TooltipTrigger = ({ children }) => {
34
+ const { tooltipContentId, isOpen, setIsOpen, tooltipTriggerContainerRef } = useTooltip();
35
+ return (_jsx(TooltipTriggerContainer, { ref: tooltipTriggerContainerRef, "data-slot": "tooltip-trigger", onMouseEnter: () => setIsOpen(true), onMouseLeave: () => {
36
+ if (!tooltipTriggerContainerRef.current?.contains(document.activeElement))
37
+ setIsOpen(false);
38
+ }, onFocus: () => setIsOpen(true), onBlur: () => {
39
+ if (!tooltipTriggerContainerRef.current?.matches(":hover"))
40
+ setIsOpen(false);
41
+ }, onKeyDown: (event) => event.key === "Escape" && setIsOpen(false), children: cloneElement(children, {
42
+ "aria-describedby": isOpen
43
+ ? [children.props["aria-describedby"], tooltipContentId]
44
+ .filter(Boolean)
45
+ .join(" ")
46
+ : children.props["aria-describedby"],
47
+ }) }));
48
+ };
49
+ const tooltipSides = ["top", "right", "bottom", "left"];
50
+ const oppositeTooltipSide = {
51
+ top: "bottom",
52
+ right: "left",
53
+ bottom: "top",
54
+ left: "right",
55
+ };
56
+ const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
57
+ export const TooltipContent = ({ children, side = "top", sideOffset = 0, ...props }) => {
58
+ const { tooltipContentId, isOpen, tooltipTriggerContainerRef } = useTooltip();
59
+ const tooltipContentContainerRef = useRef(null);
60
+ const [tooltipPosition, setTooltipPosition] = useState();
61
+ useLayoutEffect(() => {
62
+ if (!isOpen)
63
+ return;
64
+ const updateTooltipPosition = () => {
65
+ const triggerContainerRect = tooltipTriggerContainerRef.current?.getBoundingClientRect();
66
+ const tooltipContentRect = tooltipContentContainerRef.current?.getBoundingClientRect();
67
+ if (!triggerContainerRect || !tooltipContentRect)
68
+ return;
69
+ const viewportPadding = 8;
70
+ const availableSpaceBySide = {
71
+ top: triggerContainerRect.top - viewportPadding,
72
+ right: window.innerWidth - triggerContainerRect.right - viewportPadding,
73
+ bottom: window.innerHeight - triggerContainerRect.bottom - viewportPadding,
74
+ left: triggerContainerRect.left - viewportPadding,
75
+ };
76
+ const oppositeSide = oppositeTooltipSide[side];
77
+ const resolvedSide = [
78
+ side,
79
+ oppositeSide,
80
+ ...tooltipSides.filter((candidateSide) => ![side, oppositeSide].includes(candidateSide)),
81
+ ].find((candidateSide) => availableSpaceBySide[candidateSide] >=
82
+ (candidateSide === "top" || candidateSide === "bottom"
83
+ ? tooltipContentRect.height
84
+ : tooltipContentRect.width) +
85
+ sideOffset) ?? side;
86
+ let tooltipTop = triggerContainerRect.top +
87
+ (triggerContainerRect.height - tooltipContentRect.height) / 2;
88
+ let tooltipLeft = triggerContainerRect.left +
89
+ (triggerContainerRect.width - tooltipContentRect.width) / 2;
90
+ if (resolvedSide === "top")
91
+ tooltipTop =
92
+ triggerContainerRect.top - tooltipContentRect.height - sideOffset;
93
+ if (resolvedSide === "right")
94
+ tooltipLeft = triggerContainerRect.right + sideOffset;
95
+ if (resolvedSide === "bottom")
96
+ tooltipTop = triggerContainerRect.bottom + sideOffset;
97
+ if (resolvedSide === "left")
98
+ tooltipLeft =
99
+ triggerContainerRect.left - tooltipContentRect.width - sideOffset;
100
+ tooltipTop = clamp(tooltipTop, viewportPadding, window.innerHeight - tooltipContentRect.height - viewportPadding);
101
+ tooltipLeft = clamp(tooltipLeft, viewportPadding, window.innerWidth - tooltipContentRect.width - viewportPadding);
102
+ setTooltipPosition({
103
+ top: tooltipTop,
104
+ left: tooltipLeft,
105
+ side: resolvedSide,
106
+ arrowOffset: resolvedSide === "top" || resolvedSide === "bottom"
107
+ ? clamp(triggerContainerRect.left +
108
+ triggerContainerRect.width / 2 -
109
+ tooltipLeft, 8, tooltipContentRect.width - 8)
110
+ : clamp(triggerContainerRect.top +
111
+ triggerContainerRect.height / 2 -
112
+ tooltipTop, 8, tooltipContentRect.height - 8),
113
+ });
114
+ };
115
+ updateTooltipPosition();
116
+ window.addEventListener("resize", updateTooltipPosition);
117
+ window.addEventListener("scroll", updateTooltipPosition, true);
118
+ return () => {
119
+ window.removeEventListener("resize", updateTooltipPosition);
120
+ window.removeEventListener("scroll", updateTooltipPosition, true);
121
+ };
122
+ }, [children, isOpen, side, sideOffset, tooltipTriggerContainerRef]);
123
+ if (!isOpen || typeof document === "undefined")
124
+ return null;
125
+ return createPortal(_jsxs(TooltipContentContainer, { ...props, ref: tooltipContentContainerRef, id: tooltipContentId, role: "tooltip", "data-slot": "tooltip-content", "data-side": tooltipPosition?.side ?? side, "$visible": !!tooltipPosition, style: {
126
+ ...props.style,
127
+ top: tooltipPosition?.top ?? 0,
128
+ left: tooltipPosition?.left ?? 0,
129
+ }, children: [children, tooltipPosition && (_jsx(TooltipArrow, { "aria-hidden": true, "data-slot": "tooltip-arrow", "$side": tooltipPosition.side, "$offset": tooltipPosition.arrowOffset }))] }), document.body);
130
+ };
131
+ const tooltipFadeIn = keyframes `
132
+ from { opacity: 0; }
133
+ to { opacity: 1; }
134
+ `;
135
+ const TooltipTriggerContainer = styled.span `
136
+ display: inline-flex;
137
+ `;
138
+ const TooltipContentContainer = styled.div `
139
+ position: fixed;
140
+ z-index: 100;
141
+
142
+ display: flex;
143
+ flex-direction: column;
144
+ align-items: stretch;
145
+ box-sizing: border-box;
146
+ width: max-content;
147
+ max-width: min(320px, calc(100vw - ${Spacing.px16}));
148
+ padding: ${Spacing.px12};
149
+
150
+ color: ${Color.typography.whiteHigh};
151
+ background: ${Color.neutral.black};
152
+ border-radius: ${Spacing.px8};
153
+
154
+ text-align: center;
155
+ visibility: ${({ $visible }) => ($visible ? "visible" : "hidden")};
156
+ pointer-events: none;
157
+ animation: ${tooltipFadeIn} 120ms ease-out;
158
+
159
+ ${applyTypography(Typography.captionRegular)}
160
+
161
+ @media (prefers-reduced-motion: reduce) {
162
+ animation: none;
163
+ }
164
+ `;
165
+ const TooltipArrow = styled.span `
166
+ position: absolute;
167
+ ${({ $side, $offset }) => {
168
+ if ($side === "top")
169
+ return `bottom: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;
170
+ if ($side === "right")
171
+ return `left: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;
172
+ if ($side === "bottom")
173
+ return `top: -${Spacing.px4}; left: ${$offset}px; transform: translateX(-50%) rotate(45deg);`;
174
+ return `right: -${Spacing.px4}; top: ${$offset}px; transform: translateY(-50%) rotate(45deg);`;
175
+ }}
176
+
177
+ width: ${Spacing.px8};
178
+ height: ${Spacing.px8};
179
+
180
+ background: ${Color.neutral.black};
181
+ `;
182
+ //# sourceMappingURL=Tooltip.js.map