aural-ui 3.0.7 → 4.1.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 (183) hide show
  1. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1199
  2. package/dist/components/avatar/Avatar.stories.tsx +235 -237
  3. package/dist/components/badge/Badge.stories.tsx +379 -116
  4. package/dist/components/banner/Banner.stories.tsx +445 -391
  5. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  6. package/dist/components/button/Button.stories.tsx +585 -230
  7. package/dist/components/button/index.tsx +7 -7
  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 -620
  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 +533 -856
  16. package/dist/components/dialog/Dialog.stories.tsx +505 -949
  17. package/dist/components/divider/Divider.stories.tsx +265 -502
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -993
  20. package/dist/components/drawer/index.tsx +3 -3
  21. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1018
  22. package/dist/components/form/Form.stories.tsx +560 -274
  23. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  24. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1221
  25. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  26. package/dist/components/if-else/if-else.stories.tsx +370 -83
  27. package/dist/components/input/Input.stories.tsx +436 -368
  28. package/dist/components/label/Label.stories.tsx +156 -154
  29. package/dist/components/list/List.stories.tsx +485 -822
  30. package/dist/components/marquee/Marquee.stories.tsx +356 -694
  31. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -410
  32. package/dist/components/overlay/Overlay.stories.tsx +452 -818
  33. package/dist/components/overlay/index.tsx +4 -4
  34. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  35. package/dist/components/popover/Popover.stories.tsx +484 -873
  36. package/dist/components/radio/Radio.stories.tsx +432 -124
  37. package/dist/components/resizable/Resizable.stories.tsx +496 -752
  38. package/dist/components/scroll-area/ScrollArea.stories.tsx +384 -1006
  39. package/dist/components/search/Search.stories.tsx +314 -575
  40. package/dist/components/select/Select.stories.tsx +684 -787
  41. package/dist/components/sheet/Sheet.stories.tsx +671 -936
  42. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  43. package/dist/components/slider/Slider.stories.tsx +384 -737
  44. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  45. package/dist/components/switch/Switch.stories.tsx +461 -208
  46. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  47. package/dist/components/table/Table.stories.tsx +770 -914
  48. package/dist/components/tabs/Tabs.stories.tsx +459 -1400
  49. package/dist/components/tag/Tag.stories.tsx +714 -542
  50. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  51. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -148
  52. package/dist/components/toast/Toast.stories.tsx +452 -1333
  53. package/dist/components/toggle/Toggle.stories.tsx +488 -909
  54. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1372
  55. package/dist/components/typography/Typography.stories.tsx +406 -89
  56. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  57. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  58. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  59. package/dist/icons/Icons.stories.tsx +0 -12
  60. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +226 -1013
  61. package/dist/icons/alert-icon/AlertIcon.stories.tsx +109 -929
  62. package/dist/icons/all-icons.tsx +124 -87
  63. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +140 -971
  64. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +148 -888
  65. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +135 -1019
  66. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +137 -953
  67. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +138 -997
  68. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +136 -942
  69. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +148 -1092
  70. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +146 -1211
  71. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +126 -615
  72. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +144 -1164
  73. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +167 -985
  74. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +122 -1179
  75. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +124 -1168
  76. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +119 -850
  77. package/dist/icons/camera-icon/CameraIcon.stories.tsx +112 -1213
  78. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +117 -934
  79. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +160 -961
  80. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +163 -961
  81. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +144 -942
  82. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +129 -966
  83. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +147 -964
  84. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +145 -975
  85. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +150 -1142
  86. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +114 -461
  87. package/dist/icons/coin-icon/CoinIcon.stories.tsx +124 -1322
  88. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +117 -1318
  89. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +114 -903
  90. package/dist/icons/command-icon/CommandIcon.stories.tsx +127 -1042
  91. package/dist/icons/copy-icon/CopyIcon.stories.tsx +123 -962
  92. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +147 -999
  93. package/dist/icons/cross-icon/CrossIcon.stories.tsx +139 -960
  94. package/dist/icons/download-icon/DownloadIcon.stories.tsx +126 -820
  95. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +124 -1031
  96. package/dist/icons/email-icon/EmailIcon.stories.tsx +115 -936
  97. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +112 -1111
  98. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +144 -1025
  99. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +143 -1036
  100. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +127 -1011
  101. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +126 -1056
  102. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +125 -614
  103. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +119 -1050
  104. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +169 -989
  105. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +115 -1145
  106. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +115 -1122
  107. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +130 -313
  108. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +145 -940
  109. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +119 -1174
  110. package/dist/icons/head-icon/HeadIcon.stories.tsx +111 -916
  111. package/dist/icons/heart-icon/HeartIcon.stories.tsx +120 -1019
  112. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +119 -683
  113. package/dist/icons/image-icon/ImageIcon.stories.tsx +105 -1121
  114. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +111 -1192
  115. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +136 -1256
  116. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +159 -962
  117. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +161 -1385
  118. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +124 -972
  119. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +119 -948
  120. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +119 -942
  121. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +108 -1215
  122. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +154 -1517
  123. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +110 -1188
  124. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +119 -678
  125. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +123 -1184
  126. package/dist/icons/message-icon/MessageIcon.stories.tsx +114 -538
  127. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +116 -1158
  128. package/dist/icons/moon-icon/MoonIcon.stories.tsx +120 -536
  129. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +109 -1184
  130. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +115 -1134
  131. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +119 -971
  132. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +111 -1100
  133. package/dist/icons/notes-icon/NotesIcon.stories.tsx +119 -1101
  134. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +109 -1111
  135. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +122 -684
  136. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +113 -954
  137. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +112 -877
  138. package/dist/icons/pause-icon/PauseIcon.stories.tsx +113 -1000
  139. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +115 -1070
  140. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +115 -978
  141. package/dist/icons/plus-icon/PlusIcon.stories.tsx +106 -1093
  142. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +107 -829
  143. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +102 -469
  144. package/dist/icons/search-icon/SearchIcon.stories.tsx +111 -1124
  145. package/dist/icons/setting-icon/SettingIcon.stories.tsx +107 -970
  146. package/dist/icons/share-icon/ShareIcon.stories.tsx +120 -1025
  147. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +117 -931
  148. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +137 -1104
  149. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +172 -982
  150. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +164 -983
  151. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +105 -958
  152. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +158 -580
  153. package/dist/icons/spinner-gradient-icon/index.tsx +6 -1
  154. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +158 -587
  155. package/dist/icons/spinner-solid-icon/index.tsx +6 -1
  156. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +146 -682
  157. package/dist/icons/spinner-solid-neutral-icon/index.tsx +1 -1
  158. package/dist/icons/star-icon/StarIcon.stories.tsx +124 -904
  159. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +112 -964
  160. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +116 -852
  161. package/dist/icons/sun-icon/SunIcon.stories.tsx +120 -831
  162. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +116 -950
  163. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +123 -980
  164. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +156 -1427
  165. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +146 -1142
  166. package/dist/icons/tick-icon/TickIcon.stories.tsx +145 -1276
  167. package/dist/icons/trash-icon/TrashIcon.stories.tsx +108 -933
  168. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +157 -1402
  169. package/dist/icons/upload-icon/UploadIcon.stories.tsx +115 -889
  170. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +118 -984
  171. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +125 -1049
  172. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +123 -1356
  173. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +110 -1171
  174. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +112 -1093
  175. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +115 -1087
  176. package/dist/icons/warning-icon/WarningIcon.stories.tsx +122 -1046
  177. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +161 -936
  178. package/dist/index.cjs +84 -84
  179. package/dist/index.js +84 -84
  180. package/dist/styles/aural-all-theme.css +1222 -0
  181. package/dist/styles/{aural-theme.css → aural-dark-theme.css} +15 -3
  182. package/dist/styles/aural-light-theme.css +1047 -0
  183. package/package.json +1 -1
