@crystallize/design-system 1.17.6 → 1.19.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @crystallize/design-system
2
2
 
3
+ ## 1.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 61e7514: Add new toast component that is based on sonner toast.
8
+
9
+ ## 1.18.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 7d5e071: Add z-index 100 to the dropdown menu content so it goes the same as dialog. There is an issue where opening a dropdown in modal would show the dropdown behind and thus be not visible.
14
+
3
15
  ## 1.17.6
4
16
 
5
17
  ### Patch Changes
package/dist/index.css CHANGED
@@ -1180,7 +1180,6 @@ button {
1180
1180
  }
1181
1181
  .c-action-menu-item {
1182
1182
  display: flex;
1183
- cursor: pointer;
1184
1183
  align-items: center;
1185
1184
  gap: 0.5rem;
1186
1185
  padding-left: 1.25rem;
@@ -1208,20 +1207,6 @@ button {
1208
1207
  font-weight: 500;
1209
1208
  color: rgb(var(--c-color-gray));
1210
1209
  }
1211
- .c-action-menu-item:hover {
1212
- --tw-bg-opacity: 1;
1213
- background-color: rgb(var(--c-color-gray-50-900) / var(--tw-bg-opacity));
1214
- }
1215
- .c-action-menu-item:focus {
1216
- --tw-bg-opacity: 1;
1217
- background-color: rgb(var(--c-color-gray-50-900) / var(--tw-bg-opacity));
1218
- outline: 2px solid transparent;
1219
- outline-offset: 2px;
1220
- }
1221
- .c-action-menu-item.danger {
1222
- --tw-text-opacity: 1;
1223
- color: rgb(var(--c-color-pink-600-300) / var(--tw-text-opacity));
1224
- }
1225
1210
  .c-action-menu-item-separator {
1226
1211
  margin: 0px;
1227
1212
  height: 1px;
@@ -1240,6 +1225,7 @@ button {
1240
1225
  .c-dropdown-menu-content {
1241
1226
  animation-duration: 0.4s;
1242
1227
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
1228
+ z-index: 100;
1243
1229
  --tw-shadow: 0 2px 4px rgba(130,138,144,0.15);
1244
1230
  --tw-shadow-colored: 0 2px 4px var(--tw-shadow-color);
1245
1231
  box-shadow:
@@ -1288,7 +1274,6 @@ button {
1288
1274
  .c-dropdown-menu-item {
1289
1275
  display: flex;
1290
1276
  height: 2.5rem;
1291
- cursor: pointer;
1292
1277
  align-items: center;
1293
1278
  --tw-bg-opacity: 1;
1294
1279
  background-color: rgb(var(--c-color-elevate) / var(--tw-bg-opacity));
@@ -1313,20 +1298,35 @@ button {
1313
1298
  font-size: 0.75rem;
1314
1299
  line-height: 1rem;
1315
1300
  font-weight: 500;
1301
+ }
1302
+ .c-dropdown-menu-item:first-child {
1303
+ border-top-left-radius: 0.25rem;
1304
+ border-top-right-radius: 0.25rem;
1305
+ }
1306
+ .c-dropdown-menu-item:last-child {
1307
+ border-bottom-left-radius: 0.25rem;
1308
+ border-bottom-right-radius: 0.25rem;
1309
+ }
1310
+ .c-dropdown-menu-item:not([data-disabled]) {
1311
+ cursor: pointer;
1316
1312
  --tw-text-opacity: 1;
1317
1313
  color: rgb(var(--c-color-gray-700-200) / var(--tw-text-opacity));
1318
1314
  }
1319
- .c-dropdown-menu-item:hover,
1320
- .c-dropdown-menu-item:focus,
1321
- .c-dropdown-menu-item:focus-visible,
1322
- .c-dropdown-menu-item:hover.selected,
1323
- .c-dropdown-menu-item:focus.selected,
1324
- .c-dropdown-menu-item:focus-visible.selected {
1315
+ .c-dropdown-menu-item:not([data-disabled]):hover,
1316
+ .c-dropdown-menu-item:not([data-disabled]):focus,
1317
+ .c-dropdown-menu-item:not([data-disabled]):focus-visible,
1318
+ .c-dropdown-menu-item:not([data-disabled]):hover.selected,
1319
+ .c-dropdown-menu-item:not([data-disabled]):focus.selected,
1320
+ .c-dropdown-menu-item:not([data-disabled]):focus-visible.selected {
1325
1321
  --tw-bg-opacity: 1;
1326
1322
  background-color: rgb(var(--c-color-gray-50-900) / var(--tw-bg-opacity));
1327
1323
  outline: 2px solid transparent;
1328
1324
  outline-offset: 2px;
1329
1325
  }
1326
+ .c-dropdown-menu-item[data-disabled] {
1327
+ cursor: not-allowed;
1328
+ color: rgb(var(--c-color-gray-700-200) / 0.5);
1329
+ }
1330
1330
  .c-dropdown-menu-item.selected {
1331
1331
  --tw-bg-opacity: 1;
1332
1332
  background-color: rgb(var(--c-color-gray-50-900) / var(--tw-bg-opacity));
@@ -1334,13 +1334,12 @@ button {
1334
1334
  --tw-text-opacity: 1;
1335
1335
  color: rgb(var(--c-color-green-500-400) / var(--tw-text-opacity));
1336
1336
  }
1337
- .c-dropdown-menu-item:first-child {
1338
- border-top-left-radius: 0.25rem;
1339
- border-top-right-radius: 0.25rem;
1337
+ .c-dropdown-menu-item.danger {
1338
+ --tw-text-opacity: 1;
1339
+ color: rgb(var(--c-color-pink-600-300) / var(--tw-text-opacity));
1340
1340
  }
1341
- .c-dropdown-menu-item:last-child {
1342
- border-bottom-left-radius: 0.25rem;
1343
- border-bottom-right-radius: 0.25rem;
1341
+ .c-dropdown-menu-item.danger[data-disabled] {
1342
+ color: rgb(var(--c-color-pink-600-300) / 0.5);
1344
1343
  }
1345
1344
  @keyframes slideUp {
1346
1345
  from {
@@ -1562,9 +1561,6 @@ button {
1562
1561
  font-weight: 500;
1563
1562
  }
1564
1563
  .c-icon-button:disabled {
1565
- --tw-scale-x: 1;
1566
- --tw-scale-y: 1;
1567
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1568
1564
  cursor: default;
1569
1565
  --tw-shadow: 0 0 #0000;
1570
1566
  --tw-shadow-colored: 0 0 #0000;
@@ -2666,10 +2662,6 @@ button {
2666
2662
  border-bottom-left-radius: 0.375rem;
2667
2663
  border-bottom-right-radius: 0.375rem;
2668
2664
  }
2669
- .c-rich-text-editor .c-rte-editor-container:not(.disabled) {
2670
- --tw-bg-opacity: 1;
2671
- background-color: rgb(255 255 255 / var(--tw-bg-opacity));
2672
- }
2673
2665
  .c-rich-text-editor .c-rte-editor {
2674
2666
  position: relative;
2675
2667
  }
@@ -3648,12 +3640,102 @@ button {
3648
3640
  outline: 2px solid rgb(60, 132, 244);
3649
3641
  }
3650
3642
 
3643
+ /* src/toast/toast.css */
3644
+ .c-toast {
3645
+ position: relative;
3646
+ display: flex;
3647
+ gap: 0.5rem;
3648
+ border-radius: 0.375rem;
3649
+ --tw-bg-opacity: 1;
3650
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
3651
+ padding-left: 0.75rem;
3652
+ padding-right: 1.5rem;
3653
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
3654
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
3655
+ box-shadow:
3656
+ var(--tw-ring-offset-shadow, 0 0 #0000),
3657
+ var(--tw-ring-shadow, 0 0 #0000),
3658
+ var(--tw-shadow);
3659
+ }
3660
+ .c-toast:hover .c-toast-close {
3661
+ display: block;
3662
+ }
3663
+ .c-toast.c-toast-with-message {
3664
+ padding-top: 0.75rem;
3665
+ padding-bottom: 0.75rem;
3666
+ }
3667
+ .c-toast.c-toast-with-message .c-toast-title {
3668
+ font-weight: 700;
3669
+ }
3670
+ .c-toast.c-toast-title-only {
3671
+ align-items: center;
3672
+ padding-top: 0.5rem;
3673
+ padding-bottom: 0.5rem;
3674
+ }
3675
+ .c-toast.c-toast-title-only .c-toast-title {
3676
+ font-weight: 500;
3677
+ }
3678
+ .c-toast-success {
3679
+ border-width: 1px;
3680
+ border-style: solid;
3681
+ --tw-border-opacity: 1;
3682
+ border-color: rgb(var(--c-color-cyan-300-600) / var(--tw-border-opacity));
3683
+ --tw-bg-opacity: 1;
3684
+ background-color: rgb(var(--c-color-cyan-100-800) / var(--tw-bg-opacity));
3685
+ --tw-text-opacity: 1;
3686
+ color: rgb(var(--c-color-green-600-300) / var(--tw-text-opacity));
3687
+ }
3688
+ .c-toast-error {
3689
+ border-width: 1px;
3690
+ border-style: solid;
3691
+ --tw-border-opacity: 1;
3692
+ border-color: rgb(var(--c-color-pink-200-700) / var(--tw-border-opacity));
3693
+ --tw-bg-opacity: 1;
3694
+ background-color: rgb(var(--c-color-pink-100-800) / var(--tw-bg-opacity));
3695
+ --tw-text-opacity: 1;
3696
+ color: rgb(var(--c-color-pink-700-200) / var(--tw-text-opacity));
3697
+ }
3698
+ .c-toast-warning {
3699
+ border-width: 1px;
3700
+ border-style: solid;
3701
+ --tw-border-opacity: 1;
3702
+ border-color: rgb(var(--c-color-orange-200-700) / var(--tw-border-opacity));
3703
+ --tw-bg-opacity: 1;
3704
+ background-color: rgb(var(--c-color-orange-100-800) / var(--tw-bg-opacity));
3705
+ --tw-text-opacity: 1;
3706
+ color: rgb(var(--c-color-green-600-300) / var(--tw-text-opacity));
3707
+ }
3708
+ .c-toast-info {
3709
+ border-width: 1px;
3710
+ border-style: solid;
3711
+ --tw-border-opacity: 1;
3712
+ border-color: rgb(var(--c-color-purple-200-700) / var(--tw-border-opacity));
3713
+ --tw-bg-opacity: 1;
3714
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
3715
+ --tw-text-opacity: 1;
3716
+ color: rgb(var(--c-color-green-600-300) / var(--tw-text-opacity));
3717
+ }
3718
+ .c-toast-title {
3719
+ font-size: 0.875rem;
3720
+ line-height: 1.25rem;
3721
+ }
3722
+ .c-toast-message {
3723
+ font-size: 13px;
3724
+ font-weight: 500;
3725
+ line-height: 1rem;
3726
+ }
3727
+ .c-toast-close {
3728
+ position: absolute;
3729
+ right: 0.25rem;
3730
+ top: 0.25rem;
3731
+ display: none;
3732
+ }
3733
+
3651
3734
  /* src/switch/switch.css */
3652
3735
  .c-switch-root {
3653
3736
  position: relative;
3654
3737
  height: 1.5rem;
3655
3738
  width: 2.75rem;
3656
- cursor: pointer;
3657
3739
  border-radius: 9999px;
3658
3740
  border-width: 1px;
3659
3741
  border-style: solid;
@@ -3674,6 +3756,9 @@ button {
3674
3756
  outline: 2px solid transparent;
3675
3757
  outline-offset: 2px;
3676
3758
  }
3759
+ .c-switch-root:enabled {
3760
+ cursor: pointer;
3761
+ }
3677
3762
  .c-switch-root:disabled {
3678
3763
  opacity: 0.4;
3679
3764
  }
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
11
11
  import * as ProgressPrimitives from '@radix-ui/react-progress';
12
12
  import * as _radix_ui_react_select from '@radix-ui/react-select';
13
13
  import * as SliderPrimitive from '@radix-ui/react-slider';
14
+ export { Toaster } from 'sonner';
14
15
  import * as RadixTooltip from '@radix-ui/react-tooltip';
15
16
  import * as RadixSwitch from '@radix-ui/react-switch';
16
17
 
@@ -20,7 +21,7 @@ declare function Separator({ className }: SeparatorProps): JSX.Element;
20
21
  type ItemProps = DropdownMenuItemProps$1 & {
21
22
  onSelect?: (event: React.MouseEvent<HTMLDivElement>) => void;
22
23
  };
23
- declare function Item({ children, className, onSelect, ...delegated }: ItemProps): JSX.Element;
24
+ declare function Item({ children, className, ...delegated }: ItemProps): JSX.Element;
24
25
 
25
26
  type DropdownMenuItemProps = DropdownMenuPrimitive.MenuItemProps & {
26
27
  children: React.ReactNode;
@@ -412,6 +413,7 @@ type CrystallizeRichTextActionMenuItem = {
412
413
  title: string;
413
414
  action: () => void;
414
415
  type?: 'default' | 'danger';
416
+ disabled?: boolean;
415
417
  };
416
418
 
417
419
  type TRichTextBase = {
@@ -432,6 +434,22 @@ declare function RichTextEditor({ initialData, language, labelTranslations, ...r
432
434
  initialData?: CrystallizeRichText | null;
433
435
  }): JSX.Element;
434
436
 
437
+ declare const toastStyles: (props?: ({
438
+ type?: "info" | "error" | "warning" | "success" | null | undefined;
439
+ } & class_variance_authority_dist_types.ClassProp) | undefined) => string;
440
+ type ToastType = 'info' | 'error' | 'success' | 'warning';
441
+ type ToastStylesProps = VariantProps<typeof toastStyles>;
442
+ type ToastProps = Omit<React.ComponentProps<'div'>, 'title'> & Omit<ToastStylesProps, 'type'> & {
443
+ title: React.ReactNode;
444
+ type?: ToastType;
445
+ message?: React.ReactNode;
446
+ timeout?: number;
447
+ };
448
+ declare const toast: {
449
+ ({ title, message, type, timeout }: ToastProps): string;
450
+ dismiss: (id?: string | number | undefined) => string | number;
451
+ };
452
+
435
453
  type TooltipProps = Partial<Pick<RadixTooltip.TooltipContentProps, 'side'>> & {
436
454
  children: ReactNode;
437
455
  content: ReactNode;
@@ -574,4 +592,4 @@ declare const tokens: {
574
592
  card: string;
575
593
  };
576
594
 
577
- export { ActionMenu, Avatar, Button, ButtonProps, Card, Checkbox, CheckboxProps, CheckboxRef, Container, CrystallizeRichText, CrystallizeRichTextActionMenuItem, Dialog, DropdownMenu, DropdownMenuRootProps, Icon, IconButton, IconButtonProps, InlineRadio, Input, InputWithLabel, Label, Progress, Radio, RichTextEditor, Select, Slider, Spinner, StackIcon, Switch, Tag, Tooltip, buttonTokens, cardToken, destroyAll, showConfirm, showDialog, showError, showInfo, showWarning, tokens };
595
+ export { ActionMenu, Avatar, Button, ButtonProps, Card, Checkbox, CheckboxProps, CheckboxRef, Container, CrystallizeRichText, CrystallizeRichTextActionMenuItem, Dialog, DropdownMenu, DropdownMenuRootProps, Icon, IconButton, IconButtonProps, InlineRadio, Input, InputWithLabel, Label, Progress, Radio, RichTextEditor, Select, Slider, Spinner, StackIcon, Switch, Tag, ToastProps, Tooltip, buttonTokens, cardToken, destroyAll, showConfirm, showDialog, showError, showInfo, showWarning, toast, tokens };
package/dist/index.js CHANGED
@@ -28875,6 +28875,7 @@ __export(src_exports, {
28875
28875
  StackIcon: () => StackIcon,
28876
28876
  Switch: () => Switch2,
28877
28877
  Tag: () => Tag,
28878
+ Toaster: () => import_sonner2.Toaster,
28878
28879
  Tooltip: () => Tooltip,
28879
28880
  buttonTokens: () => buttonTokens,
28880
28881
  cardToken: () => cardToken,
@@ -28884,6 +28885,7 @@ __export(src_exports, {
28884
28885
  showError: () => showError,
28885
28886
  showInfo: () => showInfo,
28886
28887
  showWarning: () => showWarning,
28888
+ toast: () => toast,
28887
28889
  tokens: () => tokens
28888
28890
  });
28889
28891
  module.exports = __toCommonJS(src_exports);
@@ -29167,10 +29169,9 @@ var DropdownMenu = {
29167
29169
  // src/action-menu/action-item.tsx
29168
29170
  var import_class_variance_authority5 = require("class-variance-authority");
29169
29171
  var import_jsx_runtime7 = require("react/jsx-runtime");
29170
- function Item2({ children, className, onSelect, ...delegated }) {
29172
+ function Item2({ children, className, ...delegated }) {
29171
29173
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DropdownMenu.Item, {
29172
29174
  ...delegated,
29173
- onClick: onSelect,
29174
29175
  className: (0, import_class_variance_authority5.cx)(className, ["c-action-menu-item"]),
29175
29176
  children
29176
29177
  });
@@ -37090,10 +37091,7 @@ async function copyJson(editor) {
37090
37091
  console.warn("Copy failed", error);
37091
37092
  }
37092
37093
  }
37093
- function ActionsPlugin({
37094
- append,
37095
- prepend
37096
- }) {
37094
+ function ActionsPlugin({ append, prepend, disabled }) {
37097
37095
  const [editor] = (0, import_LexicalComposerContext10.useLexicalComposerContext)();
37098
37096
  const tr = useTr();
37099
37097
  return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(ActionMenu, {
@@ -37101,6 +37099,7 @@ function ActionsPlugin({
37101
37099
  !prepend ? null : prepend.map((actionItem) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(ActionMenu.Item, {
37102
37100
  onSelect: actionItem.action,
37103
37101
  className: actionItem.type === "danger" ? "danger" : "",
37102
+ disabled: actionItem.disabled,
37104
37103
  children: actionItem.title
37105
37104
  }, actionItem.title)),
37106
37105
  /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(ActionMenu.Item, {
@@ -37108,6 +37107,7 @@ function ActionsPlugin({
37108
37107
  children: tr("actionCopyJSON")
37109
37108
  }),
37110
37109
  /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(ActionMenu.Item, {
37110
+ disabled,
37111
37111
  className: "danger",
37112
37112
  onSelect: () => {
37113
37113
  editor.dispatchCommand(import_lexical14.CLEAR_EDITOR_COMMAND, void 0);
@@ -37118,6 +37118,7 @@ function ActionsPlugin({
37118
37118
  !append ? null : append.map((actionItem) => /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(ActionMenu.Item, {
37119
37119
  onSelect: actionItem.action,
37120
37120
  className: actionItem.type === "danger" ? "danger" : "",
37121
+ disabled: actionItem.disabled,
37121
37122
  children: actionItem.title
37122
37123
  }, actionItem.title))
37123
37124
  ]
@@ -37393,7 +37394,8 @@ function Divider() {
37393
37394
  }
37394
37395
  function ToolbarPlugin({
37395
37396
  actionsMenuPrepend,
37396
- actionsMenuAppend
37397
+ actionsMenuAppend,
37398
+ disabled
37397
37399
  }) {
37398
37400
  const [editor] = (0, import_LexicalComposerContext11.useLexicalComposerContext)();
37399
37401
  const [activeEditor, setActiveEditor] = (0, import_react121.useState)(editor);
@@ -37818,6 +37820,7 @@ function ToolbarPlugin({
37818
37820
  ]
37819
37821
  }),
37820
37822
  /* @__PURE__ */ (0, import_jsx_runtime136.jsx)(ActionsPlugin, {
37823
+ disabled,
37821
37824
  prepend: actionsMenuPrepend,
37822
37825
  append: actionsMenuAppend
37823
37826
  })
@@ -38004,6 +38007,7 @@ function RichTextEditorWithoutContext({
38004
38007
  ignoreSelectionChange: true
38005
38008
  }),
38006
38009
  /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(ToolbarPlugin, {
38010
+ disabled,
38007
38011
  actionsMenuPrepend,
38008
38012
  actionsMenuAppend
38009
38013
  }),
@@ -38067,14 +38071,90 @@ function RichTextEditorWithoutContext({
38067
38071
  });
38068
38072
  }
38069
38073
 
38074
+ // src/toast/index.ts
38075
+ var import_sonner2 = require("sonner");
38076
+
38077
+ // src/toast/toast.tsx
38078
+ var import_class_variance_authority19 = require("class-variance-authority");
38079
+ var import_sonner = require("sonner");
38080
+ var import_jsx_runtime138 = require("react/jsx-runtime");
38081
+ var toastStyles = (0, import_class_variance_authority19.cva)("c-toast", {
38082
+ variants: {
38083
+ type: {
38084
+ info: "c-toast-info",
38085
+ error: "c-toast-error",
38086
+ success: "c-toast-success",
38087
+ warning: "c-toast-warning"
38088
+ }
38089
+ },
38090
+ defaultVariants: {
38091
+ type: "success"
38092
+ }
38093
+ });
38094
+ var iconMap = {
38095
+ info: Icon.Info,
38096
+ error: Icon.Error,
38097
+ success: Icon.CheckWithCircle,
38098
+ warning: Icon.Warning
38099
+ };
38100
+ var toast = ({ title, message, type = "success", timeout = 6e3 }) => {
38101
+ const ToastIcon = iconMap[type];
38102
+ const withMessage = !!message;
38103
+ const toastId = Date.now().toString();
38104
+ import_sonner.toast.custom(
38105
+ (id) => /* @__PURE__ */ (0, import_jsx_runtime138.jsxs)("div", {
38106
+ "data-testid": "toast-content",
38107
+ className: (0, import_class_variance_authority19.cx)(toastStyles({ type }), withMessage ? "c-toast-with-message" : "c-toast-title-only"),
38108
+ children: [
38109
+ /* @__PURE__ */ (0, import_jsx_runtime138.jsx)("div", {
38110
+ children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(ToastIcon, {
38111
+ width: 26,
38112
+ height: 26
38113
+ })
38114
+ }),
38115
+ /* @__PURE__ */ (0, import_jsx_runtime138.jsxs)("div", {
38116
+ children: [
38117
+ /* @__PURE__ */ (0, import_jsx_runtime138.jsx)("div", {
38118
+ className: "c-toast-title",
38119
+ children: title
38120
+ }),
38121
+ !!message && /* @__PURE__ */ (0, import_jsx_runtime138.jsx)("div", {
38122
+ className: "c-toast-message",
38123
+ children: message
38124
+ })
38125
+ ]
38126
+ }),
38127
+ /* @__PURE__ */ (0, import_jsx_runtime138.jsx)("div", {
38128
+ className: "c-toast-close",
38129
+ children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(IconButton, {
38130
+ onClick: () => import_sonner.toast.dismiss(id),
38131
+ size: "xs",
38132
+ children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(Icon.Cancel, {
38133
+ width: 12,
38134
+ height: 12
38135
+ })
38136
+ })
38137
+ })
38138
+ ]
38139
+ }),
38140
+ {
38141
+ id: toastId,
38142
+ duration: timeout,
38143
+ style: { width: "100%" }
38144
+ }
38145
+ );
38146
+ return toastId;
38147
+ };
38148
+ toast.dismiss = import_sonner.toast.dismiss;
38149
+
38070
38150
  // src/switch/switch.tsx
38071
38151
  var RadixSwitch = __toESM(require("@radix-ui/react-switch"));
38072
- var import_jsx_runtime138 = require("react/jsx-runtime");
38152
+ var import_jsx_runtime139 = require("react/jsx-runtime");
38073
38153
  function Switch2(props) {
38074
- return /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(RadixSwitch.Root, {
38154
+ return /* @__PURE__ */ (0, import_jsx_runtime139.jsx)(RadixSwitch.Root, {
38075
38155
  ...props,
38076
38156
  className: "c-switch-root",
38077
- children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(RadixSwitch.Thumb, {
38157
+ children: /* @__PURE__ */ (0, import_jsx_runtime139.jsx)(RadixSwitch.Thumb, {
38078
38158
  className: "c-switch-thumb"
38079
38159
  })
38080
38160
  });
@@ -38109,6 +38189,7 @@ var tokens = {
38109
38189
  StackIcon,
38110
38190
  Switch,
38111
38191
  Tag,
38192
+ Toaster,
38112
38193
  Tooltip,
38113
38194
  buttonTokens,
38114
38195
  cardToken,
@@ -38118,5 +38199,6 @@ var tokens = {
38118
38199
  showError,
38119
38200
  showInfo,
38120
38201
  showWarning,
38202
+ toast,
38121
38203
  tokens
38122
38204
  });
package/dist/index.mjs CHANGED
@@ -279,10 +279,9 @@ var DropdownMenu = {
279
279
  // src/action-menu/action-item.tsx
280
280
  import { cx as cx3 } from "class-variance-authority";
281
281
  import { jsx as jsx7 } from "react/jsx-runtime";
282
- function Item2({ children, className, onSelect, ...delegated }) {
282
+ function Item2({ children, className, ...delegated }) {
283
283
  return /* @__PURE__ */ jsx7(DropdownMenu.Item, {
284
284
  ...delegated,
285
- onClick: onSelect,
286
285
  className: cx3(className, ["c-action-menu-item"]),
287
286
  children
288
287
  });
@@ -1444,7 +1443,7 @@ Dashboard.displayName = "DashboardIcon";
1444
1443
  // src/iconography/date.tsx
1445
1444
  import { forwardRef as forwardRef27 } from "react";
1446
1445
  import { jsx as jsx34, jsxs as jsxs25 } from "react/jsx-runtime";
1447
- var Date = forwardRef27((delegated, ref) => {
1446
+ var Date2 = forwardRef27((delegated, ref) => {
1448
1447
  return /* @__PURE__ */ jsxs25("svg", {
1449
1448
  ref,
1450
1449
  width: "22",
@@ -1556,7 +1555,7 @@ var Date = forwardRef27((delegated, ref) => {
1556
1555
  ]
1557
1556
  });
1558
1557
  });
1559
- Date.displayName = "Date";
1558
+ Date2.displayName = "Date";
1560
1559
 
1561
1560
  // src/iconography/document.tsx
1562
1561
  import { forwardRef as forwardRef28 } from "react";
@@ -5299,7 +5298,7 @@ var Icon = {
5299
5298
  Location,
5300
5299
  Video,
5301
5300
  GridRelation,
5302
- Date,
5301
+ Date: Date2,
5303
5302
  Relation,
5304
5303
  Numeric,
5305
5304
  FileUpload,
@@ -8284,10 +8283,7 @@ async function copyJson(editor) {
8284
8283
  console.warn("Copy failed", error);
8285
8284
  }
8286
8285
  }
8287
- function ActionsPlugin({
8288
- append,
8289
- prepend
8290
- }) {
8286
+ function ActionsPlugin({ append, prepend, disabled }) {
8291
8287
  const [editor] = useLexicalComposerContext10();
8292
8288
  const tr = useTr();
8293
8289
  return /* @__PURE__ */ jsxs110(ActionMenu, {
@@ -8295,6 +8291,7 @@ function ActionsPlugin({
8295
8291
  !prepend ? null : prepend.map((actionItem) => /* @__PURE__ */ jsx134(ActionMenu.Item, {
8296
8292
  onSelect: actionItem.action,
8297
8293
  className: actionItem.type === "danger" ? "danger" : "",
8294
+ disabled: actionItem.disabled,
8298
8295
  children: actionItem.title
8299
8296
  }, actionItem.title)),
8300
8297
  /* @__PURE__ */ jsx134(ActionMenu.Item, {
@@ -8302,6 +8299,7 @@ function ActionsPlugin({
8302
8299
  children: tr("actionCopyJSON")
8303
8300
  }),
8304
8301
  /* @__PURE__ */ jsx134(ActionMenu.Item, {
8302
+ disabled,
8305
8303
  className: "danger",
8306
8304
  onSelect: () => {
8307
8305
  editor.dispatchCommand(CLEAR_EDITOR_COMMAND, void 0);
@@ -8312,6 +8310,7 @@ function ActionsPlugin({
8312
8310
  !append ? null : append.map((actionItem) => /* @__PURE__ */ jsx134(ActionMenu.Item, {
8313
8311
  onSelect: actionItem.action,
8314
8312
  className: actionItem.type === "danger" ? "danger" : "",
8313
+ disabled: actionItem.disabled,
8315
8314
  children: actionItem.title
8316
8315
  }, actionItem.title))
8317
8316
  ]
@@ -8587,7 +8586,8 @@ function Divider() {
8587
8586
  }
8588
8587
  function ToolbarPlugin({
8589
8588
  actionsMenuPrepend,
8590
- actionsMenuAppend
8589
+ actionsMenuAppend,
8590
+ disabled
8591
8591
  }) {
8592
8592
  const [editor] = useLexicalComposerContext11();
8593
8593
  const [activeEditor, setActiveEditor] = useState9(editor);
@@ -9012,6 +9012,7 @@ function ToolbarPlugin({
9012
9012
  ]
9013
9013
  }),
9014
9014
  /* @__PURE__ */ jsx136(ActionsPlugin, {
9015
+ disabled,
9015
9016
  prepend: actionsMenuPrepend,
9016
9017
  append: actionsMenuAppend
9017
9018
  })
@@ -9198,6 +9199,7 @@ function RichTextEditorWithoutContext({
9198
9199
  ignoreSelectionChange: true
9199
9200
  }),
9200
9201
  /* @__PURE__ */ jsx137(ToolbarPlugin, {
9202
+ disabled,
9201
9203
  actionsMenuPrepend,
9202
9204
  actionsMenuAppend
9203
9205
  }),
@@ -9261,14 +9263,90 @@ function RichTextEditorWithoutContext({
9261
9263
  });
9262
9264
  }
9263
9265
 
9266
+ // src/toast/index.ts
9267
+ import { Toaster } from "sonner";
9268
+
9269
+ // src/toast/toast.tsx
9270
+ import { cva as cva12, cx as cx11 } from "class-variance-authority";
9271
+ import { toast as sonnerToast } from "sonner";
9272
+ import { jsx as jsx138, jsxs as jsxs114 } from "react/jsx-runtime";
9273
+ var toastStyles = cva12("c-toast", {
9274
+ variants: {
9275
+ type: {
9276
+ info: "c-toast-info",
9277
+ error: "c-toast-error",
9278
+ success: "c-toast-success",
9279
+ warning: "c-toast-warning"
9280
+ }
9281
+ },
9282
+ defaultVariants: {
9283
+ type: "success"
9284
+ }
9285
+ });
9286
+ var iconMap = {
9287
+ info: Icon.Info,
9288
+ error: Icon.Error,
9289
+ success: Icon.CheckWithCircle,
9290
+ warning: Icon.Warning
9291
+ };
9292
+ var toast = ({ title, message, type = "success", timeout = 6e3 }) => {
9293
+ const ToastIcon = iconMap[type];
9294
+ const withMessage = !!message;
9295
+ const toastId = Date.now().toString();
9296
+ sonnerToast.custom(
9297
+ (id) => /* @__PURE__ */ jsxs114("div", {
9298
+ "data-testid": "toast-content",
9299
+ className: cx11(toastStyles({ type }), withMessage ? "c-toast-with-message" : "c-toast-title-only"),
9300
+ children: [
9301
+ /* @__PURE__ */ jsx138("div", {
9302
+ children: /* @__PURE__ */ jsx138(ToastIcon, {
9303
+ width: 26,
9304
+ height: 26
9305
+ })
9306
+ }),
9307
+ /* @__PURE__ */ jsxs114("div", {
9308
+ children: [
9309
+ /* @__PURE__ */ jsx138("div", {
9310
+ className: "c-toast-title",
9311
+ children: title
9312
+ }),
9313
+ !!message && /* @__PURE__ */ jsx138("div", {
9314
+ className: "c-toast-message",
9315
+ children: message
9316
+ })
9317
+ ]
9318
+ }),
9319
+ /* @__PURE__ */ jsx138("div", {
9320
+ className: "c-toast-close",
9321
+ children: /* @__PURE__ */ jsx138(IconButton, {
9322
+ onClick: () => sonnerToast.dismiss(id),
9323
+ size: "xs",
9324
+ children: /* @__PURE__ */ jsx138(Icon.Cancel, {
9325
+ width: 12,
9326
+ height: 12
9327
+ })
9328
+ })
9329
+ })
9330
+ ]
9331
+ }),
9332
+ {
9333
+ id: toastId,
9334
+ duration: timeout,
9335
+ style: { width: "100%" }
9336
+ }
9337
+ );
9338
+ return toastId;
9339
+ };
9340
+ toast.dismiss = sonnerToast.dismiss;
9341
+
9264
9342
  // src/switch/switch.tsx
9265
9343
  import * as RadixSwitch from "@radix-ui/react-switch";
9266
- import { jsx as jsx138 } from "react/jsx-runtime";
9344
+ import { jsx as jsx139 } from "react/jsx-runtime";
9267
9345
  function Switch2(props) {
9268
- return /* @__PURE__ */ jsx138(RadixSwitch.Root, {
9346
+ return /* @__PURE__ */ jsx139(RadixSwitch.Root, {
9269
9347
  ...props,
9270
9348
  className: "c-switch-root",
9271
- children: /* @__PURE__ */ jsx138(RadixSwitch.Thumb, {
9349
+ children: /* @__PURE__ */ jsx139(RadixSwitch.Thumb, {
9272
9350
  className: "c-switch-thumb"
9273
9351
  })
9274
9352
  });
@@ -9302,6 +9380,7 @@ export {
9302
9380
  StackIcon,
9303
9381
  Switch2 as Switch,
9304
9382
  Tag,
9383
+ Toaster,
9305
9384
  Tooltip,
9306
9385
  buttonTokens,
9307
9386
  cardToken,
@@ -9311,5 +9390,6 @@ export {
9311
9390
  showError,
9312
9391
  showInfo,
9313
9392
  showWarning,
9393
+ toast,
9314
9394
  tokens
9315
9395
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crystallize/design-system",
3
- "version": "1.17.6",
3
+ "version": "1.19.0",
4
4
  "types": "./dist/index.d.ts",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -45,10 +45,11 @@
45
45
  "@radix-ui/react-radio-group": "1.1.0",
46
46
  "@radix-ui/react-select": "1.1.2",
47
47
  "@radix-ui/react-slider": "^1.1.0",
48
- "@radix-ui/react-tooltip": "1.0.0",
49
48
  "@radix-ui/react-switch": "^1.0.2",
49
+ "@radix-ui/react-tooltip": "1.0.0",
50
50
  "class-variance-authority": "^0.4.0",
51
51
  "lexical": "0.10.0",
52
+ "sonner": "^0.6.2",
52
53
  "use-debounce": "8.0.4"
53
54
  },
54
55
  "peerDependencies": {
@@ -7,9 +7,9 @@ type ItemProps = DropdownMenuItemProps & {
7
7
  onSelect?: (event: React.MouseEvent<HTMLDivElement>) => void;
8
8
  };
9
9
 
10
- export function Item({ children, className, onSelect, ...delegated }: ItemProps) {
10
+ export function Item({ children, className, ...delegated }: ItemProps) {
11
11
  return (
12
- <DropdownMenu.Item {...delegated} onClick={onSelect} className={cx(className, ['c-action-menu-item'])}>
12
+ <DropdownMenu.Item {...delegated} className={cx(className, ['c-action-menu-item'])}>
13
13
  {children}
14
14
  </DropdownMenu.Item>
15
15
  );
@@ -45,19 +45,7 @@
45
45
  }
46
46
 
47
47
  .c-action-menu-item {
48
- @apply flex cursor-pointer items-center gap-2 px-5 py-2.5 font-sans text-sm font-medium text-gray;
49
-
50
- &:hover {
51
- @apply bg-gray-50-900;
52
- }
53
-
54
- &:focus {
55
- @apply bg-gray-50-900 outline-none;
56
- }
57
-
58
- &.danger {
59
- @apply text-pink-600-300;
60
- }
48
+ @apply flex items-center gap-2 px-5 py-2.5 font-sans text-sm font-medium text-gray;
61
49
  }
62
50
 
63
51
  .c-action-menu-item-separator {
@@ -19,7 +19,7 @@ describe('ActionMenu', () => {
19
19
  const callbackThatShouldNotBeExecuted = vi.fn();
20
20
  const user = userEvent.setup();
21
21
 
22
- const touchableProps = { onSelect: onSelectOption, onClick: onSelectOption };
22
+ const touchableProps = { onSelect: onSelectOption };
23
23
  const touchablePropsThatShouldNotBeCalled = {
24
24
  onSelect: callbackThatShouldNotBeExecuted,
25
25
  onClick: callbackThatShouldNotBeExecuted,
@@ -36,7 +36,7 @@ describe('ActionMenu', () => {
36
36
  await user.click(triggerButton);
37
37
 
38
38
  // The user should be able to see the dropdown menu
39
- const dropdownMenu = await screen.getByRole('menu');
39
+ const dropdownMenu = screen.getByRole('menu');
40
40
  expect(dropdownMenu).toBeInTheDocument();
41
41
 
42
42
  // The user should be able to see the list of options
@@ -1,7 +1,7 @@
1
1
  .c-dropdown-menu-content {
2
2
  animation-duration: 0.4s;
3
3
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
4
- @apply shadow;
4
+ @apply z-[100] shadow;
5
5
  }
6
6
 
7
7
  .c-dropdown-menu-content[data-side='top'] {
@@ -17,22 +17,37 @@
17
17
  }
18
18
 
19
19
  .c-dropdown-menu-item {
20
- @apply flex h-10 cursor-pointer items-center bg-elevate px-4 font-sans text-xs font-medium text-gray-700-200;
20
+ @apply flex h-10 items-center bg-elevate px-4 font-sans text-xs font-medium;
21
+ @apply first:rounded-tl first:rounded-tr last:rounded-bl last:rounded-br;
22
+
23
+ &:not([data-disabled]) {
24
+ @apply cursor-pointer text-gray-700-200;
25
+
26
+ &:hover,
27
+ &:focus,
28
+ &:focus-visible,
29
+ &:hover.selected,
30
+ &:focus.selected,
31
+ &:focus-visible.selected {
32
+ @apply bg-gray-50-900 outline-none;
33
+ }
34
+ }
21
35
 
22
- &:hover,
23
- &:focus,
24
- &:focus-visible,
25
- &:hover.selected,
26
- &:focus.selected,
27
- &:focus-visible.selected {
28
- @apply bg-gray-50-900 outline-none;
36
+ &[data-disabled] {
37
+ @apply cursor-not-allowed text-gray-700-200/50;
29
38
  }
30
39
 
31
40
  &.selected {
32
41
  @apply bg-gray-50-900 font-bold text-green-500-400;
33
42
  }
34
43
 
35
- @apply first:rounded-tl first:rounded-tr last:rounded-bl last:rounded-br;
44
+ &.danger {
45
+ @apply text-pink-600-300;
46
+
47
+ &[data-disabled] {
48
+ @apply text-pink-600-300/50;
49
+ }
50
+ }
36
51
  }
37
52
 
38
53
  @keyframes slideUp {
@@ -2,7 +2,7 @@
2
2
  @apply flex cursor-pointer appearance-none items-center justify-center rounded border-none bg-transparent p-0 font-medium;
3
3
 
4
4
  &:disabled {
5
- @apply scale-100 cursor-default shadow-none;
5
+ @apply cursor-default shadow-none;
6
6
  }
7
7
 
8
8
  &:enabled {
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export * from './spinner';
24
24
  export * from './stack-icon';
25
25
  export * from './tag';
26
26
  export * from './rich-text-editor';
27
+ export * from './toast';
27
28
  export * from './tooltip';
28
29
  export * from './switch';
29
30
 
@@ -25,13 +25,13 @@ async function copyJson(editor: LexicalEditor) {
25
25
  }
26
26
  }
27
27
 
28
- export default function ActionsPlugin({
29
- append,
30
- prepend,
31
- }: {
28
+ type ActionsPluginProps = {
32
29
  append?: CrystallizeRichTextActionMenuItem[];
33
30
  prepend?: CrystallizeRichTextActionMenuItem[];
34
- }): JSX.Element {
31
+ disabled?: boolean;
32
+ };
33
+
34
+ export default function ActionsPlugin({ append, prepend, disabled }: ActionsPluginProps): JSX.Element {
35
35
  const [editor] = useLexicalComposerContext();
36
36
  const tr = useTr();
37
37
 
@@ -44,12 +44,14 @@ export default function ActionsPlugin({
44
44
  key={actionItem.title}
45
45
  onSelect={actionItem.action}
46
46
  className={actionItem.type === 'danger' ? 'danger' : ''}
47
+ disabled={actionItem.disabled}
47
48
  >
48
49
  {actionItem.title}
49
50
  </ActionMenu.Item>
50
51
  ))}
51
52
  <ActionMenu.Item onSelect={() => copyJson(editor)}>{tr('actionCopyJSON')}</ActionMenu.Item>
52
53
  <ActionMenu.Item
54
+ disabled={disabled}
53
55
  className="danger"
54
56
  onSelect={() => {
55
57
  editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
@@ -65,6 +67,7 @@ export default function ActionsPlugin({
65
67
  key={actionItem.title}
66
68
  onSelect={actionItem.action}
67
69
  className={actionItem.type === 'danger' ? 'danger' : ''}
70
+ disabled={actionItem.disabled}
68
71
  >
69
72
  {actionItem.title}
70
73
  </ActionMenu.Item>
@@ -279,13 +279,17 @@ function Divider(): JSX.Element {
279
279
  return <div className="c-rte-toolbar__divider" />;
280
280
  }
281
281
 
282
+ type ToolbarPluginProps = {
283
+ actionsMenuPrepend?: CrystallizeRichTextActionMenuItem[];
284
+ actionsMenuAppend?: CrystallizeRichTextActionMenuItem[];
285
+ disabled?: boolean;
286
+ };
287
+
282
288
  export default function ToolbarPlugin({
283
289
  actionsMenuPrepend,
284
290
  actionsMenuAppend,
285
- }: {
286
- actionsMenuPrepend?: CrystallizeRichTextActionMenuItem[];
287
- actionsMenuAppend?: CrystallizeRichTextActionMenuItem[];
288
- }): JSX.Element {
291
+ disabled,
292
+ }: ToolbarPluginProps): JSX.Element {
289
293
  const [editor] = useLexicalComposerContext();
290
294
  const [activeEditor, setActiveEditor] = useState(editor);
291
295
  const [blockType, setBlockType] = useState<keyof typeof blockTypeToBlockName>('paragraph');
@@ -686,7 +690,7 @@ export default function ToolbarPlugin({
686
690
  </Dialog>
687
691
  )}
688
692
  </div>
689
- <ActionsPlugin prepend={actionsMenuPrepend} append={actionsMenuAppend} />
693
+ <ActionsPlugin disabled={disabled} prepend={actionsMenuPrepend} append={actionsMenuAppend} />
690
694
  </div>
691
695
  );
692
696
  }
@@ -40,10 +40,6 @@
40
40
  @apply relative mt-2 cursor-text rounded-bl-md rounded-br-md;
41
41
  }
42
42
 
43
- .c-rte-editor-container:not(.disabled) {
44
- @apply bg-white;
45
- }
46
-
47
43
  .c-rte-editor {
48
44
  @apply relative;
49
45
  }
@@ -140,7 +140,11 @@ function RichTextEditorWithoutContext({
140
140
  return (
141
141
  <>
142
142
  <OnChangePlugin onChange={onLocalChange} ignoreSelectionChange />
143
- <ToolbarPlugin actionsMenuPrepend={actionsMenuPrepend} actionsMenuAppend={actionsMenuAppend} />
143
+ <ToolbarPlugin
144
+ disabled={disabled}
145
+ actionsMenuPrepend={actionsMenuPrepend}
146
+ actionsMenuAppend={actionsMenuAppend}
147
+ />
144
148
  {slotPreContent}
145
149
  <div className={`c-rte-editor-container ${disabled ? 'disabled' : ''}`}>
146
150
  {maxLength != null ? <MaxLengthPlugin maxLength={maxLength} /> : null}
@@ -2,4 +2,5 @@ export type CrystallizeRichTextActionMenuItem = {
2
2
  title: string;
3
3
  action: () => void;
4
4
  type?: 'default' | 'danger';
5
+ disabled?: boolean;
5
6
  };
@@ -1,6 +1,6 @@
1
1
  .c-switch-root {
2
2
  /* @apply radix-state-checked:border-[#528693] radix-state-checked:bg-neptune h-4 w-7 rounded-full border border-solid border-[#52869399] bg-[#9095a84d] p-0 focus:outline-none focus:ring-1 focus:ring-[#528693] focus:ring-offset-1 disabled:opacity-40; */
3
- @apply relative h-6 w-11 cursor-pointer rounded-full border border-solid border-purple-200-700 bg-purple-50-900 pl-0.5 transition-all hover:border-purple-300-600 focus:outline-none disabled:opacity-40 data-[state=checked]:border-green-400-500 data-[state=checked]:bg-cyan-100-800;
3
+ @apply relative h-6 w-11 rounded-full border border-solid border-purple-200-700 bg-purple-50-900 pl-0.5 transition-all hover:border-purple-300-600 focus:outline-none enabled:cursor-pointer disabled:opacity-40 data-[state=checked]:border-green-400-500 data-[state=checked]:bg-cyan-100-800;
4
4
  }
5
5
 
6
6
  .c-switch-thumb {
@@ -0,0 +1,2 @@
1
+ export { Toaster } from 'sonner';
2
+ export { toast, type ToastProps } from './toast';
@@ -0,0 +1,51 @@
1
+ .c-toast {
2
+ @apply relative flex gap-2 rounded-md bg-white pl-3 pr-6 shadow-sm;
3
+
4
+ &:hover .c-toast-close {
5
+ @apply block;
6
+ }
7
+
8
+ &.c-toast-with-message {
9
+ @apply py-3;
10
+
11
+ & .c-toast-title {
12
+ @apply font-bold;
13
+ }
14
+ }
15
+
16
+ &.c-toast-title-only {
17
+ @apply items-center py-2;
18
+
19
+ & .c-toast-title {
20
+ @apply font-medium;
21
+ }
22
+ }
23
+ }
24
+
25
+ .c-toast-success {
26
+ @apply border border-solid border-cyan-300-600 bg-cyan-100-800 text-green-600-300;
27
+ }
28
+
29
+ .c-toast-error {
30
+ @apply border border-solid border-pink-200-700 bg-pink-100-800 text-pink-700-200;
31
+ }
32
+
33
+ .c-toast-warning {
34
+ @apply border border-solid border-orange-200-700 bg-orange-100-800 text-green-600-300;
35
+ }
36
+
37
+ .c-toast-info {
38
+ @apply border border-solid border-purple-200-700 bg-white text-green-600-300;
39
+ }
40
+
41
+ .c-toast-title {
42
+ @apply text-sm;
43
+ }
44
+
45
+ .c-toast-message {
46
+ @apply text-[13px] font-medium leading-4;
47
+ }
48
+
49
+ .c-toast-close {
50
+ @apply absolute right-1 top-1 hidden;
51
+ }
@@ -0,0 +1,110 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { toast, Toaster } from '.';
4
+ import { Button } from '../button';
5
+
6
+ const meta: Meta<typeof toast> = {
7
+ title: 'Components/Toast',
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof toast>;
12
+
13
+ export const SimpleToast: Story = {
14
+ name: 'Simple Toast',
15
+ render: () => (
16
+ <>
17
+ <Toaster />
18
+ <div className="flex w-1/2 gap-2">
19
+ <Button onClick={() => toast({ title: 'You did something good' })}>Success toast</Button>
20
+ <Button onClick={() => toast({ title: 'You have been warned buddy', type: 'warning' })}>Warning toast</Button>
21
+ <Button onClick={() => toast({ title: 'There was a oopsie daisy ', type: 'error' })}>Error toast</Button>
22
+ <Button onClick={() => toast({ title: 'Let me inform you about something ', type: 'info' })}>
23
+ Info toast
24
+ </Button>
25
+ </div>
26
+ </>
27
+ ),
28
+ };
29
+ export const WarningToast: Story = {
30
+ name: 'Toast with message ',
31
+ render: () => (
32
+ <>
33
+ <Toaster />
34
+ <div
35
+ className="flex w-full
36
+ gap-4"
37
+ >
38
+ <Button
39
+ onClick={() =>
40
+ toast({
41
+ title: 'You did alot of good',
42
+ message: 'So let me tell you are story about the bread and the toast',
43
+ })
44
+ }
45
+ >
46
+ Success message
47
+ </Button>
48
+
49
+ <Button
50
+ onClick={() =>
51
+ toast({
52
+ title: 'You have been warned with a explanation',
53
+ type: 'warning',
54
+ message: 'Here i could explain in detail why i am warning you.',
55
+ })
56
+ }
57
+ >
58
+ Warning message
59
+ </Button>
60
+ <Button
61
+ onClick={() =>
62
+ toast({
63
+ title: 'There was an oopsie daisy',
64
+ type: 'error',
65
+ message: 'And it happend because you clicked the button above',
66
+ })
67
+ }
68
+ >
69
+ Error message
70
+ </Button>
71
+ <Button
72
+ onClick={() =>
73
+ toast({
74
+ title: 'This is that something',
75
+ type: 'info',
76
+ message: 'And this is something with more details',
77
+ })
78
+ }
79
+ >
80
+ Info message
81
+ </Button>
82
+ </div>
83
+ </>
84
+ ),
85
+ };
86
+
87
+ export const TimeoutToast: Story = {
88
+ name: 'Timeout toast',
89
+ render: () => (
90
+ <>
91
+ <Toaster />
92
+ <div className="flex w-full gap-4">
93
+ <Button onClick={() => toast({ title: 'Can you read this? ', timeout: 500, type: 'info' })}>Quick toast</Button>
94
+
95
+ <Button
96
+ onClick={() =>
97
+ toast({
98
+ title: 'A slow message',
99
+ message: 'With with 20s duration, you can cross it out if you hover it ',
100
+ timeout: 20000,
101
+ type: 'info',
102
+ })
103
+ }
104
+ >
105
+ A slow toast
106
+ </Button>
107
+ </div>
108
+ </>
109
+ ),
110
+ };
@@ -0,0 +1,72 @@
1
+ import { cva, cx, VariantProps } from 'class-variance-authority';
2
+ import { toast as sonnerToast } from 'sonner';
3
+
4
+ import { IconButton } from '../icon-button';
5
+ import { Icon } from '../iconography';
6
+ import './toast.css';
7
+
8
+ const toastStyles = cva('c-toast', {
9
+ variants: {
10
+ type: {
11
+ info: 'c-toast-info',
12
+ error: 'c-toast-error',
13
+ success: 'c-toast-success',
14
+ warning: 'c-toast-warning',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ type: 'success',
19
+ },
20
+ });
21
+
22
+ type ToastType = 'info' | 'error' | 'success' | 'warning';
23
+ type ToastStylesProps = VariantProps<typeof toastStyles>;
24
+ export type ToastProps = Omit<React.ComponentProps<'div'>, 'title'> &
25
+ Omit<ToastStylesProps, 'type'> & {
26
+ title: React.ReactNode;
27
+ type?: ToastType;
28
+ message?: React.ReactNode;
29
+ timeout?: number;
30
+ };
31
+
32
+ const iconMap = {
33
+ info: Icon.Info,
34
+ error: Icon.Error,
35
+ success: Icon.CheckWithCircle,
36
+ warning: Icon.Warning,
37
+ };
38
+
39
+ export const toast = ({ title, message, type = 'success', timeout = 6000 }: ToastProps) => {
40
+ const ToastIcon = iconMap[type];
41
+ const withMessage = !!message;
42
+ const toastId = Date.now().toString();
43
+
44
+ sonnerToast.custom(
45
+ id => (
46
+ <div
47
+ data-testid="toast-content"
48
+ className={cx(toastStyles({ type }), withMessage ? 'c-toast-with-message' : 'c-toast-title-only')}
49
+ >
50
+ <div>{<ToastIcon width={26} height={26} />}</div>
51
+ <div>
52
+ <div className="c-toast-title">{title}</div>
53
+ {!!message && <div className="c-toast-message">{message}</div>}
54
+ </div>
55
+ <div className="c-toast-close">
56
+ <IconButton onClick={() => sonnerToast.dismiss(id)} size="xs">
57
+ <Icon.Cancel width={12} height={12} />
58
+ </IconButton>
59
+ </div>
60
+ </div>
61
+ ),
62
+ {
63
+ id: toastId,
64
+ duration: timeout,
65
+ style: { width: '100%' },
66
+ },
67
+ );
68
+
69
+ return toastId;
70
+ };
71
+
72
+ toast.dismiss = sonnerToast.dismiss;