@crystallize/design-system 1.18.0 → 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,11 @@
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
+
3
9
  ## 1.18.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.css CHANGED
@@ -3640,6 +3640,97 @@ button {
3640
3640
  outline: 2px solid rgb(60, 132, 244);
3641
3641
  }
3642
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
+
3643
3734
  /* src/switch/switch.css */
3644
3735
  .c-switch-root {
3645
3736
  position: relative;
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
 
@@ -433,6 +434,22 @@ declare function RichTextEditor({ initialData, language, labelTranslations, ...r
433
434
  initialData?: CrystallizeRichText | null;
434
435
  }): JSX.Element;
435
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
+
436
453
  type TooltipProps = Partial<Pick<RadixTooltip.TooltipContentProps, 'side'>> & {
437
454
  children: ReactNode;
438
455
  content: ReactNode;
@@ -575,4 +592,4 @@ declare const tokens: {
575
592
  card: string;
576
593
  };
577
594
 
578
- 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);
@@ -38069,14 +38071,90 @@ function RichTextEditorWithoutContext({
38069
38071
  });
38070
38072
  }
38071
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
+
38072
38150
  // src/switch/switch.tsx
38073
38151
  var RadixSwitch = __toESM(require("@radix-ui/react-switch"));
38074
- var import_jsx_runtime138 = require("react/jsx-runtime");
38152
+ var import_jsx_runtime139 = require("react/jsx-runtime");
38075
38153
  function Switch2(props) {
38076
- return /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(RadixSwitch.Root, {
38154
+ return /* @__PURE__ */ (0, import_jsx_runtime139.jsx)(RadixSwitch.Root, {
38077
38155
  ...props,
38078
38156
  className: "c-switch-root",
38079
- children: /* @__PURE__ */ (0, import_jsx_runtime138.jsx)(RadixSwitch.Thumb, {
38157
+ children: /* @__PURE__ */ (0, import_jsx_runtime139.jsx)(RadixSwitch.Thumb, {
38080
38158
  className: "c-switch-thumb"
38081
38159
  })
38082
38160
  });
@@ -38111,6 +38189,7 @@ var tokens = {
38111
38189
  StackIcon,
38112
38190
  Switch,
38113
38191
  Tag,
38192
+ Toaster,
38114
38193
  Tooltip,
38115
38194
  buttonTokens,
38116
38195
  cardToken,
@@ -38120,5 +38199,6 @@ var tokens = {
38120
38199
  showError,
38121
38200
  showInfo,
38122
38201
  showWarning,
38202
+ toast,
38123
38203
  tokens
38124
38204
  });
package/dist/index.mjs CHANGED
@@ -1443,7 +1443,7 @@ Dashboard.displayName = "DashboardIcon";
1443
1443
  // src/iconography/date.tsx
1444
1444
  import { forwardRef as forwardRef27 } from "react";
1445
1445
  import { jsx as jsx34, jsxs as jsxs25 } from "react/jsx-runtime";
1446
- var Date = forwardRef27((delegated, ref) => {
1446
+ var Date2 = forwardRef27((delegated, ref) => {
1447
1447
  return /* @__PURE__ */ jsxs25("svg", {
1448
1448
  ref,
1449
1449
  width: "22",
@@ -1555,7 +1555,7 @@ var Date = forwardRef27((delegated, ref) => {
1555
1555
  ]
1556
1556
  });
1557
1557
  });
1558
- Date.displayName = "Date";
1558
+ Date2.displayName = "Date";
1559
1559
 
1560
1560
  // src/iconography/document.tsx
1561
1561
  import { forwardRef as forwardRef28 } from "react";
@@ -5298,7 +5298,7 @@ var Icon = {
5298
5298
  Location,
5299
5299
  Video,
5300
5300
  GridRelation,
5301
- Date,
5301
+ Date: Date2,
5302
5302
  Relation,
5303
5303
  Numeric,
5304
5304
  FileUpload,
@@ -9263,14 +9263,90 @@ function RichTextEditorWithoutContext({
9263
9263
  });
9264
9264
  }
9265
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
+
9266
9342
  // src/switch/switch.tsx
9267
9343
  import * as RadixSwitch from "@radix-ui/react-switch";
9268
- import { jsx as jsx138 } from "react/jsx-runtime";
9344
+ import { jsx as jsx139 } from "react/jsx-runtime";
9269
9345
  function Switch2(props) {
9270
- return /* @__PURE__ */ jsx138(RadixSwitch.Root, {
9346
+ return /* @__PURE__ */ jsx139(RadixSwitch.Root, {
9271
9347
  ...props,
9272
9348
  className: "c-switch-root",
9273
- children: /* @__PURE__ */ jsx138(RadixSwitch.Thumb, {
9349
+ children: /* @__PURE__ */ jsx139(RadixSwitch.Thumb, {
9274
9350
  className: "c-switch-thumb"
9275
9351
  })
9276
9352
  });
@@ -9304,6 +9380,7 @@ export {
9304
9380
  StackIcon,
9305
9381
  Switch2 as Switch,
9306
9382
  Tag,
9383
+ Toaster,
9307
9384
  Tooltip,
9308
9385
  buttonTokens,
9309
9386
  cardToken,
@@ -9313,5 +9390,6 @@ export {
9313
9390
  showError,
9314
9391
  showInfo,
9315
9392
  showWarning,
9393
+ toast,
9316
9394
  tokens
9317
9395
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crystallize/design-system",
3
- "version": "1.18.0",
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": {
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
 
@@ -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;