@@ -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,971 +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="rounded-lg bg-blue-50 p-4">
817
- <h3 className="font-medium text-blue-900">Keyboard Navigation</h3>
818
- <p className="text-sm text-blue-700">
819
- Use Tab to focus toasts, Enter/Space to activate actions, Escape to
820
- dismiss.
821
- </p>
822
- </div>
823
-
824
- <div className="rounded-lg bg-green-50 p-4">
825
- <h3 className="font-medium text-green-900">Screen Reader Support</h3>
826
- <p className="text-sm text-green-700">
827
- Toasts are announced to screen readers with proper ARIA labels.
828
- </p>
829
- </div>
830
-
831
- <div className="rounded-lg bg-purple-50 p-4">
832
- <h3 className="font-medium text-purple-900">Reduced Motion</h3>
833
- <p className="text-sm text-purple-700">
834
- Respects user's motion preferences for animations.
835
- </p>
836
- </div>
837
- </div>
838
-
839
- <div className="flex gap-2">
840
- <Button
841
- onClick={() =>
842
- toast.success("Accessible success message", {
843
- description: "This toast is fully accessible to screen readers",
844
- })
845
- }
846
- >
847
- Try Accessible Toast
848
- </Button>
849
- </div>
850
- </div>
851
- ),
852
- parameters: {
853
- docs: {
854
- description: {
855
- story:
856
- "Toast component includes comprehensive accessibility features including keyboard navigation, screen reader support, and reduced motion respect.",
857
- },
858
- },
859
- },
860
- }
861
-
862
- export const RealWorldExamples: Story = {
863
- render: () => {
864
- const handleSave = () => {
865
- toast.loading("Saving changes...")
866
-
867
- // Simulate API call
868
- setTimeout(() => {
869
- toast.dismiss()
870
- toast.success("Changes saved successfully!", {
871
- description: "Your document has been updated.",
872
- action: {
873
- label: "View",
874
- onClick: () => toast("Opening document..."),
875
- },
876
- })
877
- }, 2000)
878
- }
879
-
880
- const handleDelete = () => {
881
- toast("Are you sure?", {
882
- description: "This action cannot be undone.",
883
- action: {
884
- label: "Delete",
885
- onClick: () => {
886
- toast.dismiss()
887
- toast.error("Item deleted", {
888
- description: "The item has been permanently removed.",
889
- })
890
- },
891
- },
892
- })
893
- }
894
-
895
- const handleUpload = () => {
896
- const files = ["document.pdf", "image.jpg", "data.csv"]
897
- let completed = 0
898
-
899
- files.forEach((file, index) => {
900
- setTimeout(
901
- () => {
902
- completed++
903
- if (completed === files.length) {
904
- toast.success("All files uploaded!", {
905
- 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).',
906
500
  })
907
- } else {
908
- toast.info(`Uploaded ${file}`, {
909
- 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,
910
512
  })
911
513
  }
912
- },
913
- (index + 1) * 1000
914
- )
915
- })
916
-
917
- toast.loading("Uploading files...", {
918
- description: "Please wait while we upload your files.",
919
- })
920
- }
921
-
922
- return (
923
- <div className="text-fm-primary space-y-4">
924
- <h2 className="text-xl font-semibold">Real World Examples</h2>
925
- <p>Common patterns and use cases for toast notifications.</p>
926
-
927
- <div className="space-y-3">
928
- <div className="rounded-lg border p-4">
929
- <h3 className="mb-2 font-medium">Document Editor</h3>
930
- <div className="flex gap-2">
931
- <Button onClick={handleSave}>Save Document</Button>
932
- <Button onClick={handleDelete}>Delete Document</Button>
933
- </div>
934
- </div>
935
-
936
- <div className="rounded-lg border p-4">
937
- <h3 className="mb-2 font-medium">File Upload</h3>
938
- <Button onClick={handleUpload}>Upload Multiple Files</Button>
939
- </div>
940
-
941
- <div className="rounded-lg border p-4">
942
- <h3 className="mb-2 font-medium">Network Status</h3>
943
- <div className="flex gap-2">
944
- <Button
945
- onClick={() =>
946
- toast.info("Connection restored", {
947
- description: "You're back online!",
948
- })
949
- }
950
- >
951
- Simulate Online
952
- </Button>
953
- <Button
954
- variant="outline"
955
- onClick={() =>
956
- toast.warning("Connection lost", {
957
- description: "Check your internet connection.",
958
- duration: Infinity,
959
- })
960
- }
961
- >
962
- Simulate Offline
963
- </Button>
964
- </div>
965
- </div>
966
- </div>
967
- </div>
968
- )
969
- },
970
- parameters: {
971
- docs: {
972
- description: {
973
- story:
974
- "Real-world examples showing how to use toasts for common application scenarios like saving, uploading, and network status.",
975
- },
976
- },
977
- },
978
- }
979
-
980
- export const HeadlessCustomDesign: Story = {
981
- render: () => {
982
- const customToast = (
983
- message: string,
984
- type: "success" | "error" | "warning" | "info" = "info"
985
- ) => {
986
- toast.custom((t) => (
987
- <div
988
- className={`flex items-center gap-3 rounded-xl border p-4 shadow-lg backdrop-blur-sm ${type === "success" ? "border-green-200 bg-green-50/90 text-green-800" : ""} ${type === "error" ? "border-red-200 bg-red-50/90 text-red-800" : ""} ${type === "warning" ? "border-orange-200 bg-orange-50/90 text-orange-800" : ""} ${type === "info" ? "border-blue-200 bg-blue-50/90 text-blue-800" : ""} transform transition-all duration-300 ease-in-out ${t ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"} `}
989
- >
990
- {/* Custom Icons */}
991
- <div className="flex-shrink-0">
992
- {type === "success" && (
993
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-100">
994
- <svg
995
- className="h-5 w-5 text-green-600"
996
- fill="none"
997
- stroke="currentColor"
998
- viewBox="0 0 24 24"
999
- >
1000
- <path
1001
- strokeLinecap="round"
1002
- strokeLinejoin="round"
1003
- strokeWidth={2}
1004
- d="M5 13l4 4L19 7"
1005
- />
1006
- </svg>
1007
- </div>
1008
- )}
1009
- {type === "error" && (
1010
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-red-100">
1011
- <svg
1012
- className="h-5 w-5 text-red-600"
1013
- fill="none"
1014
- stroke="currentColor"
1015
- viewBox="0 0 24 24"
1016
- >
1017
- <path
1018
- strokeLinecap="round"
1019
- strokeLinejoin="round"
1020
- strokeWidth={2}
1021
- d="M6 18L18 6M6 6l12 12"
1022
- />
1023
- </svg>
1024
- </div>
1025
- )}
1026
- {type === "warning" && (
1027
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-orange-100">
1028
- <svg
1029
- className="h-5 w-5 text-orange-600"
1030
- fill="none"
1031
- stroke="currentColor"
1032
- viewBox="0 0 24 24"
1033
- >
1034
- <path
1035
- strokeLinecap="round"
1036
- strokeLinejoin="round"
1037
- strokeWidth={2}
1038
- 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"
1039
- />
1040
- </svg>
1041
- </div>
1042
- )}
1043
- {type === "info" && (
1044
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-100">
1045
- <svg
1046
- className="h-5 w-5 text-blue-600"
1047
- fill="none"
1048
- stroke="currentColor"
1049
- viewBox="0 0 24 24"
1050
- >
1051
- <path
1052
- strokeLinecap="round"
1053
- strokeLinejoin="round"
1054
- strokeWidth={2}
1055
- d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
1056
- />
1057
- </svg>
1058
- </div>
1059
- )}
1060
- </div>
1061
-
1062
- {/* Message */}
1063
- <div className="flex-1 font-medium">{message}</div>
1064
-
1065
- {/* Close Button */}
1066
- <button
1067
- onClick={() => toast.dismiss(t)}
1068
- className="flex-shrink-0 rounded-full p-1 transition-colors hover:bg-black/10"
1069
514
  >
1070
- <svg
1071
- className="h-4 w-4"
1072
- fill="none"
1073
- stroke="currentColor"
1074
- viewBox="0 0 24 24"
1075
- >
1076
- <path
1077
- strokeLinecap="round"
1078
- strokeLinejoin="round"
1079
- strokeWidth={2}
1080
- d="M6 18L18 6M6 6l12 12"
1081
- />
1082
- </svg>
1083
- </button>
1084
- </div>
1085
- ))
1086
- }
1087
-
1088
- const cardToast = (title: string, description: string, avatar?: string) => {
1089
- toast.custom((t) => (
1090
- <div
1091
- className={`max-w-sm transform rounded-2xl border border-gray-200 bg-white 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"} `}
1092
- >
1093
- <div className="flex items-start gap-3">
1094
- {avatar && (
1095
- <img
1096
- src={avatar}
1097
- alt=""
1098
- className="h-10 w-10 flex-shrink-0 rounded-full object-cover"
1099
- />
1100
- )}
1101
- <div className="min-w-0 flex-1">
1102
- <div className="flex items-start justify-between">
1103
- <div>
1104
- <p className="text-sm font-semibold text-gray-900">{title}</p>
1105
- <p className="mt-1 text-sm text-gray-600">{description}</p>
1106
- </div>
1107
- <button
1108
- onClick={() => toast.dismiss(t)}
1109
- className="ml-2 flex-shrink-0 rounded-full p-1 transition-colors hover:bg-gray-100"
1110
- >
1111
- <svg
1112
- className="h-4 w-4 text-gray-400"
1113
- fill="none"
1114
- stroke="currentColor"
1115
- viewBox="0 0 24 24"
1116
- >
1117
- <path
1118
- strokeLinecap="round"
1119
- strokeLinejoin="round"
1120
- strokeWidth={2}
1121
- d="M6 18L18 6M6 6l12 12"
1122
- />
1123
- </svg>
1124
- </button>
1125
- </div>
1126
- <div className="mt-3 flex gap-2">
1127
- <button className="rounded-full bg-blue-600 px-3 py-1 text-xs text-white transition-colors hover:bg-blue-700">
1128
- View
1129
- </button>
1130
- <button
1131
- onClick={() => toast.dismiss(t)}
1132
- className="rounded-full bg-gray-100 px-3 py-1 text-xs text-gray-700 transition-colors hover:bg-gray-200"
1133
- >
1134
- Dismiss
1135
- </button>
1136
- </div>
1137
- </div>
1138
- </div>
1139
- </div>
1140
- ))
1141
- }
1142
-
1143
- const progressToast = (message: string) => {
1144
- const toastId = toast.custom((t) => (
1145
- <div
1146
- className={`min-w-[300px] transform rounded-xl border border-gray-200 bg-white p-4 shadow-lg transition-all duration-300 ease-in-out ${t ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"} `}
1147
- >
1148
- <div className="flex items-center gap-3">
1149
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-100">
1150
- <div className="h-4 w-4 animate-spin rounded-full border-2 border-blue-600 border-t-transparent"></div>
1151
- </div>
1152
- <div className="flex-1">
1153
- <p className="font-medium text-gray-900">{message}</p>
1154
- <div className="mt-2 h-2 w-full rounded-full bg-gray-200">
1155
- <div
1156
- className="progress-bar h-2 rounded-full bg-blue-600"
1157
- style={{ width: "0%" }}
1158
- ></div>
1159
- </div>
1160
- </div>
1161
- <button
1162
- onClick={() => toast.dismiss(t)}
1163
- className="flex-shrink-0 rounded-full p-1 transition-colors hover:bg-gray-100"
1164
- >
1165
- <svg
1166
- className="h-4 w-4 text-gray-400"
1167
- fill="none"
1168
- stroke="currentColor"
1169
- viewBox="0 0 24 24"
1170
- >
1171
- <path
1172
- strokeLinecap="round"
1173
- strokeLinejoin="round"
1174
- strokeWidth={2}
1175
- d="M6 18L18 6M6 6l12 12"
1176
- />
1177
- </svg>
1178
- </button>
1179
- </div>
1180
- </div>
1181
- ))
1182
-
1183
- // Simulate progress
1184
- let progress = 0
1185
- const interval = setInterval(() => {
1186
- progress += 10
1187
- const progressBar = document.querySelector(
1188
- ".progress-bar"
1189
- ) as HTMLElement
1190
- if (progressBar) {
1191
- progressBar.style.width = `${progress}%`
1192
- }
1193
-
1194
- if (progress >= 100) {
1195
- clearInterval(interval)
1196
- setTimeout(() => {
1197
- toast.dismiss(toastId)
1198
- toast.custom(() => (
1199
- <div className="flex items-center gap-3 rounded-xl border border-green-200 bg-green-50 p-4">
1200
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-100">
1201
- <svg
1202
- className="h-5 w-5 text-green-600"
1203
- fill="none"
1204
- stroke="currentColor"
1205
- viewBox="0 0 24 24"
1206
- >
1207
- <path
1208
- strokeLinecap="round"
1209
- strokeLinejoin="round"
1210
- strokeWidth={2}
1211
- d="M5 13l4 4L19 7"
1212
- />
1213
- </svg>
1214
- </div>
1215
- <span className="font-medium text-green-800">
1216
- Upload completed!
1217
- </span>
1218
- </div>
1219
- ))
1220
- }, 500)
1221
- }
1222
- }, 300)
1223
- }
1224
-
1225
- const glassmorphismToast = (message: string, emoji: string) => {
1226
- toast.custom((t) => (
1227
- <div
1228
- className={`transform rounded-2xl border border-white/30 bg-white/20 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"} `}
1229
- >
1230
- <div className="flex items-center gap-3">
1231
- <span className="text-2xl">{emoji}</span>
1232
- <span className="font-medium text-gray-800">{message}</span>
1233
- <button
1234
- onClick={() => toast.dismiss(t)}
1235
- className="ml-auto rounded-full p-1 transition-colors hover:bg-white/20"
1236
- >
1237
- <svg
1238
- className="h-4 w-4 text-gray-600"
1239
- fill="none"
1240
- stroke="currentColor"
1241
- viewBox="0 0 24 24"
1242
- >
1243
- <path
1244
- strokeLinecap="round"
1245
- strokeLinejoin="round"
1246
- strokeWidth={2}
1247
- d="M6 18L18 6M6 6l12 12"
1248
- />
1249
- </svg>
1250
- </button>
1251
- </div>
1252
- </div>
1253
- ))
1254
- }
1255
-
1256
- return (
1257
- <div className="text-fm-primary space-y-6">
1258
- <div className="space-y-4">
1259
- <h2 className="text-xl font-semibold">Headless Custom Design</h2>
1260
- <p>
1261
- Create completely custom toast designs using toast.custom() with
1262
- your own components and styling.
1263
- </p>
515
+ Assertive (error)
516
+ </Button>
1264
517
  </div>
518
+ </div>
1265
519
 
1266
- <div className="space-y-6">
1267
- {/* Icon-based Toasts */}
1268
- <div className="space-y-3">
1269
- <h3 className="text-lg font-medium">Icon-based Custom Toasts</h3>
1270
- <p className="text-sm">
1271
- Custom designed toasts with icons and tailored styling for each
1272
- type.
1273
- </p>
1274
- <div className="flex flex-wrap gap-2">
1275
- <Button
1276
- onClick={() =>
1277
- customToast("Profile updated successfully!", "success")
1278
- }
1279
- >
1280
- Custom Success
1281
- </Button>
1282
- <Button
1283
- onClick={() => customToast("Failed to save changes", "error")}
1284
- >
1285
- Custom Error
1286
- </Button>
1287
- <Button
1288
- onClick={() =>
1289
- customToast("Please review your settings", "warning")
1290
- }
1291
- >
1292
- Custom Warning
1293
- </Button>
1294
- <Button
1295
- onClick={() => customToast("New feature available", "info")}
1296
- >
1297
- Custom Info
1298
- </Button>
1299
- </div>
1300
- </div>
1301
-
1302
- {/* Card-style Toasts */}
1303
- <div className="space-y-3">
1304
- <h3 className="text-lg font-medium">Card-style Notifications</h3>
1305
- <p className="text-sm">
1306
- Rich card-style toasts with avatars and action buttons.
1307
- </p>
1308
- <div className="flex flex-wrap gap-2">
1309
- <Button
1310
- onClick={() =>
1311
- cardToast(
1312
- "New Message",
1313
- "John sent you a new message about the project timeline.",
1314
- "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=40&h=40&fit=crop&crop=face"
1315
- )
1316
- }
1317
- >
1318
- Message Notification
1319
- </Button>
1320
- <Button
1321
- onClick={() =>
1322
- cardToast(
1323
- "System Update",
1324
- "A new system update is available. Update now to get the latest features."
1325
- )
1326
- }
1327
- >
1328
- System Notification
1329
- </Button>
1330
- </div>
1331
- </div>
1332
-
1333
- {/* Progress Toasts */}
1334
- <div className="space-y-3">
1335
- <h3 className="text-lg font-medium">Progress Notifications</h3>
1336
- <p className="text-sm">
1337
- Custom toasts with progress bars for long-running operations.
1338
- </p>
1339
- <div className="flex flex-wrap gap-2">
1340
- <Button onClick={() => progressToast("Uploading files...")}>
1341
- Upload Progress
1342
- </Button>
1343
- </div>
1344
- </div>
1345
-
1346
- {/* Glassmorphism Toasts */}
1347
- <div className="space-y-3">
1348
- <h3 className="text-lg font-medium">Glassmorphism Style</h3>
1349
- <p className="text-sm">
1350
- Modern glassmorphism design with backdrop blur effects.
1351
- </p>
1352
- <div className="flex flex-wrap gap-2">
1353
- <Button
1354
- onClick={() =>
1355
- glassmorphismToast("Welcome to the future!", "🚀")
1356
- }
1357
- >
1358
- Glassmorphism Toast
1359
- </Button>
1360
- <Button
1361
- onClick={() =>
1362
- glassmorphismToast("Achievement unlocked!", "🏆")
1363
- }
1364
- >
1365
- Achievement Toast
1366
- </Button>
1367
- <Button
1368
- onClick={() =>
1369
- glassmorphismToast("You have a new follower", "👋")
1370
- }
1371
- >
1372
- Social Toast
1373
- </Button>
1374
- </div>
1375
- </div>
1376
-
1377
- {/* Code Example */}
1378
- <div className="space-y-3">
1379
- <h3 className="text-lg font-medium">Implementation Example</h3>
1380
- <div className="rounded-lg bg-gray-50 p-4 text-sm">
1381
- <pre className="overflow-x-auto text-gray-800">
1382
- {`// Custom toast with full control
1383
- toast.custom((t) => (
1384
- <div className={\`
1385
- bg-white border rounded-xl p-4 shadow-lg
1386
- transform transition-all duration-300
1387
- \${t ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
1388
- \`}>
1389
- <div className="flex items-center gap-3">
1390
- <div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
1391
- <CheckIcon className="w-5 h-5 text-blue-600" />
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>
1392
542
  </div>
1393
- <span className="font-medium">Custom message</span>
1394
- <button onClick={() => toast.dismiss(t)}>
1395
- <XIcon className="w-4 h-4" />
1396
- </button>
1397
543
  </div>
1398
- </div>
1399
- ))`}
1400
- </pre>
1401
- </div>
1402
- </div>
1403
- </div>
1404
- </div>
1405
- )
1406
- },
544
+ ),
1407
545
  parameters: {
1408
546
  docs: {
1409
547
  description: {
1410
- story: `
1411
- Create completely custom toast designs using \`toast.custom()\`. This gives you full control over the toast appearance and behavior.
1412
-
1413
- **Key Features:**
1414
- - Full design control with custom React components
1415
- - Access to toast state (\`t\` parameter) for animations
1416
- - Custom dismiss logic and interactions
1417
- - Integration with your design system
1418
- - Advanced layouts like cards, progress bars, and glassmorphism
1419
- - Custom icons, avatars, and action buttons
1420
-
1421
- **Use Cases:**
1422
- - Branded notifications matching your design system
1423
- - Rich notifications with images and multiple actions
1424
- - Progress indicators for uploads/downloads
1425
- - Social media style notifications
1426
- - Achievement and gamification toasts
1427
- - Complex form validation feedback
1428
-
1429
- The \`t\` parameter in the render function represents the toast's visibility state, allowing you to create smooth enter/exit animations.
1430
- `,
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.",
1431
550
  },
1432
551
  },
1433
552
  },