aural-ui 4.0.1 → 4.2.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.
Files changed (175) hide show
  1. package/README.md +8 -1
  2. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
  3. package/dist/components/avatar/Avatar.stories.tsx +219 -235
  4. package/dist/components/badge/Badge.stories.tsx +379 -116
  5. package/dist/components/banner/Banner.stories.tsx +445 -391
  6. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  7. package/dist/components/button/Button.stories.tsx +585 -230
  8. package/dist/components/card/Card.stories.tsx +619 -301
  9. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  10. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  11. package/dist/components/chip/Chip.stories.tsx +362 -168
  12. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
  13. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  14. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  15. package/dist/components/command/Command.stories.tsx +530 -867
  16. package/dist/components/dialog/Dialog.stories.tsx +501 -950
  17. package/dist/components/divider/Divider.stories.tsx +264 -527
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -1023
  20. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
  21. package/dist/components/form/Form.stories.tsx +560 -274
  22. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  23. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
  24. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  25. package/dist/components/if-else/if-else.stories.tsx +370 -83
  26. package/dist/components/input/Input.stories.tsx +436 -368
  27. package/dist/components/label/Label.stories.tsx +156 -154
  28. package/dist/components/list/List.stories.tsx +484 -835
  29. package/dist/components/marquee/Marquee.stories.tsx +356 -712
  30. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
  31. package/dist/components/overlay/Overlay.stories.tsx +452 -824
  32. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  33. package/dist/components/popover/Popover.stories.tsx +481 -896
  34. package/dist/components/radio/Radio.stories.tsx +432 -124
  35. package/dist/components/resizable/Resizable.stories.tsx +495 -799
  36. package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
  37. package/dist/components/search/Search.stories.tsx +312 -595
  38. package/dist/components/select/Select.stories.tsx +684 -789
  39. package/dist/components/sheet/Sheet.stories.tsx +671 -950
  40. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  41. package/dist/components/slider/Slider.stories.tsx +383 -760
  42. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  43. package/dist/components/switch/Switch.stories.tsx +461 -208
  44. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  45. package/dist/components/table/Table.stories.tsx +770 -916
  46. package/dist/components/tabs/Tabs.stories.tsx +458 -1455
  47. package/dist/components/tag/Tag.stories.tsx +714 -542
  48. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  49. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
  50. package/dist/components/toast/Toast.stories.tsx +452 -1339
  51. package/dist/components/toggle/Toggle.stories.tsx +488 -931
  52. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
  53. package/dist/components/typography/Typography.stories.tsx +406 -89
  54. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  55. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  56. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  57. package/dist/icons/Icons.stories.tsx +0 -12
  58. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
  59. package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
  60. package/dist/icons/all-icons.tsx +37 -16
  61. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
  62. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
  63. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
  64. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
  65. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
  66. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
  67. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
  68. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
  69. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
  70. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
  71. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
  72. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
  73. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
  74. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
  75. package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
  76. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
  77. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
  78. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
  79. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
  80. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
  81. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
  82. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
  83. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
  84. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
  85. package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
  86. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
  87. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
  88. package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
  89. package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
  90. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
  91. package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
  92. package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
  93. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
  94. package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
  95. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
  96. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
  97. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
  98. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
  99. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
  100. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
  101. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
  102. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
  103. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
  104. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
  105. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
  106. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
  107. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
  108. package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
  109. package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
  110. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
  111. package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
  112. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
  113. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
  114. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
  115. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
  116. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
  117. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
  118. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
  119. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
  120. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
  121. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
  122. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
  123. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
  124. package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
  125. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
  126. package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
  127. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
  128. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
  129. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
  130. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
  131. package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
  132. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
  133. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
  134. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
  135. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
  136. package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
  137. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
  138. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
  139. package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
  140. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
  141. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
  142. package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
  143. package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
  144. package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
  145. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
  146. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
  147. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
  148. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
  149. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
  150. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
  151. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
  152. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
  153. package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
  154. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
  155. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
  156. package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
  157. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
  158. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
  159. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
  160. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
  161. package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
  162. package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
  163. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
  164. package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
  165. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
  166. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
  167. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
  168. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
  169. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
  170. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
  171. package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
  172. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
  173. package/dist/index.cjs +90 -90
  174. package/dist/index.js +90 -90
  175. package/package.json +8 -3
@@ -3,6 +3,8 @@ import { Button } from "@components/button"
3
3
  import type { Meta, StoryObj } from "@storybook/react-vite"
4
4
  import { toast } from "sonner"
5
5
 
6
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
7
+
6
8
  import { Toaster } from "."
7
9
 
8
10
  const meta: Meta<typeof Toaster> = {
@@ -10,201 +12,35 @@ const meta: Meta<typeof Toaster> = {
10
12
  component: Toaster,
11
13
  parameters: {
12
14
  layout: "fullscreen",
13
- backgrounds: {
14
- default: "dark",
15
- values: [
16
- { name: "dark", value: "#0a0a0a" },
17
- { name: "light", value: "#ffffff" },
18
- ],
19
- },
20
15
  docs: {
21
16
  description: {
22
- component: `
23
- A toast notification system built on top of Sonner for displaying temporary messages to users.
24
- Provides a clean, accessible way to show success messages, errors, loading states, and other notifications.
25
-
26
- ## Features
27
- - Built on Sonner for excellent performance and accessibility
28
- - Multiple toast types (success, error, warning, info, loading)
29
- - Customizable styling with design tokens
30
- - Action buttons and dismiss functionality
31
- - Promise-based toasts for async operations
32
- - Rich content support (icons, descriptions, custom content)
33
- - Keyboard navigation and screen reader support
34
- - Automatic dismiss with customizable duration
35
- - Positioning options (top, bottom, left, right)
36
- - Swipe to dismiss on mobile
37
- - Queue management for multiple toasts
38
-
39
- ## Installation
40
- \`\`\`bash
41
- npm install sonner
42
- \`\`\`
43
-
44
- ## Usage
45
-
46
- ### Basic Setup
47
- First, add the Toaster component to your app root:
48
-
49
- \`\`\`tsx
50
- import { Toaster } from '@/ui/components/toast'
51
-
52
- export default function App() {
53
- return (
54
- <>
55
- {/* Your app content */}
56
- <Toaster />
57
- </>
58
- )
59
- }
60
- \`\`\`
61
-
62
- ### Triggering Toasts
63
- \`\`\`tsx
64
- import { toast } from 'sonner'
65
-
66
- // Basic toast
67
- toast('Hello World!')
68
-
69
- // Success toast
70
- toast.success('Profile updated successfully!')
71
-
72
- // Error toast
73
- toast.error('Something went wrong')
74
-
75
- // Warning toast
76
- toast.warning('Please save your changes')
77
-
78
- // Info toast
79
- toast.info('New version available')
80
-
81
- // Loading toast
82
- toast.loading('Uploading file...')
83
-
84
- // Toast with description
85
- toast('Event Created', {
86
- description: 'Your event has been created successfully'
87
- })
88
-
89
- // Toast with action
90
- toast('Event Created', {
91
- action: {
92
- label: 'View',
93
- onClick: () => console.log('View clicked')
94
- }
95
- })
96
-
97
- // Promise-based toast
98
- const promise = fetch('/api/data')
99
- toast.promise(promise, {
100
- loading: 'Loading data...',
101
- success: 'Data loaded successfully!',
102
- error: 'Failed to load data'
103
- })
104
- \`\`\`
105
-
106
- ### Custom Styling
107
- The component uses your design system tokens and can be customized through the \`toastOptions\` prop:
108
-
109
- \`\`\`tsx
110
- <Toaster
111
- position="top-right"
112
- toastOptions={{
113
- duration: 4000,
114
- style: {
115
- background: 'var(--color-fm-surface-primary)',
116
- color: 'var(--color-fm-text-primary)',
117
- }
118
- }}
119
- />
120
- \`\`\`
121
- `,
17
+ component:
18
+ "A toast notification system built on Sonner. The `Toaster` component must be mounted once at the app root; individual toasts are triggered imperatively with the `toast` helper from sonner. Supports six toast types (default, success, error, warning, info, loading), promise-based automatic state transitions, action/cancel buttons, configurable duration (including persistent), and six screen positions. Styling is applied via design-system tokens in the component's `toastOptions.classNames` — no custom CSS is needed in stories.",
122
19
  },
20
+ page: () => (
21
+ <AuralComponentDocsPage
22
+ features={[
23
+ {
24
+ title: "6 Toast Types",
25
+ description: "Default to loading",
26
+ },
27
+ {
28
+ title: "Promise Support",
29
+ description: "Auto state transitions",
30
+ },
31
+ {
32
+ title: "6 Positions",
33
+ description: "Configurable screen side",
34
+ },
35
+ ]}
36
+ />
37
+ ),
123
38
  },
124
39
  },
125
40
  tags: ["autodocs"],
126
- argTypes: {
127
- position: {
128
- control: { type: "select" },
129
- options: [
130
- "top-left",
131
- "top-center",
132
- "top-right",
133
- "bottom-left",
134
- "bottom-center",
135
- "bottom-right",
136
- ],
137
- description: "Position where toasts appear on screen",
138
- table: {
139
- type: {
140
- summary:
141
- '"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"',
142
- },
143
- defaultValue: { summary: '"bottom-right"' },
144
- },
145
- },
146
- expand: {
147
- control: { type: "boolean" },
148
- description: "Whether toasts should expand when hovered",
149
- table: {
150
- type: { summary: "boolean" },
151
- defaultValue: { summary: "false" },
152
- },
153
- },
154
- richColors: {
155
- control: { type: "boolean" },
156
- description: "Whether to use rich colors for different toast types",
157
- table: {
158
- type: { summary: "boolean" },
159
- defaultValue: { summary: "false" },
160
- },
161
- },
162
- closeButton: {
163
- control: { type: "boolean" },
164
- description: "Whether to show close button on toasts",
165
- table: {
166
- type: { summary: "boolean" },
167
- defaultValue: { summary: "false" },
168
- },
169
- },
170
- theme: {
171
- control: { type: "select" },
172
- options: ["light", "dark", "system"],
173
- description: "Color theme for toasts",
174
- table: {
175
- type: { summary: '"light" | "dark" | "system"' },
176
- defaultValue: { summary: '"system"' },
177
- },
178
- },
179
- toastOptions: {
180
- control: { type: "object" },
181
- description: "Default options for all toasts",
182
- table: {
183
- type: { summary: "ToastOptions" },
184
- defaultValue: { summary: "{}" },
185
- },
186
- },
187
- offset: {
188
- control: { type: "text" },
189
- description: "Offset from screen edge (CSS value)",
190
- table: {
191
- type: { summary: "string | number" },
192
- defaultValue: { summary: '"32px"' },
193
- },
194
- },
195
- dir: {
196
- control: { type: "select" },
197
- options: ["ltr", "rtl"],
198
- description: "Text direction for RTL support",
199
- table: {
200
- type: { summary: '"ltr" | "rtl"' },
201
- defaultValue: { summary: '"ltr"' },
202
- },
203
- },
204
- },
205
41
  decorators: [
206
42
  (Story) => (
207
- <div style={{ height: "80vh", padding: "2rem" }}>
43
+ <div className="min-h-screen p-8">
208
44
  <Story />
209
45
  <Toaster />
210
46
  </div>
@@ -215,174 +51,173 @@ The component uses your design system tokens and can be customized through the \
215
51
  export default meta
216
52
  type Story = StoryObj<typeof meta>
217
53
 
218
- export const Default: Story = {
219
- args: {
220
- position: "bottom-right",
221
- expand: false,
222
- richColors: false,
223
- closeButton: false,
224
- },
225
- render: (args) => (
226
- <div className="text-fm-primary space-y-4">
227
- <h2 className="text-xl font-semibold">Toast Examples</h2>
228
- <p>
229
- Click the buttons below to see different types of toast notifications.
230
- </p>
231
-
232
- <div className="flex flex-wrap gap-2">
233
- <Button onClick={() => toast("Hello World!")}>Basic Toast</Button>
234
-
235
- <Button
236
- onClick={() => toast.success("Operation completed successfully!")}
237
- >
238
- Success Toast
239
- </Button>
240
-
241
- <Button onClick={() => toast.error("Something went wrong")}>
242
- Error Toast
243
- </Button>
244
- </div>
245
-
246
- <Toaster {...args} />
247
- </div>
248
- ),
249
- }
54
+ // ─── Configurations ───────────────────────────────────────────────────────────
250
55
 
251
- export const ToastTypes: Story = {
56
+ export const Configurations: Story = {
252
57
  render: () => (
253
- <div className="text-fm-primary space-y-4">
254
- <h2 className="text-xl font-semibold">Toast Types</h2>
255
- <p>Different types of toast notifications for various use cases.</p>
256
-
257
- <div className="grid max-w-lg grid-cols-2 gap-3">
258
- <Button onClick={() => toast("Default message")} variant="outline">
259
- Default
260
- </Button>
261
-
262
- <Button onClick={() => toast.success("Success message")}>
263
- Success
264
- </Button>
265
-
266
- <Button onClick={() => toast.error("Error message")}>Error</Button>
267
-
268
- <Button onClick={() => toast.warning("Warning message")}>
269
- Warning
270
- </Button>
271
-
272
- <Button onClick={() => toast.info("Info message")}>Info</Button>
273
-
274
- <Button onClick={() => toast.loading("Loading...")}>Loading</Button>
58
+ <div className="space-y-8">
59
+ {/* Toast types */}
60
+ <div className="space-y-3">
61
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
62
+ Toast Types
63
+ </h4>
64
+ <div className="flex flex-wrap gap-3">
65
+ <div className="space-y-2 text-center">
66
+ <Button
67
+ variant="secondary"
68
+ onClick={() => toast("Default notification message")}
69
+ >
70
+ Default
71
+ </Button>
72
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
73
+ default
74
+ </p>
75
+ </div>
76
+ <div className="space-y-2 text-center">
77
+ <Button
78
+ variant="secondary"
79
+ onClick={() => toast.success("Track added to library")}
80
+ >
81
+ Success
82
+ </Button>
83
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
84
+ success
85
+ </p>
86
+ </div>
87
+ <div className="space-y-2 text-center">
88
+ <Button
89
+ variant="secondary"
90
+ onClick={() => toast.error("Upload failed — try again")}
91
+ >
92
+ Error
93
+ </Button>
94
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
95
+ error
96
+ </p>
97
+ </div>
98
+ <div className="space-y-2 text-center">
99
+ <Button
100
+ variant="secondary"
101
+ onClick={() => toast.warning("Offline — changes saved locally")}
102
+ >
103
+ Warning
104
+ </Button>
105
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
106
+ warning
107
+ </p>
108
+ </div>
109
+ <div className="space-y-2 text-center">
110
+ <Button
111
+ variant="secondary"
112
+ onClick={() => toast.info("New version available")}
113
+ >
114
+ Info
115
+ </Button>
116
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
117
+ info
118
+ </p>
119
+ </div>
120
+ <div className="space-y-2 text-center">
121
+ <Button
122
+ variant="secondary"
123
+ onClick={() => toast.loading("Syncing library…")}
124
+ >
125
+ Loading
126
+ </Button>
127
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
128
+ loading
129
+ </p>
130
+ </div>
131
+ </div>
275
132
  </div>
276
- </div>
277
- ),
278
- parameters: {
279
- docs: {
280
- description: {
281
- story:
282
- "Different toast types available: default, success, error, warning, info, and loading.",
283
- },
284
- },
285
- },
286
- }
287
-
288
- export const WithDescription: Story = {
289
- render: () => (
290
- <div className="text-fm-primary space-y-4">
291
- <h2 className="text-xl font-semibold">Toasts with Descriptions</h2>
292
- <p>Add additional context with description text.</p>
293
-
294
- <div className="flex flex-wrap gap-2">
295
- <Button
296
- onClick={() =>
297
- toast("Profile Updated", {
298
- description:
299
- "Your profile information has been saved successfully.",
300
- })
301
- }
302
- >
303
- With Description
304
- </Button>
305
-
306
- <Button
307
- onClick={() =>
308
- toast.success("File Uploaded", {
309
- description: "document.pdf has been uploaded to the cloud.",
310
- })
311
- }
312
- >
313
- Success with Description
314
- </Button>
315
133
 
316
- <Button
317
- onClick={() =>
318
- toast.error("Upload Failed", {
319
- description: "The file could not be uploaded. Please try again.",
320
- })
321
- }
322
- >
323
- Error with Description
324
- </Button>
134
+ {/* Position options */}
135
+ <div className="space-y-3">
136
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
137
+ Position Options
138
+ </h4>
139
+ <div className="flex flex-wrap gap-3">
140
+ {(
141
+ [
142
+ "top-left",
143
+ "top-center",
144
+ "top-right",
145
+ "bottom-left",
146
+ "bottom-center",
147
+ "bottom-right",
148
+ ] as const
149
+ ).map((pos) => (
150
+ <Button
151
+ key={pos}
152
+ variant="outline"
153
+ size="sm"
154
+ onClick={() => {
155
+ toast.dismiss()
156
+ toast(`Position: ${pos}`, { position: pos, duration: 3000 })
157
+ }}
158
+ >
159
+ {pos}
160
+ </Button>
161
+ ))}
162
+ </div>
325
163
  </div>
326
- </div>
327
- ),
328
- parameters: {
329
- docs: {
330
- description: {
331
- story:
332
- "Toasts can include additional description text for more context.",
333
- },
334
- },
335
- },
336
- }
337
-
338
- export const WithActions: Story = {
339
- render: () => (
340
- <div className="text-fm-primary space-y-4">
341
- <h2 className="text-xl font-semibold">Toasts with Actions</h2>
342
- <p>Include action buttons for user interaction.</p>
343
-
344
- <div className="flex flex-wrap gap-2">
345
- <Button
346
- onClick={() =>
347
- toast("Event Created", {
348
- description: "Your event has been scheduled for tomorrow.",
349
- action: {
350
- label: "View",
351
- onClick: () => toast("Viewing event..."),
352
- },
353
- })
354
- }
355
- >
356
- With Action
357
- </Button>
358
164
 
359
- <Button
360
- onClick={() =>
361
- toast("Changes Saved", {
362
- description: "Your document has been saved to the cloud.",
363
- action: {
364
- label: "Undo",
365
- onClick: () => toast.info("Changes undone"),
366
- },
367
- })
368
- }
369
- >
370
- With Undo Action
371
- </Button>
372
-
373
- <Button
374
- onClick={() =>
375
- toast("New Message", {
376
- description: "You have received a new message from John.",
377
- action: {
378
- label: "Reply",
379
- onClick: () => toast("Opening reply..."),
380
- },
381
- })
382
- }
383
- >
384
- With Reply Action
385
- </Button>
165
+ {/* Duration */}
166
+ <div className="space-y-3">
167
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
168
+ Duration (Timed vs Persistent)
169
+ </h4>
170
+ <div className="flex flex-wrap gap-3">
171
+ <div className="space-y-2 text-center">
172
+ <Button
173
+ variant="secondary"
174
+ onClick={() =>
175
+ toast("Quick notification", {
176
+ duration: 1500,
177
+ })
178
+ }
179
+ >
180
+ 1.5 s
181
+ </Button>
182
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
183
+ 1 500 ms
184
+ </p>
185
+ </div>
186
+ <div className="space-y-2 text-center">
187
+ <Button
188
+ variant="secondary"
189
+ onClick={() =>
190
+ toast("Standard notification", {
191
+ duration: 4000,
192
+ })
193
+ }
194
+ >
195
+ 4 s (default)
196
+ </Button>
197
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
198
+ 4 000 ms
199
+ </p>
200
+ </div>
201
+ <div className="space-y-2 text-center">
202
+ <Button
203
+ variant="secondary"
204
+ onClick={() =>
205
+ toast("Persistent — dismiss manually", {
206
+ duration: Infinity,
207
+ action: {
208
+ label: "Dismiss",
209
+ onClick: () => toast.dismiss(),
210
+ },
211
+ })
212
+ }
213
+ >
214
+ Persistent
215
+ </Button>
216
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
217
+ Infinity
218
+ </p>
219
+ </div>
220
+ </div>
386
221
  </div>
387
222
  </div>
388
223
  ),
@@ -390,71 +225,189 @@ export const WithActions: Story = {
390
225
  docs: {
391
226
  description: {
392
227
  story:
393
- "Toasts can include action buttons for immediate user interaction.",
228
+ "All configuration axes in one view: the six toast types (default, success, error, warning, info, loading), the six supported screen positions triggered live, and three duration presets including an Infinity persistent toast that must be manually dismissed.",
394
229
  },
395
230
  },
396
231
  },
397
232
  }
398
233
 
399
- export const PromiseToasts: Story = {
234
+ // ─── Interactive ──────────────────────────────────────────────────────────────
235
+
236
+ export const Interactive: Story = {
400
237
  render: () => {
401
- const simulateApiCall = (shouldFail = false, delay = 2000) => {
402
- return new Promise((resolve, reject) => {
238
+ const simulateUpload = (shouldFail = false, ms = 2000) =>
239
+ new Promise<string>((resolve, reject) => {
403
240
  setTimeout(() => {
404
- if (shouldFail) {
405
- reject(new Error("API call failed"))
406
- } else {
407
- resolve("Data loaded successfully")
408
- }
409
- }, delay)
241
+ if (shouldFail) reject(new Error("Network error"))
242
+ else resolve("track.mp3")
243
+ }, ms)
410
244
  })
411
- }
412
245
 
413
246
  return (
414
- <div className="text-fm-primary space-y-4">
415
- <h2 className="text-xl font-semibold">Promise-based Toasts</h2>
416
- <p>Automatically update toasts based on promise resolution.</p>
247
+ <div className="w-full p-8">
248
+ <div className="mx-auto max-w-3xl space-y-6">
249
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
250
+ {/* Controls panel */}
251
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
252
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
253
+ Toast Triggers
254
+ </p>
255
+
256
+ <div className="space-y-2">
257
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
258
+ Basic Types
259
+ </p>
260
+ <div className="space-y-2">
261
+ <Button
262
+ variant="secondary"
263
+ size="sm"
264
+ className="w-full"
265
+ onClick={() => toast("Track added to queue")}
266
+ >
267
+ Default
268
+ </Button>
269
+ <Button
270
+ variant="secondary"
271
+ size="sm"
272
+ className="w-full"
273
+ onClick={() => toast.success("Liked — saved to favourites")}
274
+ >
275
+ Success
276
+ </Button>
277
+ <Button
278
+ variant="secondary"
279
+ size="sm"
280
+ className="w-full"
281
+ onClick={() =>
282
+ toast.error("Playback error — track unavailable")
283
+ }
284
+ >
285
+ Error
286
+ </Button>
287
+ <Button
288
+ variant="secondary"
289
+ size="sm"
290
+ className="w-full"
291
+ onClick={() =>
292
+ toast.warning("Storage almost full (95% used)")
293
+ }
294
+ >
295
+ Warning
296
+ </Button>
297
+ <Button
298
+ variant="secondary"
299
+ size="sm"
300
+ className="w-full"
301
+ onClick={() => toast.info("3 new releases from Luna Vex")}
302
+ >
303
+ Info
304
+ </Button>
305
+ </div>
306
+ </div>
417
307
 
418
- <div className="flex flex-wrap gap-2">
419
- <Button
420
- onClick={() => {
421
- const promise = simulateApiCall(false, 2000)
422
- toast.promise(promise, {
423
- loading: "Loading data...",
424
- success: "Data loaded successfully!",
425
- error: "Failed to load data",
426
- })
427
- }}
428
- >
429
- Successful Promise
430
- </Button>
308
+ <div className="border-fm-divider-secondary border-t pt-4" />
309
+
310
+ <div className="space-y-2">
311
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
312
+ With Actions
313
+ </p>
314
+ <div className="space-y-2">
315
+ <Button
316
+ variant="secondary"
317
+ size="sm"
318
+ className="w-full"
319
+ onClick={() =>
320
+ toast("Track removed from queue", {
321
+ action: {
322
+ label: "Undo",
323
+ onClick: () => toast.success("Track restored"),
324
+ },
325
+ })
326
+ }
327
+ >
328
+ With Undo
329
+ </Button>
330
+ <Button
331
+ variant="secondary"
332
+ size="sm"
333
+ className="w-full"
334
+ onClick={() =>
335
+ toast.success("Playlist shared", {
336
+ description: "Link copied to clipboard",
337
+ action: {
338
+ label: "View",
339
+ onClick: () => toast("Opening shared link…"),
340
+ },
341
+ })
342
+ }
343
+ >
344
+ Success + Action
345
+ </Button>
346
+ </div>
347
+ </div>
348
+ </div>
431
349
 
432
- <Button
433
- variant="outline"
434
- onClick={() => {
435
- const promise = simulateApiCall(true, 1500)
436
- toast.promise(promise, {
437
- loading: "Uploading file...",
438
- success: "File uploaded successfully!",
439
- error: "Upload failed",
440
- })
441
- }}
442
- >
443
- Failed Promise
444
- </Button>
350
+ {/* Preview stage */}
351
+ <div className="flex flex-col gap-4 lg:col-span-2">
352
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-4 rounded-xl border p-6">
353
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
354
+ Promise Toasts
355
+ </p>
356
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
357
+ Promise toasts automatically transition from loading → success
358
+ or loading → error based on the promise result.
359
+ </p>
360
+ <div className="flex flex-wrap gap-3">
361
+ <Button
362
+ variant="secondary"
363
+ onClick={() => {
364
+ const p = simulateUpload(false, 2500)
365
+ toast.promise(p, {
366
+ loading: "Uploading track…",
367
+ success: (file) => `${file} uploaded successfully`,
368
+ error: "Upload failed",
369
+ })
370
+ }}
371
+ >
372
+ Successful upload
373
+ </Button>
374
+ <Button
375
+ variant="outline"
376
+ onClick={() => {
377
+ const p = simulateUpload(true, 1800)
378
+ toast.promise(p, {
379
+ loading: "Uploading track…",
380
+ success: "Uploaded",
381
+ error: (err: Error) => `Upload failed: ${err.message}`,
382
+ })
383
+ }}
384
+ >
385
+ Failed upload
386
+ </Button>
387
+ <Button
388
+ variant="secondary"
389
+ onClick={() => {
390
+ const p = simulateUpload(false, 3000)
391
+ toast.promise(p, {
392
+ loading: "Syncing library…",
393
+ success: "Library synced — 142 tracks updated",
394
+ error: "Sync failed — check your connection",
395
+ })
396
+ }}
397
+ >
398
+ Library sync
399
+ </Button>
400
+ </div>
401
+ </div>
445
402
 
446
- <Button
447
- onClick={() => {
448
- const promise = simulateApiCall(false, 3000)
449
- toast.promise(promise, {
450
- loading: "Processing payment...",
451
- success: (data) => `Payment processed: ${data}`,
452
- error: (error) => `Payment failed: ${error.message}`,
453
- })
454
- }}
455
- >
456
- Custom Messages
457
- </Button>
403
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
404
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
405
+ Toasts stack and auto-dismiss — trigger multiple to see the
406
+ queue.
407
+ </code>
408
+ </div>
409
+ </div>
410
+ </div>
458
411
  </div>
459
412
  </div>
460
413
  )
@@ -463,977 +416,137 @@ export const PromiseToasts: Story = {
463
416
  docs: {
464
417
  description: {
465
418
  story:
466
- "Use promise-based toasts for async operations. The toast automatically updates based on promise state.",
467
- },
468
- },
469
- },
470
- }
471
-
472
- export const CustomDuration: Story = {
473
- render: () => (
474
- <div className="text-fm-primary space-y-4">
475
- <h2 className="text-xl font-semibold">Custom Duration</h2>
476
- <p>Control how long toasts stay visible.</p>
477
-
478
- <div className="flex flex-wrap gap-2">
479
- <Button onClick={() => toast("Quick message", { duration: 1000 })}>
480
- 1 Second
481
- </Button>
482
-
483
- <Button onClick={() => toast("Normal message", { duration: 4000 })}>
484
- 4 Seconds (Default)
485
- </Button>
486
-
487
- <Button onClick={() => toast("Long message", { duration: 10000 })}>
488
- 10 Seconds
489
- </Button>
490
-
491
- <Button
492
- onClick={() =>
493
- toast("Persistent message", {
494
- description: "Your event has been scheduled for tomorrow.",
495
- action: {
496
- label: "View",
497
- onClick: () => toast("Viewing event..."),
498
- },
499
- cancel: {
500
- label: "Cancel",
501
- onClick: () => console.log("Cancel!"),
502
- },
503
- duration: Infinity,
504
- })
505
- }
506
- >
507
- Persistent (Manual dismiss)
508
- </Button>
509
- </div>
510
- </div>
511
- ),
512
- parameters: {
513
- docs: {
514
- description: {
515
- story:
516
- "Customize how long toasts remain visible. Use Infinity for persistent toasts.",
517
- },
518
- },
519
- },
520
- }
521
-
522
- export const Positioning: Story = {
523
- render: () => (
524
- <div className="text-fm-primary space-y-4">
525
- <h2 className="text-xl font-semibold">Toast Positioning</h2>
526
- <p>Try different positions for the toast container.</p>
527
-
528
- <div className="grid max-w-md grid-cols-3 gap-2">
529
- <Button
530
- size="sm"
531
- onClick={() => {
532
- toast.dismiss()
533
- toast("Top Left", { duration: 3000 })
534
- }}
535
- >
536
- Top Left
537
- </Button>
538
-
539
- <Button
540
- size="sm"
541
- onClick={() => {
542
- toast.dismiss()
543
- toast("Top Center", { duration: 3000 })
544
- }}
545
- >
546
- Top Center
547
- </Button>
548
-
549
- <Button
550
- size="sm"
551
- onClick={() => {
552
- toast.dismiss()
553
- toast("Top Right", { duration: 3000 })
554
- }}
555
- >
556
- Top Right
557
- </Button>
558
-
559
- <Button
560
- size="sm"
561
- onClick={() => {
562
- toast.dismiss()
563
- toast("Bottom Left", { duration: 3000 })
564
- }}
565
- >
566
- Bottom Left
567
- </Button>
568
-
569
- <Button
570
- size="sm"
571
- onClick={() => {
572
- toast.dismiss()
573
- toast("Bottom Center", { duration: 3000 })
574
- }}
575
- >
576
- Bottom Center
577
- </Button>
578
-
579
- <Button
580
- size="sm"
581
- onClick={() => {
582
- toast.dismiss()
583
- toast("Bottom Right", { duration: 3000 })
584
- }}
585
- >
586
- Bottom Right
587
- </Button>
588
- </div>
589
-
590
- <p className="text-sm">
591
- Note: Change the position prop on the Toaster component to see different
592
- positions.
593
- </p>
594
- </div>
595
- ),
596
- parameters: {
597
- docs: {
598
- description: {
599
- story:
600
- "Toasts can be positioned in different areas of the screen. Configure via the position prop on Toaster.",
601
- },
602
- },
603
- },
604
- }
605
-
606
- export const RichColors: Story = {
607
- args: {
608
- richColors: true,
609
- },
610
- render: (args) => (
611
- <div className="text-fm-primary space-y-4">
612
- <h2 className="text-xl font-semibold">Rich Colors</h2>
613
- <p>Enable rich colors for more vibrant toast notifications.</p>
614
-
615
- <div className="flex flex-wrap gap-2">
616
- <Button onClick={() => toast.success("Success with rich colors!")}>
617
- Success
618
- </Button>
619
-
620
- <Button onClick={() => toast.error("Error with rich colors!")}>
621
- Error
622
- </Button>
623
-
624
- <Button onClick={() => toast.warning("Warning with rich colors!")}>
625
- Warning
626
- </Button>
627
-
628
- <Button onClick={() => toast.info("Info with rich colors!")}>
629
- Info
630
- </Button>
631
- </div>
632
-
633
- <Toaster {...args} />
634
- </div>
635
- ),
636
- parameters: {
637
- docs: {
638
- description: {
639
- story:
640
- "Enable richColors prop for more vibrant, colorful toast notifications.",
419
+ "Live interactive story with two panels. The left panel provides one-click triggers for every toast type (default, success, error, warning, info) plus two variants with action buttons. The right panel demonstrates promise-based toasts that automatically transition through loading → success/error states — simulating real async operations like track uploads and library syncs.",
641
420
  },
642
421
  },
643
422
  },
644
423
  }
645
424
 
646
- export const WithCloseButton: Story = {
647
- args: {
648
- closeButton: true,
649
- },
650
- render: (args) => (
651
- <div className="text-fm-primary space-y-4">
652
- <h2 className="text-xl font-semibold">With Close Button</h2>
653
- <p>Show close buttons on all toasts for manual dismissal.</p>
654
-
655
- <div className="flex flex-wrap gap-2">
656
- <Button onClick={() => toast("Message with close button")}>
657
- Show Toast
658
- </Button>
425
+ // ─── Accessibility ─────────────────────────────────────────────────────────────
659
426
 
660
- <Button onClick={() => toast.success("Success with close button")}>
661
- Show Success
662
- </Button>
663
-
664
- <Button
665
- onClick={() => toast("Persistent with close", { duration: Infinity })}
666
- >
667
- Persistent Toast
668
- </Button>
669
- </div>
670
-
671
- <Toaster {...args} />
672
- </div>
673
- ),
674
- parameters: {
675
- docs: {
676
- description: {
677
- story: "Enable closeButton prop to show close buttons on all toasts.",
678
- },
679
- },
680
- },
681
- }
682
-
683
- export const CustomStyling: Story = {
427
+ export const Accessibility: Story = {
684
428
  render: () => (
685
- <div className="text-fm-primary space-y-4">
686
- <h2 className="text-xl font-semibold">Custom Styling</h2>
687
- <p>Customize toast appearance with inline styles and class names.</p>
688
-
689
- <div className="flex flex-wrap gap-2">
690
- <Button
691
- onClick={() =>
692
- toast("Custom styled toast", {
693
- style: {
694
- background: "#1f2937",
695
- color: "#f9fafb",
696
- border: "1px solid #374151",
697
- },
698
- })
699
- }
700
- >
701
- Dark Style
702
- </Button>
703
-
704
- <Button
705
- onClick={() =>
706
- toast.success("Gradient success", {
707
- style: {
708
- background: "linear-gradient(to right, #10b981, #059669)",
709
- color: "white",
710
- },
711
- })
712
- }
713
- >
714
- Gradient Style
715
- </Button>
716
-
717
- <Button
718
- onClick={() =>
719
- toast("Large toast", {
720
- className: "text-lg p-6",
721
- style: {
722
- minHeight: "80px",
723
- },
724
- })
725
- }
726
- >
727
- Large Size
728
- </Button>
729
- </div>
730
- </div>
731
- ),
732
- parameters: {
733
- docs: {
734
- description: {
735
- story:
736
- "Customize individual toasts with inline styles and CSS classes.",
737
- },
738
- },
739
- },
740
- }
741
-
742
- export const ToastManagement: Story = {
743
- render: () => {
744
- const [toastCount, setToastCount] = React.useState(0)
745
-
746
- return (
747
- <div className="text-fm-primary space-y-4">
748
- <h2 className="text-xl font-semibold">Toast Management</h2>
749
- <p>Programmatically control toast behavior.</p>
750
-
751
- <div className="flex flex-wrap gap-2">
429
+ <div className="space-y-8">
430
+ {/* Live region demo */}
431
+ <div className="space-y-3">
432
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
433
+ ARIA Live Region
434
+ </h4>
435
+ <div className="flex flex-wrap gap-3">
752
436
  <Button
753
- onClick={() => {
754
- setToastCount((count) => count + 1)
755
- toast(`Toast #${toastCount + 1}`)
756
- }}
437
+ variant="secondary"
438
+ onClick={() =>
439
+ toast("Track saved", {
440
+ description: "Midnight Echoes has been added to your library.",
441
+ })
442
+ }
757
443
  >
758
- Add Toast
759
- </Button>
760
-
761
- <Button variant="outline" onClick={() => toast.dismiss()}>
762
- Dismiss All
444
+ Announce success
763
445
  </Button>
764
-
765
446
  <Button
766
- variant="outline"
767
- onClick={() => {
768
- // Store toast ID for later dismissal
769
- const toastId = toast("Dismissible toast", {
770
- duration: Infinity,
771
- action: {
772
- label: "Dismiss",
773
- onClick: () => toast.dismiss(toastId),
774
- },
447
+ variant="secondary"
448
+ onClick={() =>
449
+ toast.error("Playback blocked", {
450
+ description: "This track is not available in your region.",
775
451
  })
776
- }}
452
+ }
777
453
  >
778
- Dismissible Toast
454
+ Announce error
779
455
  </Button>
456
+ </div>
457
+ </div>
780
458
 
459
+ {/* Keyboard dismissal */}
460
+ <div className="space-y-3">
461
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
462
+ Keyboard Dismissal
463
+ </h4>
464
+ <div className="flex flex-wrap gap-3">
781
465
  <Button
782
- onClick={() => {
783
- // Queue multiple toasts
784
- toast("First toast")
785
- setTimeout(() => toast("Second toast"), 500)
786
- setTimeout(() => toast("Third toast"), 1000)
787
- }}
466
+ variant="secondary"
467
+ onClick={() =>
468
+ toast(
469
+ "Press Escape or Tab to a toast then Enter/Space to dismiss",
470
+ {
471
+ duration: 8000,
472
+ action: {
473
+ label: "Dismiss",
474
+ onClick: () => toast.dismiss(),
475
+ },
476
+ }
477
+ )
478
+ }
788
479
  >
789
- Queue Multiple
480
+ Focusable toast
481
+ </Button>
482
+ <Button variant="outline" onClick={() => toast.dismiss()}>
483
+ Dismiss all (toast.dismiss)
790
484
  </Button>
791
- </div>
792
-
793
- <div className="text-fm-primary text-sm">
794
- <p>Toast count: {toastCount}</p>
795
485
  </div>
796
486
  </div>
797
- )
798
- },
799
- parameters: {
800
- docs: {
801
- description: {
802
- story:
803
- "Manage toasts programmatically with dismiss, queuing, and ID-based control.",
804
- },
805
- },
806
- },
807
- }
808
-
809
- export const AccessibilityFeatures: Story = {
810
- render: () => (
811
- <div className="text-fm-primary space-y-4">
812
- <h2 className="text-xl font-semibold">Accessibility Features</h2>
813
- <p>Toast notifications are built with accessibility in mind.</p>
814
487
 
488
+ {/* Role demo */}
815
489
  <div className="space-y-3">
816
- <div className="bg-fm-surface-info-sec rounded-lg p-4">
817
- <h3 className="text-fm-info font-medium">Keyboard Navigation</h3>
818
- <p className="text-fm-info-sec text-sm">
819
- Use Tab to focus toasts, Enter/Space to activate actions, Escape to
820
- dismiss.
821
- </p>
822
- </div>
823
-
824
- <div className="bg-fm-surface-positive-sec rounded-lg p-4">
825
- <h3 className="text-fm-positive font-medium">
826
- Screen Reader Support
827
- </h3>
828
- <p className="text-fm-positive text-sm">
829
- Toasts are announced to screen readers with proper ARIA labels.
830
- </p>
831
- </div>
832
-
833
- <div className="bg-fm-surface-secondary rounded-lg p-4">
834
- <h3 className="text-fm-primary font-medium">Reduced Motion</h3>
835
- <p className="text-fm-secondary text-sm">
836
- Respects user's motion preferences for animations.
837
- </p>
838
- </div>
839
- </div>
840
-
841
- <div className="flex gap-2">
842
- <Button
843
- onClick={() =>
844
- toast.success("Accessible success message", {
845
- description: "This toast is fully accessible to screen readers",
846
- })
847
- }
848
- >
849
- Try Accessible Toast
850
- </Button>
851
- </div>
852
- </div>
853
- ),
854
- parameters: {
855
- docs: {
856
- description: {
857
- story:
858
- "Toast component includes comprehensive accessibility features including keyboard navigation, screen reader support, and reduced motion respect.",
859
- },
860
- },
861
- },
862
- }
863
-
864
- export const RealWorldExamples: Story = {
865
- render: () => {
866
- const handleSave = () => {
867
- toast.loading("Saving changes...")
868
-
869
- // Simulate API call
870
- setTimeout(() => {
871
- toast.dismiss()
872
- toast.success("Changes saved successfully!", {
873
- description: "Your document has been updated.",
874
- action: {
875
- label: "View",
876
- onClick: () => toast("Opening document..."),
877
- },
878
- })
879
- }, 2000)
880
- }
881
-
882
- const handleDelete = () => {
883
- toast("Are you sure?", {
884
- description: "This action cannot be undone.",
885
- action: {
886
- label: "Delete",
887
- onClick: () => {
888
- toast.dismiss()
889
- toast.error("Item deleted", {
890
- description: "The item has been permanently removed.",
891
- })
892
- },
893
- },
894
- })
895
- }
896
-
897
- const handleUpload = () => {
898
- const files = ["document.pdf", "image.jpg", "data.csv"]
899
- let completed = 0
900
-
901
- files.forEach((file, index) => {
902
- setTimeout(
903
- () => {
904
- completed++
905
- if (completed === files.length) {
906
- toast.success("All files uploaded!", {
907
- description: `Successfully uploaded ${files.length} files.`,
490
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
491
+ role=&quot;status&quot; vs role=&quot;alert&quot;
492
+ </h4>
493
+ <div className="flex flex-wrap gap-3">
494
+ <Button
495
+ variant="secondary"
496
+ onClick={() =>
497
+ toast.info("Shuffle mode enabled", {
498
+ description:
499
+ 'Announced politely via aria-live="polite" (role=status).',
908
500
  })
909
- } else {
910
- toast.info(`Uploaded ${file}`, {
911
- description: `${completed}/${files.length} files complete.`,
501
+ }
502
+ >
503
+ Polite (info)
504
+ </Button>
505
+ <Button
506
+ variant="secondary"
507
+ onClick={() =>
508
+ toast.error("Session expired — please log in again", {
509
+ description:
510
+ 'Announced immediately via aria-live="assertive" (role=alert).',
511
+ duration: 6000,
912
512
  })
913
513
  }
914
- },
915
- (index + 1) * 1000
916
- )
917
- })
918
-
919
- toast.loading("Uploading files...", {
920
- description: "Please wait while we upload your files.",
921
- })
922
- }
923
-
924
- return (
925
- <div className="text-fm-primary space-y-4">
926
- <h2 className="text-xl font-semibold">Real World Examples</h2>
927
- <p>Common patterns and use cases for toast notifications.</p>
928
-
929
- <div className="space-y-3">
930
- <div className="rounded-lg border p-4">
931
- <h3 className="mb-2 font-medium">Document Editor</h3>
932
- <div className="flex gap-2">
933
- <Button onClick={handleSave}>Save Document</Button>
934
- <Button onClick={handleDelete}>Delete Document</Button>
935
- </div>
936
- </div>
937
-
938
- <div className="rounded-lg border p-4">
939
- <h3 className="mb-2 font-medium">File Upload</h3>
940
- <Button onClick={handleUpload}>Upload Multiple Files</Button>
941
- </div>
942
-
943
- <div className="rounded-lg border p-4">
944
- <h3 className="mb-2 font-medium">Network Status</h3>
945
- <div className="flex gap-2">
946
- <Button
947
- onClick={() =>
948
- toast.info("Connection restored", {
949
- description: "You're back online!",
950
- })
951
- }
952
- >
953
- Simulate Online
954
- </Button>
955
- <Button
956
- variant="outline"
957
- onClick={() =>
958
- toast.warning("Connection lost", {
959
- description: "Check your internet connection.",
960
- duration: Infinity,
961
- })
962
- }
963
- >
964
- Simulate Offline
965
- </Button>
966
- </div>
967
- </div>
968
- </div>
969
- </div>
970
- )
971
- },
972
- parameters: {
973
- docs: {
974
- description: {
975
- story:
976
- "Real-world examples showing how to use toasts for common application scenarios like saving, uploading, and network status.",
977
- },
978
- },
979
- },
980
- }
981
-
982
- export const HeadlessCustomDesign: Story = {
983
- render: () => {
984
- const customToast = (
985
- message: string,
986
- type: "success" | "error" | "warning" | "info" = "info"
987
- ) => {
988
- toast.custom((t) => (
989
- <div
990
- className={`flex items-center gap-3 rounded-xl border p-4 shadow-lg backdrop-blur-sm ${type === "success" ? "border-fm-divider-positive bg-fm-surface-positive-sec text-fm-positive" : ""} ${type === "error" ? "border-fm-divider-secondary bg-fm-surface-negative-sec text-fm-negative" : ""} ${type === "warning" ? "border-fm-divider-warning bg-fm-surface-warning-sec text-fm-warning" : ""} ${type === "info" ? "border-fm-divider-secondary bg-fm-surface-info-sec text-fm-info" : ""} transform transition-all duration-300 ease-in-out ${t ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"} `}
991
- >
992
- {/* Custom Icons */}
993
- <div className="flex-shrink-0">
994
- {type === "success" && (
995
- <div className="bg-fm-surface-positive-sec flex h-8 w-8 items-center justify-center rounded-full">
996
- <svg
997
- className="text-fm-icon-positive h-5 w-5"
998
- fill="none"
999
- stroke="currentColor"
1000
- viewBox="0 0 24 24"
1001
- >
1002
- <path
1003
- strokeLinecap="round"
1004
- strokeLinejoin="round"
1005
- strokeWidth={2}
1006
- d="M5 13l4 4L19 7"
1007
- />
1008
- </svg>
1009
- </div>
1010
- )}
1011
- {type === "error" && (
1012
- <div className="bg-fm-surface-negative-sec flex h-8 w-8 items-center justify-center rounded-full">
1013
- <svg
1014
- className="text-fm-icon-negative h-5 w-5"
1015
- fill="none"
1016
- stroke="currentColor"
1017
- viewBox="0 0 24 24"
1018
- >
1019
- <path
1020
- strokeLinecap="round"
1021
- strokeLinejoin="round"
1022
- strokeWidth={2}
1023
- d="M6 18L18 6M6 6l12 12"
1024
- />
1025
- </svg>
1026
- </div>
1027
- )}
1028
- {type === "warning" && (
1029
- <div className="bg-fm-surface-warning-sec flex h-8 w-8 items-center justify-center rounded-full">
1030
- <svg
1031
- className="text-fm-icon-warning h-5 w-5"
1032
- fill="none"
1033
- stroke="currentColor"
1034
- viewBox="0 0 24 24"
1035
- >
1036
- <path
1037
- strokeLinecap="round"
1038
- strokeLinejoin="round"
1039
- strokeWidth={2}
1040
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
1041
- />
1042
- </svg>
1043
- </div>
1044
- )}
1045
- {type === "info" && (
1046
- <div className="bg-fm-surface-info-sec flex h-8 w-8 items-center justify-center rounded-full">
1047
- <svg
1048
- className="text-fm-info h-5 w-5"
1049
- fill="none"
1050
- stroke="currentColor"
1051
- viewBox="0 0 24 24"
1052
- >
1053
- <path
1054
- strokeLinecap="round"
1055
- strokeLinejoin="round"
1056
- strokeWidth={2}
1057
- d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
1058
- />
1059
- </svg>
1060
- </div>
1061
- )}
1062
- </div>
1063
-
1064
- {/* Message */}
1065
- <div className="flex-1 font-medium">{message}</div>
1066
-
1067
- {/* Close Button */}
1068
- <button
1069
- onClick={() => toast.dismiss(t)}
1070
- className="flex-shrink-0 rounded-full p-1 transition-colors hover:bg-black/10"
1071
514
  >
1072
- <svg
1073
- className="h-4 w-4"
1074
- fill="none"
1075
- stroke="currentColor"
1076
- viewBox="0 0 24 24"
1077
- >
1078
- <path
1079
- strokeLinecap="round"
1080
- strokeLinejoin="round"
1081
- strokeWidth={2}
1082
- d="M6 18L18 6M6 6l12 12"
1083
- />
1084
- </svg>
1085
- </button>
1086
- </div>
1087
- ))
1088
- }
1089
-
1090
- const cardToast = (title: string, description: string, avatar?: string) => {
1091
- toast.custom((t) => (
1092
- <div
1093
- className={`border-fm-divider-secondary bg-fm-surface-primary max-w-sm transform rounded-2xl border p-4 shadow-xl transition-all duration-300 ease-in-out ${t ? "translate-y-0 scale-100 opacity-100" : "translate-y-2 scale-95 opacity-0"} `}
1094
- >
1095
- <div className="flex items-start gap-3">
1096
- {avatar && (
1097
- <img
1098
- src={avatar}
1099
- alt=""
1100
- className="h-10 w-10 flex-shrink-0 rounded-full object-cover"
1101
- />
1102
- )}
1103
- <div className="min-w-0 flex-1">
1104
- <div className="flex items-start justify-between">
1105
- <div>
1106
- <p className="text-fm-primary text-sm font-semibold">
1107
- {title}
1108
- </p>
1109
- <p className="text-fm-secondary mt-1 text-sm">
1110
- {description}
1111
- </p>
1112
- </div>
1113
- <button
1114
- onClick={() => toast.dismiss(t)}
1115
- className="hover:bg-fm-surface-secondary ml-2 flex-shrink-0 rounded-full p-1 transition-colors"
1116
- >
1117
- <svg
1118
- className="text-fm-secondary h-4 w-4"
1119
- fill="none"
1120
- stroke="currentColor"
1121
- viewBox="0 0 24 24"
1122
- >
1123
- <path
1124
- strokeLinecap="round"
1125
- strokeLinejoin="round"
1126
- strokeWidth={2}
1127
- d="M6 18L18 6M6 6l12 12"
1128
- />
1129
- </svg>
1130
- </button>
1131
- </div>
1132
- <div className="mt-3 flex gap-2">
1133
- <button className="bg-fm-surface-info text-fm-surface-primary hover:bg-fm-surface-info rounded-full px-3 py-1 text-xs transition-colors">
1134
- View
1135
- </button>
1136
- <button
1137
- onClick={() => toast.dismiss(t)}
1138
- className="bg-fm-surface-secondary text-fm-secondary hover:bg-fm-surface-tertiary rounded-full px-3 py-1 text-xs transition-colors"
1139
- >
1140
- Dismiss
1141
- </button>
1142
- </div>
1143
- </div>
1144
- </div>
1145
- </div>
1146
- ))
1147
- }
1148
-
1149
- const progressToast = (message: string) => {
1150
- const toastId = toast.custom((t) => (
1151
- <div
1152
- className={`border-fm-divider-secondary bg-fm-surface-primary min-w-[300px] transform rounded-xl border p-4 shadow-lg transition-all duration-300 ease-in-out ${t ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"} `}
1153
- >
1154
- <div className="flex items-center gap-3">
1155
- <div className="bg-fm-surface-info-sec flex h-8 w-8 items-center justify-center rounded-full">
1156
- <div className="border-fm-info h-4 w-4 animate-spin rounded-full border-2 border-t-transparent"></div>
1157
- </div>
1158
- <div className="flex-1">
1159
- <p className="text-fm-primary font-medium">{message}</p>
1160
- <div className="bg-fm-surface-tertiary mt-2 h-2 w-full rounded-full">
1161
- <div
1162
- className="progress-bar bg-fm-surface-info h-2 rounded-full"
1163
- style={{ width: "0%" }}
1164
- ></div>
1165
- </div>
1166
- </div>
1167
- <button
1168
- onClick={() => toast.dismiss(t)}
1169
- className="hover:bg-fm-surface-secondary flex-shrink-0 rounded-full p-1 transition-colors"
1170
- >
1171
- <svg
1172
- className="text-fm-secondary h-4 w-4"
1173
- fill="none"
1174
- stroke="currentColor"
1175
- viewBox="0 0 24 24"
1176
- >
1177
- <path
1178
- strokeLinecap="round"
1179
- strokeLinejoin="round"
1180
- strokeWidth={2}
1181
- d="M6 18L18 6M6 6l12 12"
1182
- />
1183
- </svg>
1184
- </button>
1185
- </div>
1186
- </div>
1187
- ))
1188
-
1189
- // Simulate progress
1190
- let progress = 0
1191
- const interval = setInterval(() => {
1192
- progress += 10
1193
- const progressBar = document.querySelector(
1194
- ".progress-bar"
1195
- ) as HTMLElement
1196
- if (progressBar) {
1197
- progressBar.style.width = `${progress}%`
1198
- }
1199
-
1200
- if (progress >= 100) {
1201
- clearInterval(interval)
1202
- setTimeout(() => {
1203
- toast.dismiss(toastId)
1204
- toast.custom(() => (
1205
- <div className="border-fm-divider-positive bg-fm-surface-positive-sec flex items-center gap-3 rounded-xl border p-4">
1206
- <div className="bg-fm-surface-positive-sec flex h-8 w-8 items-center justify-center rounded-full">
1207
- <svg
1208
- className="text-fm-icon-positive h-5 w-5"
1209
- fill="none"
1210
- stroke="currentColor"
1211
- viewBox="0 0 24 24"
1212
- >
1213
- <path
1214
- strokeLinecap="round"
1215
- strokeLinejoin="round"
1216
- strokeWidth={2}
1217
- d="M5 13l4 4L19 7"
1218
- />
1219
- </svg>
1220
- </div>
1221
- <span className="text-fm-positive font-medium">
1222
- Upload completed!
1223
- </span>
1224
- </div>
1225
- ))
1226
- }, 500)
1227
- }
1228
- }, 300)
1229
- }
1230
-
1231
- const glassmorphismToast = (message: string, emoji: string) => {
1232
- toast.custom((t) => (
1233
- <div
1234
- className={`border-fm-divider-secondary bg-fm-surface-secondary transform rounded-2xl border p-4 shadow-xl backdrop-blur-md transition-all duration-500 ease-out ${t ? "translate-y-0 scale-100 opacity-100" : "translate-y-4 scale-95 opacity-0"} `}
1235
- >
1236
- <div className="flex items-center gap-3">
1237
- <span className="text-2xl">{emoji}</span>
1238
- <span className="text-fm-primary font-medium">{message}</span>
1239
- <button
1240
- onClick={() => toast.dismiss(t)}
1241
- className="hover:bg-fm-surface-tertiary ml-auto rounded-full p-1 transition-colors"
1242
- >
1243
- <svg
1244
- className="text-fm-secondary h-4 w-4"
1245
- fill="none"
1246
- stroke="currentColor"
1247
- viewBox="0 0 24 24"
1248
- >
1249
- <path
1250
- strokeLinecap="round"
1251
- strokeLinejoin="round"
1252
- strokeWidth={2}
1253
- d="M6 18L18 6M6 6l12 12"
1254
- />
1255
- </svg>
1256
- </button>
1257
- </div>
1258
- </div>
1259
- ))
1260
- }
1261
-
1262
- return (
1263
- <div className="text-fm-primary space-y-6">
1264
- <div className="space-y-4">
1265
- <h2 className="text-xl font-semibold">Headless Custom Design</h2>
1266
- <p>
1267
- Create completely custom toast designs using toast.custom() with
1268
- your own components and styling.
1269
- </p>
515
+ Assertive (error)
516
+ </Button>
1270
517
  </div>
518
+ </div>
1271
519
 
1272
- <div className="space-y-6">
1273
- {/* Icon-based Toasts */}
1274
- <div className="space-y-3">
1275
- <h3 className="text-lg font-medium">Icon-based Custom Toasts</h3>
1276
- <p className="text-sm">
1277
- Custom designed toasts with icons and tailored styling for each
1278
- type.
1279
- </p>
1280
- <div className="flex flex-wrap gap-2">
1281
- <Button
1282
- onClick={() =>
1283
- customToast("Profile updated successfully!", "success")
1284
- }
1285
- >
1286
- Custom Success
1287
- </Button>
1288
- <Button
1289
- onClick={() => customToast("Failed to save changes", "error")}
1290
- >
1291
- Custom Error
1292
- </Button>
1293
- <Button
1294
- onClick={() =>
1295
- customToast("Please review your settings", "warning")
1296
- }
1297
- >
1298
- Custom Warning
1299
- </Button>
1300
- <Button
1301
- onClick={() => customToast("New feature available", "info")}
1302
- >
1303
- Custom Info
1304
- </Button>
1305
- </div>
1306
- </div>
1307
-
1308
- {/* Card-style Toasts */}
1309
- <div className="space-y-3">
1310
- <h3 className="text-lg font-medium">Card-style Notifications</h3>
1311
- <p className="text-sm">
1312
- Rich card-style toasts with avatars and action buttons.
1313
- </p>
1314
- <div className="flex flex-wrap gap-2">
1315
- <Button
1316
- onClick={() =>
1317
- cardToast(
1318
- "New Message",
1319
- "John sent you a new message about the project timeline.",
1320
- "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=40&h=40&fit=crop&crop=face"
1321
- )
1322
- }
1323
- >
1324
- Message Notification
1325
- </Button>
1326
- <Button
1327
- onClick={() =>
1328
- cardToast(
1329
- "System Update",
1330
- "A new system update is available. Update now to get the latest features."
1331
- )
1332
- }
1333
- >
1334
- System Notification
1335
- </Button>
1336
- </div>
1337
- </div>
1338
-
1339
- {/* Progress Toasts */}
1340
- <div className="space-y-3">
1341
- <h3 className="text-lg font-medium">Progress Notifications</h3>
1342
- <p className="text-sm">
1343
- Custom toasts with progress bars for long-running operations.
1344
- </p>
1345
- <div className="flex flex-wrap gap-2">
1346
- <Button onClick={() => progressToast("Uploading files...")}>
1347
- Upload Progress
1348
- </Button>
1349
- </div>
1350
- </div>
1351
-
1352
- {/* Glassmorphism Toasts */}
1353
- <div className="space-y-3">
1354
- <h3 className="text-lg font-medium">Glassmorphism Style</h3>
1355
- <p className="text-sm">
1356
- Modern glassmorphism design with backdrop blur effects.
1357
- </p>
1358
- <div className="flex flex-wrap gap-2">
1359
- <Button
1360
- onClick={() =>
1361
- glassmorphismToast("Welcome to the future!", "🚀")
1362
- }
1363
- >
1364
- Glassmorphism Toast
1365
- </Button>
1366
- <Button
1367
- onClick={() =>
1368
- glassmorphismToast("Achievement unlocked!", "🏆")
1369
- }
1370
- >
1371
- Achievement Toast
1372
- </Button>
1373
- <Button
1374
- onClick={() =>
1375
- glassmorphismToast("You have a new follower", "👋")
1376
- }
1377
- >
1378
- Social Toast
1379
- </Button>
1380
- </div>
1381
- </div>
1382
-
1383
- {/* Code Example */}
1384
- <div className="space-y-3">
1385
- <h3 className="text-lg font-medium">Implementation Example</h3>
1386
- <div className="bg-fm-surface-secondary rounded-lg p-4 text-sm">
1387
- <pre className="text-fm-primary overflow-x-auto">
1388
- {`// Custom toast with full control
1389
- toast.custom((t) => (
1390
- <div className={\`
1391
- bg-white border rounded-xl p-4 shadow-lg
1392
- transform transition-all duration-300
1393
- \${t ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
1394
- \`}>
1395
- <div className="flex items-center gap-3">
1396
- <div className="w-8 h-8 bg-fm-surface-info-sec rounded-full flex items-center justify-center">
1397
- <CheckIcon className="w-5 h-5 text-fm-info" />
520
+ {/* Best practices */}
521
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
522
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
523
+ Accessibility best practices for Toast:
524
+ </p>
525
+ <ul className="mt-3 list-none space-y-2">
526
+ {[
527
+ 'Sonner mounts toasts in a <ol> with aria-live="polite" by default — assertive is used automatically for error toasts.',
528
+ "Keep messages brief and descriptive so screen readers can convey them in one phrase without cutting off.",
529
+ "For destructive or time-sensitive actions, use a duration of at least 5 000 ms or set duration: Infinity with a manual dismiss action.",
530
+ "Action and cancel buttons inside toasts are focusable via Tab — ensure their labels (label prop) are self-explanatory without surrounding context.",
531
+ "Never convey information through color alone — always pair a type (success/error/warning/info) with a clear message string.",
532
+ "Use the description prop to add context without cluttering the title — screen readers announce both in sequence.",
533
+ "toast.dismiss() is the programmatic way to remove all active toasts — useful for route changes or cleanup on unmount.",
534
+ ].map((tip) => (
535
+ <li key={tip}>
536
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
537
+ {tip}
538
+ </p>
539
+ </li>
540
+ ))}
541
+ </ul>
1398
542
  </div>
1399
- <span className="font-medium">Custom message</span>
1400
- <button onClick={() => toast.dismiss(t)}>
1401
- <XIcon className="w-4 h-4" />
1402
- </button>
1403
543
  </div>
1404
- </div>
1405
- ))`}
1406
- </pre>
1407
- </div>
1408
- </div>
1409
- </div>
1410
- </div>
1411
- )
1412
- },
544
+ ),
1413
545
  parameters: {
1414
546
  docs: {
1415
547
  description: {
1416
- story: `
1417
- Create completely custom toast designs using \`toast.custom()\`. This gives you full control over the toast appearance and behavior.
1418
-
1419
- **Key Features:**
1420
- - Full design control with custom React components
1421
- - Access to toast state (\`t\` parameter) for animations
1422
- - Custom dismiss logic and interactions
1423
- - Integration with your design system
1424
- - Advanced layouts like cards, progress bars, and glassmorphism
1425
- - Custom icons, avatars, and action buttons
1426
-
1427
- **Use Cases:**
1428
- - Branded notifications matching your design system
1429
- - Rich notifications with images and multiple actions
1430
- - Progress indicators for uploads/downloads
1431
- - Social media style notifications
1432
- - Achievement and gamification toasts
1433
- - Complex form validation feedback
1434
-
1435
- The \`t\` parameter in the render function represents the toast's visibility state, allowing you to create smooth enter/exit animations.
1436
- `,
548
+ story:
549
+ "Accessibility-focused story demonstrating ARIA live region announcements, keyboard dismissal via Tab focus and Escape, and the distinction between polite (aria-live=polite / role=status for info toasts) and assertive (aria-live=assertive / role=alert for error toasts) announcement modes. The infoBox at the bottom lists actionable best practices for building accessible toast notifications.",
1437
550
  },
1438
551
  },
1439
552
  },