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
@@ -1,13 +1,11 @@
1
1
  import React, { useState } from "react"
2
2
  import { Button } from "@components/button"
3
- import { AlertIcon } from "@icons/alert-icon"
4
- import { ChevronRightIcon } from "@icons/chevron-right-icon"
5
3
  import { CrossIcon } from "@icons/cross-icon"
6
- import { MaintenanceIcon } from "@icons/maintenance-icon"
7
4
  import { SearchIcon } from "@icons/search-icon"
8
- import { TickCircleIcon } from "@icons/tick-circle-icon"
9
5
  import type { Meta, StoryObj } from "@storybook/react-vite"
10
6
 
7
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
8
+
11
9
  import { Overlay } from "."
12
10
 
13
11
  const meta: Meta<typeof Overlay> = {
@@ -15,74 +13,29 @@ const meta: Meta<typeof Overlay> = {
15
13
  component: Overlay,
16
14
  parameters: {
17
15
  layout: "fullscreen",
18
- backgrounds: {
19
- default: "dark",
20
- values: [
21
- { name: "dark", value: "#0a0a0a" },
22
- { name: "light", value: "#ffffff" },
23
- ],
24
- },
25
16
  docs: {
26
17
  description: {
27
- component: `
28
- # Overlay Component
29
-
30
- A flexible overlay component for creating modal backgrounds, loading states, and layered content with customizable opacity, glass effects, and noise textures.
31
-
32
- ## Features
33
-
34
- - **Multiple Opacity Levels**: High (80%), medium (60%), low (40%), or none
35
- - **Glass Effect**: Backdrop blur with high, medium, low, or no blur
36
- - **Noise Texture**: Visual noise patterns for enhanced aesthetics
37
- - **Animation Support**: Built-in fade in/out animations
38
- - **Z-Index Management**: Proper layering with configurable z-index
39
- - **Content Positioning**: Automatic centering of overlay content
40
- - **Event Handling**: Pointer events management for interactive overlays
41
-
42
- ## Variant Options
43
-
44
- ### Opacity Variants
45
- - **high**: 80% black background - Maximum dimming
46
- - **medium**: 60% black background - Balanced dimming (default)
47
- - **low**: 40% black background - Subtle dimming
48
- - **none**: Solid black background - Complete coverage
49
-
50
- ### Glass Effect Variants
51
- - **high**: Strong backdrop blur - Heavy glass effect
52
- - **medium**: Medium backdrop blur - Moderate glass effect
53
- - **low**: Light backdrop blur - Subtle glass effect (default)
54
- - **none**: No backdrop blur - No glass effect
55
-
56
- ### Noise Variants
57
- - **high**: Strong noise texture - Pronounced texture
58
- - **medium**: Medium noise texture - Balanced texture
59
- - **low**: Light noise texture - Subtle texture (default)
60
- - **none**: No noise texture - Clean overlay
61
-
62
- ## Usage Examples
63
-
64
- ### Basic Overlay
65
- \`\`\`tsx
66
- <Overlay>
67
- <div>Overlay content</div>
68
- </Overlay>
69
- \`\`\`
70
-
71
- ### Custom Configuration
72
- \`\`\`tsx
73
- <Overlay opacity="high" glass="medium" noise="low">
74
- <div className="bg-white/10 p-4 rounded-lg">Modal content</div>
75
- </Overlay>
76
- \`\`\`
77
-
78
- ### Loading State
79
- \`\`\`tsx
80
- <Overlay opacity="medium" glass="high">
81
- <div className="text-fm-primary">Loading...</div>
82
- </Overlay>
83
- \`\`\`
84
- `,
18
+ component:
19
+ "A flexible full-screen overlay for modal backgrounds, loading states, and layered UI. Offers independent control over opacity (low/medium/high/none), glass blur (none/low/medium/high), and noise texture (none/low/medium/high). Renders children in a centered z-50 container above the scrim.",
85
20
  },
21
+ page: () => (
22
+ <AuralComponentDocsPage
23
+ features={[
24
+ {
25
+ title: "Opacity Control",
26
+ description: "Low to high scrim",
27
+ },
28
+ {
29
+ title: "Glass Blur",
30
+ description: "None to high backdrop",
31
+ },
32
+ {
33
+ title: "Noise Texture",
34
+ description: "Grain overlay level",
35
+ },
36
+ ]}
37
+ />
38
+ ),
86
39
  },
87
40
  },
88
41
  tags: ["autodocs"],
@@ -91,48 +44,48 @@ A flexible overlay component for creating modal backgrounds, loading states, and
91
44
  export default meta
92
45
  type Story = StoryObj<typeof Overlay>
93
46
 
94
- // Demo background content component
95
- const BackgroundContent = () => (
96
- <div className="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-pink-900 p-8">
47
+ // ─── shared backing content ───────────────────────────────────────────────────
48
+
49
+ /**
50
+ * A content card background built entirely from design tokens.
51
+ * No hardcoded gradient colors.
52
+ */
53
+ const BackingContent = () => (
54
+ <div className="bg-fm-surface-primary min-h-screen w-full p-8">
97
55
  <div className="mx-auto max-w-4xl space-y-8">
98
- <header className="text-center">
99
- <h1 className="mb-4 text-4xl font-bold text-white">
100
- Background Content
56
+ <header className="space-y-2">
57
+ <p className="text-fm-tertiary font-fm-brand text-fm-sm leading-fm-sm tracking-widest uppercase">
58
+ PocketFM
59
+ </p>
60
+ <h1 className="text-fm-primary font-fm-brand text-fm-4xl leading-fm-4xl font-semibold">
61
+ Your music, everywhere
101
62
  </h1>
102
- <p className="text-fm-tertiary">
103
- This content sits behind the overlay to demonstrate the effects
63
+ <p className="text-fm-secondary font-fm-text text-fm-lg leading-fm-xl">
64
+ Discover curated playlists, follow your favourite artists, and listen
65
+ offline — all in one place.
104
66
  </p>
105
67
  </header>
106
68
 
107
69
  <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
108
- {Array.from({ length: 6 }, (_, i) => (
70
+ {[
71
+ { label: "New Releases", meta: "Updated today" },
72
+ { label: "Top Charts", meta: "Global & local" },
73
+ { label: "Your Mixes", meta: "Personalised for you" },
74
+ { label: "Artist Radio", meta: "Endless discovery" },
75
+ { label: "Podcasts", meta: "500 k+ shows" },
76
+ { label: "Audiobooks", meta: "10 k+ titles" },
77
+ ].map((card) => (
109
78
  <div
110
- key={i}
111
- className="border-fm-divider-secondary bg-fm-surface-secondary/80 rounded-lg border p-6"
79
+ key={card.label}
80
+ className="border-fm-divider-secondary bg-fm-surface-secondary rounded-xl border p-6"
112
81
  >
113
- <div className="mb-4">
114
- <h3 className="text-lg font-semibold text-white">Card {i + 1}</h3>
115
- <p className="text-fm-secondary text-sm">
116
- Sample card content with some description text
117
- </p>
118
- </div>
119
- <div className="space-y-3">
120
- <div className="flex h-32 items-center justify-center rounded-lg bg-gradient-to-r from-cyan-500 to-blue-600">
121
- <span className="font-medium text-white">
122
- Image Placeholder
123
- </span>
124
- </div>
125
- <p className="text-fm-tertiary text-sm">
126
- This is some sample content that demonstrates how the overlay
127
- affects background visibility and readability.
128
- </p>
129
- <div className="flex gap-2">
130
- <Button size="sm" variant="outline">
131
- Action
132
- </Button>
133
- <Button size="sm">Primary</Button>
134
- </div>
135
- </div>
82
+ <div className="bg-fm-surface-contrast mb-4 h-24 w-full rounded-lg" />
83
+ <p className="text-fm-primary font-fm-brand text-fm-md leading-fm-md font-semibold">
84
+ {card.label}
85
+ </p>
86
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
87
+ {card.meta}
88
+ </p>
136
89
  </div>
137
90
  ))}
138
91
  </div>
@@ -140,666 +93,203 @@ const BackgroundContent = () => (
140
93
  </div>
141
94
  )
142
95
 
143
- // 1. Opacity Variants
144
- export const OpacityVariants: Story = {
145
- render: () => {
146
- const [activeOverlay, setActiveOverlay] = useState<string | null>(null)
96
+ // ─── 1. Configurations ────────────────────────────────────────────────────────
147
97
 
148
- return (
149
- <div className="relative">
150
- <BackgroundContent />
151
-
152
- {/* Control Panel */}
153
- <div className="fixed top-4 left-4 z-50 space-y-2">
154
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
155
- <h3 className="text-fm-primary text-sm font-medium">
156
- Opacity Variants
157
- </h3>
158
- <div className="space-y-2">
159
- <Button
160
- size="sm"
161
- variant={activeOverlay === "low" ? "primary" : "outline"}
162
- onClick={() =>
163
- setActiveOverlay(activeOverlay === "low" ? null : "low")
164
- }
165
- className="w-full"
166
- >
167
- Low (40%)
168
- </Button>
169
- <Button
170
- size="sm"
171
- variant={activeOverlay === "medium" ? "primary" : "outline"}
172
- onClick={() =>
173
- setActiveOverlay(activeOverlay === "medium" ? null : "medium")
174
- }
175
- className="w-full"
176
- >
177
- Medium (60%)
178
- </Button>
179
- <Button
180
- size="sm"
181
- variant={activeOverlay === "high" ? "primary" : "outline"}
182
- onClick={() =>
183
- setActiveOverlay(activeOverlay === "high" ? null : "high")
184
- }
185
- className="w-full"
186
- >
187
- High (80%)
188
- </Button>
189
- <Button
190
- size="sm"
191
- variant={activeOverlay === "none" ? "primary" : "outline"}
192
- onClick={() =>
193
- setActiveOverlay(activeOverlay === "none" ? null : "none")
194
- }
195
- className="w-full"
196
- >
197
- None (100%)
198
- </Button>
199
- </div>
200
- {activeOverlay && (
201
- <Button
202
- size="sm"
203
- variant="secondary"
204
- onClick={() => setActiveOverlay(null)}
205
- className="w-full"
206
- >
207
- Clear Overlay
208
- </Button>
209
- )}
210
- </div>
211
- </div>
98
+ export const Configurations: Story = {
99
+ render: () => {
100
+ const [activeOpacity, setActiveOpacity] = useState<
101
+ "low" | "medium" | "high" | "none" | null
102
+ >(null)
103
+ const [activeGlass, setActiveGlass] = useState<
104
+ "none" | "low" | "medium" | "high" | null
105
+ >(null)
106
+ const [activeNoise, setActiveNoise] = useState<
107
+ "none" | "low" | "medium" | "high" | null
108
+ >(null)
109
+
110
+ const clearAll = () => {
111
+ setActiveOpacity(null)
112
+ setActiveGlass(null)
113
+ setActiveNoise(null)
114
+ }
212
115
 
213
- {/* Overlays */}
214
- {activeOverlay === "low" && (
215
- <Overlay opacity="low">
216
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
217
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
218
- Low Opacity Overlay
219
- </h3>
220
- <p className="text-fm-secondary text-sm">
221
- 40% background dimming - Background remains quite visible
222
- </p>
223
- </div>
224
- </Overlay>
225
- )}
116
+ const hasActive = activeOpacity || activeGlass || activeNoise
226
117
 
227
- {activeOverlay === "medium" && (
228
- <Overlay opacity="medium">
229
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
230
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
231
- Medium Opacity Overlay
232
- </h3>
233
- <p className="text-fm-secondary text-sm">
234
- 60% background dimming - Balanced visibility
235
- </p>
236
- </div>
237
- </Overlay>
238
- )}
239
-
240
- {activeOverlay === "high" && (
241
- <Overlay opacity="high">
242
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
243
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
244
- High Opacity Overlay
245
- </h3>
246
- <p className="text-fm-secondary text-sm">
247
- 80% background dimming - Strong focus on overlay content
248
- </p>
249
- </div>
250
- </Overlay>
251
- )}
118
+ const opacityOptions = [
119
+ { value: "low", label: "Low · 40%" },
120
+ { value: "medium", label: "Medium · 60%" },
121
+ { value: "high", label: "High · 80%" },
122
+ { value: "none", label: "Solid · 100%" },
123
+ ] as const
252
124
 
253
- {activeOverlay === "none" && (
254
- <Overlay opacity="none">
255
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
256
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
257
- No Opacity Overlay
258
- </h3>
259
- <p className="text-fm-secondary text-sm">
260
- 100% background coverage - Complete background blocking
261
- </p>
262
- </div>
263
- </Overlay>
264
- )}
265
- </div>
266
- )
267
- },
268
- parameters: {
269
- docs: {
270
- description: {
271
- story:
272
- "Interactive demonstration of different opacity levels showing how background visibility changes from 40% to 100% coverage.",
273
- },
274
- },
275
- },
276
- }
125
+ const glassOptions = [
126
+ { value: "none", label: "No blur" },
127
+ { value: "low", label: "Low blur" },
128
+ { value: "medium", label: "Medium blur" },
129
+ { value: "high", label: "High blur" },
130
+ ] as const
277
131
 
278
- // 2. Glass Effect Variants
279
- export const GlassEffectVariants: Story = {
280
- render: () => {
281
- const [activeGlass, setActiveGlass] = useState<string | null>(null)
132
+ const noiseOptions = [
133
+ { value: "none", label: "No texture" },
134
+ { value: "low", label: "Low texture" },
135
+ { value: "medium", label: "Medium texture" },
136
+ { value: "high", label: "Strong texture" },
137
+ ] as const
282
138
 
283
139
  return (
284
- <div className="relative">
285
- <BackgroundContent />
286
-
287
- {/* Control Panel */}
288
- <div className="fixed top-4 right-4 z-50 space-y-2">
289
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
290
- <h3 className="text-fm-primary text-sm font-medium">
291
- Glass Effect Variants
292
- </h3>
293
- <div className="space-y-2">
294
- <Button
295
- size="sm"
296
- variant={activeGlass === "none" ? "primary" : "outline"}
297
- onClick={() =>
298
- setActiveGlass(activeGlass === "none" ? null : "none")
299
- }
300
- className="w-full"
301
- >
302
- No Glass
303
- </Button>
304
- <Button
305
- size="sm"
306
- variant={activeGlass === "low" ? "primary" : "outline"}
307
- onClick={() =>
308
- setActiveGlass(activeGlass === "low" ? null : "low")
309
- }
310
- className="w-full"
311
- >
312
- Low Blur
313
- </Button>
314
- <Button
315
- size="sm"
316
- variant={activeGlass === "medium" ? "primary" : "outline"}
317
- onClick={() =>
318
- setActiveGlass(activeGlass === "medium" ? null : "medium")
319
- }
320
- className="w-full"
321
- >
322
- Medium Blur
323
- </Button>
324
- <Button
325
- size="sm"
326
- variant={activeGlass === "high" ? "primary" : "outline"}
327
- onClick={() =>
328
- setActiveGlass(activeGlass === "high" ? null : "high")
329
- }
330
- className="w-full"
331
- >
332
- High Blur
333
- </Button>
334
- </div>
335
- {activeGlass && (
336
- <Button
337
- size="sm"
338
- variant="secondary"
339
- onClick={() => setActiveGlass(null)}
340
- className="w-full"
341
- >
342
- Clear Overlay
343
- </Button>
344
- )}
345
- </div>
346
- </div>
347
-
348
- {/* Glass Effect Overlays */}
349
- {activeGlass === "none" && (
350
- <Overlay opacity="medium" glass="none">
351
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6">
352
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
353
- No Glass Effect
354
- </h3>
355
- <p className="text-fm-secondary text-sm">
356
- Clean overlay without backdrop blur
140
+ <div className="w-full p-8">
141
+ <div className="mx-auto grid max-w-5xl grid-cols-1 gap-6 lg:grid-cols-[280px_minmax(0,1fr)]">
142
+ {/* ── Control panel ── */}
143
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary h-[32rem] overflow-y-auto rounded-xl border p-5 shadow-lg">
144
+ <div className="space-y-5">
145
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
146
+ Opacity
357
147
  </p>
358
- </div>
359
- </Overlay>
360
- )}
361
-
362
- {activeGlass === "low" && (
363
- <Overlay opacity="medium" glass="low">
364
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6">
365
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
366
- Low Glass Effect
367
- </h3>
368
- <p className="text-fm-secondary text-sm">
369
- Subtle backdrop blur for gentle glass effect
370
- </p>
371
- </div>
372
- </Overlay>
373
- )}
148
+ <div className="space-y-2">
149
+ {opacityOptions.map((o) => (
150
+ <Button
151
+ key={o.value}
152
+ size="sm"
153
+ variant={activeOpacity === o.value ? "primary" : "outline"}
154
+ onClick={() =>
155
+ setActiveOpacity(
156
+ activeOpacity === o.value ? null : o.value
157
+ )
158
+ }
159
+ className="w-full justify-start"
160
+ >
161
+ {o.label}
162
+ </Button>
163
+ ))}
164
+ </div>
374
165
 
375
- {activeGlass === "medium" && (
376
- <Overlay opacity="medium" glass="medium">
377
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6">
378
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
379
- Medium Glass Effect
380
- </h3>
381
- <p className="text-fm-secondary text-sm">
382
- Balanced backdrop blur for modern glass aesthetics
383
- </p>
384
- </div>
385
- </Overlay>
386
- )}
166
+ <div className="border-fm-divider-secondary border-t pt-4" />
387
167
 
388
- {activeGlass === "high" && (
389
- <Overlay opacity="medium" glass="high">
390
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6">
391
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
392
- High Glass Effect
393
- </h3>
394
- <p className="text-fm-secondary text-sm">
395
- Strong backdrop blur for premium frosted glass look
168
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
169
+ Glass
396
170
  </p>
397
- </div>
398
- </Overlay>
399
- )}
400
- </div>
401
- )
402
- },
403
- parameters: {
404
- docs: {
405
- description: {
406
- story:
407
- "Interactive demonstration of glass effect variants showing different levels of backdrop blur from none to high intensity.",
408
- },
409
- },
410
- },
411
- }
412
-
413
- // 3. Loading States
414
- export const LoadingStates: Story = {
415
- render: () => {
416
- const [loadingType, setLoadingType] = useState<string | null>(null)
417
-
418
- const startLoading = (type: string) => {
419
- setLoadingType(type)
420
- setTimeout(() => setLoadingType(null), 3000)
421
- }
171
+ <div className="space-y-2">
172
+ {glassOptions.map((g) => (
173
+ <Button
174
+ key={g.value}
175
+ size="sm"
176
+ variant={activeGlass === g.value ? "primary" : "outline"}
177
+ onClick={() =>
178
+ setActiveGlass(activeGlass === g.value ? null : g.value)
179
+ }
180
+ className="w-full justify-start"
181
+ >
182
+ {g.label}
183
+ </Button>
184
+ ))}
185
+ </div>
422
186
 
423
- return (
424
- <div className="relative">
425
- <BackgroundContent />
426
-
427
- {/* Control Panel */}
428
- <div className="fixed top-4 left-1/2 z-50 -translate-x-1/2 transform">
429
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
430
- <h3 className="text-fm-primary text-center text-sm font-medium">
431
- Loading States
432
- </h3>
433
- <div className="flex gap-2">
434
- <Button
435
- size="sm"
436
- onClick={() => startLoading("spinner")}
437
- disabled={!!loadingType}
438
- >
439
- Spinner Loading
440
- </Button>
441
- <Button
442
- size="sm"
443
- onClick={() => startLoading("progress")}
444
- disabled={!!loadingType}
445
- >
446
- Progress Loading
447
- </Button>
448
- <Button
449
- size="sm"
450
- onClick={() => startLoading("dots")}
451
- disabled={!!loadingType}
452
- >
453
- Dots Loading
454
- </Button>
455
- </div>
456
- </div>
457
- </div>
187
+ <div className="border-fm-divider-secondary border-t pt-4" />
458
188
 
459
- {/* Loading Overlays */}
460
- {loadingType === "spinner" && (
461
- <Overlay opacity="high" glass="medium">
462
- <div className="text-fm-primary text-center">
463
- <div className="border-fm-primary mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2"></div>
464
- <h3 className="mb-2 text-lg font-medium">Loading...</h3>
465
- <p className="text-fm-secondary">
466
- Please wait while we process your request
189
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
190
+ Noise
467
191
  </p>
468
- </div>
469
- </Overlay>
470
- )}
471
-
472
- {loadingType === "progress" && (
473
- <Overlay opacity="high" glass="low">
474
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 min-w-80 rounded-lg border p-6 backdrop-blur-sm">
475
- <div className="text-fm-primary text-center">
476
- <h3 className="mb-4 text-lg font-medium">Uploading Files</h3>
477
- <div className="bg-fm-surface-tertiary mb-4 h-2 w-full rounded-full">
478
- <div
479
- className="bg-fm-info h-2 animate-pulse rounded-full"
480
- style={{ width: "65%" }}
481
- ></div>
482
- </div>
483
- <p className="text-fm-secondary">
484
- 65% complete - 3 of 5 files uploaded
485
- </p>
192
+ <div className="space-y-2">
193
+ {noiseOptions.map((n) => (
194
+ <Button
195
+ key={n.value}
196
+ size="sm"
197
+ variant={activeNoise === n.value ? "primary" : "outline"}
198
+ onClick={() =>
199
+ setActiveNoise(activeNoise === n.value ? null : n.value)
200
+ }
201
+ className="w-full justify-start"
202
+ >
203
+ {n.label}
204
+ </Button>
205
+ ))}
486
206
  </div>
487
- </div>
488
- </Overlay>
489
- )}
490
207
 
491
- {loadingType === "dots" && (
492
- <Overlay opacity="medium" glass="high">
493
- <div className="text-fm-primary text-center">
494
- <div className="mb-4 flex items-center justify-center space-x-2">
495
- <div className="bg-fm-primary h-3 w-3 animate-bounce rounded-full"></div>
496
- <div
497
- className="bg-fm-primary h-3 w-3 animate-bounce rounded-full"
498
- style={{ animationDelay: "0.1s" }}
499
- ></div>
500
- <div
501
- className="bg-fm-primary h-3 w-3 animate-bounce rounded-full"
502
- style={{ animationDelay: "0.2s" }}
503
- ></div>
504
- </div>
505
- <h3 className="mb-2 text-lg font-medium">Processing</h3>
506
- <p className="text-fm-secondary">Analyzing your data...</p>
208
+ {hasActive && (
209
+ <>
210
+ <div className="border-fm-divider-secondary border-t pt-4" />
211
+ <Button
212
+ size="sm"
213
+ variant="secondary"
214
+ onClick={clearAll}
215
+ className="w-full"
216
+ >
217
+ Clear overlay
218
+ </Button>
219
+ </>
220
+ )}
507
221
  </div>
508
- </Overlay>
509
- )}
510
- </div>
511
- )
512
- },
513
- parameters: {
514
- docs: {
515
- description: {
516
- story:
517
- "Common loading state patterns using overlays with different animations and progress indicators.",
518
- },
519
- },
520
- },
521
- }
522
-
523
- // 4. Interactive Examples
524
- export const InteractiveExamples: Story = {
525
- render: () => {
526
- const [showSearch, setShowSearch] = useState(false)
527
- const [showSettings, setShowSettings] = useState(false)
528
- const [showConfirm, setShowConfirm] = useState(false)
222
+ </div>
529
223
 
530
- return (
531
- <div className="relative">
532
- <BackgroundContent />
533
-
534
- {/* Control Panel */}
535
- <div className="fixed top-4 left-1/2 z-50 -translate-x-1/2 transform">
536
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
537
- <h3 className="text-fm-primary text-center text-sm font-medium">
538
- Interactive Examples
539
- </h3>
540
- <div className="flex gap-2">
541
- <Button size="sm" onClick={() => setShowSearch(true)}>
542
- Search Modal
543
- </Button>
544
- <Button size="sm" onClick={() => setShowSettings(true)}>
545
- Settings
546
- </Button>
547
- <Button
548
- size="sm"
549
- variant="primary"
550
- onClick={() => setShowConfirm(true)}
551
- >
552
- Confirm Action
553
- </Button>
224
+ {/* ── Preview stage ── */}
225
+ <div className="relative overflow-hidden rounded-2xl">
226
+ <div className="h-[32rem] overflow-hidden rounded-2xl">
227
+ <BackingContent />
554
228
  </div>
555
- </div>
556
- </div>
557
229
 
558
- {/* Search Modal */}
559
- {showSearch && (
560
- <Overlay opacity="medium" glass="medium">
561
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 w-full max-w-lg rounded-lg border p-6 backdrop-blur-sm">
562
- <div className="mb-4 flex items-center justify-between">
563
- <h3 className="text-fm-primary text-lg font-semibold">
564
- Search
565
- </h3>
566
- <button
567
- onClick={() => setShowSearch(false)}
568
- className="text-fm-primary hover:bg-fm-surface-secondary rounded-full p-2"
569
- >
570
- <CrossIcon className="h-4 w-4" />
571
- </button>
572
- </div>
573
- <div className="space-y-4">
574
- <div className="relative">
575
- <SearchIcon className="text-fm-secondary absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
576
- <input
577
- type="text"
578
- placeholder="Search anything..."
579
- className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-primary placeholder:text-fm-tertiary focus:border-fm-divider-primary w-full rounded-lg border py-3 pr-4 pl-10 focus:outline-none"
580
- />
581
- </div>
582
- <div className="space-y-2">
583
- {["Recent searches", "Popular items", "Suggestions"].map(
584
- (item, i) => (
585
- <div
586
- key={i}
587
- className="hover:bg-fm-surface-secondary flex items-center gap-3 rounded-lg p-3"
588
- >
589
- <SearchIcon className="text-fm-secondary h-4 w-4" />
590
- <span className="text-fm-primary">{item}</span>
591
- </div>
592
- )
593
- )}
230
+ {!hasActive && (
231
+ <div className="pointer-events-none absolute inset-x-6 bottom-6 z-20">
232
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary/90 ml-auto max-w-sm rounded-xl border p-4 backdrop-blur-sm">
233
+ <p className="text-fm-primary font-fm-brand text-fm-md leading-fm-md mb-1 font-semibold">
234
+ Overlay Preview
235
+ </p>
236
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-xl">
237
+ Choose opacity, glass, and noise options from the left to
238
+ preview the overlay over this bounded content stage.
239
+ </p>
594
240
  </div>
595
241
  </div>
596
- </div>
597
- </Overlay>
598
- )}
242
+ )}
599
243
 
600
- {/* Settings Modal */}
601
- {showSettings && (
602
- <Overlay opacity="high" glass="low">
603
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 w-full max-w-md rounded-lg border p-6 backdrop-blur-sm">
604
- <div className="mb-4 flex items-center justify-between">
605
- <h3 className="text-fm-primary text-lg font-semibold">
606
- Settings
607
- </h3>
608
- <button
609
- onClick={() => setShowSettings(false)}
610
- className="text-fm-primary hover:bg-fm-surface-secondary rounded-full p-2"
611
- >
612
- <CrossIcon className="h-4 w-4" />
613
- </button>
614
- </div>
615
- <div className="space-y-4">
616
- {[
617
- { label: "Notifications", icon: AlertIcon },
618
- { label: "Privacy", icon: MaintenanceIcon },
619
- { label: "Account", icon: TickCircleIcon },
620
- ].map((item, i) => (
621
- <div
622
- key={i}
623
- className="hover:bg-fm-surface-secondary flex items-center justify-between rounded-lg p-3"
624
- >
625
- <div className="flex items-center gap-3">
626
- <item.icon className="text-fm-secondary h-5 w-5" />
627
- <span className="text-fm-primary">{item.label}</span>
628
- </div>
629
- <ChevronRightIcon className="text-fm-secondary h-4 w-4" />
244
+ {/* ── Active overlay ── */}
245
+ {hasActive && (
246
+ <Overlay
247
+ opacity={activeOpacity ?? "medium"}
248
+ glass={activeGlass ?? "none"}
249
+ noise={activeNoise ?? "none"}
250
+ className="absolute inset-0 z-10"
251
+ classes={{
252
+ wrapper: "absolute inset-0",
253
+ content: "px-6",
254
+ }}
255
+ >
256
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-sm rounded-xl border p-6">
257
+ <p className="text-fm-primary font-fm-brand text-fm-lg leading-fm-lg mb-1 font-semibold">
258
+ Active overlay
259
+ </p>
260
+ <div className="text-fm-secondary font-fm-text text-fm-sm leading-fm-xl space-y-0.5">
261
+ <p>
262
+ Opacity:{" "}
263
+ <span className="text-fm-primary">
264
+ {activeOpacity ?? "medium"}
265
+ </span>
266
+ </p>
267
+ <p>
268
+ Glass:{" "}
269
+ <span className="text-fm-primary">
270
+ {activeGlass ?? "none"}
271
+ </span>
272
+ </p>
273
+ <p>
274
+ Noise:{" "}
275
+ <span className="text-fm-primary">
276
+ {activeNoise ?? "none"}
277
+ </span>
278
+ </p>
630
279
  </div>
631
- ))}
632
- </div>
633
- </div>
634
- </Overlay>
635
- )}
636
-
637
- {/* Confirmation Modal */}
638
- {showConfirm && (
639
- <Overlay opacity="high" glass="high">
640
- <div className="border-fm-divider-negative bg-fm-surface-negative-sec w-full max-w-sm rounded-lg border p-6 backdrop-blur-sm">
641
- <div className="text-center">
642
- <AlertIcon className="text-fm-negative mx-auto mb-4 h-12 w-12" />
643
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
644
- Confirm Action
645
- </h3>
646
- <p className="text-fm-secondary mb-6 text-sm">
647
- This action cannot be undone. Are you sure you want to
648
- continue?
649
- </p>
650
- <div className="flex gap-3">
651
280
  <Button
652
- variant="outline"
653
- size="sm"
654
- onClick={() => setShowConfirm(false)}
655
- className="flex-1"
656
- >
657
- Cancel
658
- </Button>
659
- <Button
660
- variant="secondary"
661
281
  size="sm"
662
- onClick={() => setShowConfirm(false)}
663
- className="flex-1"
282
+ variant="outline"
283
+ onClick={clearAll}
284
+ className="mt-4 w-full"
664
285
  >
665
- Confirm
286
+ Dismiss
666
287
  </Button>
667
288
  </div>
668
- </div>
669
- </div>
670
- </Overlay>
671
- )}
672
- </div>
673
- )
674
- },
675
- parameters: {
676
- docs: {
677
- description: {
678
- story:
679
- "Interactive overlay examples including search modals, settings panels, and confirmation dialogs demonstrating real-world usage patterns.",
680
- },
681
- },
682
- },
683
- }
684
-
685
- // 5. Noise Texture Variants
686
- export const NoiseTextureVariants: Story = {
687
- render: () => {
688
- const [activeNoise, setActiveNoise] = useState<string | null>(null)
689
-
690
- return (
691
- <div className="relative">
692
- <BackgroundContent />
693
-
694
- {/* Control Panel */}
695
- <div className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2 transform">
696
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
697
- <h3 className="text-fm-primary text-center text-sm font-medium">
698
- Noise Texture Variants
699
- </h3>
700
- <div className="flex gap-2">
701
- <Button
702
- size="sm"
703
- variant={activeNoise === "none" ? "primary" : "outline"}
704
- onClick={() =>
705
- setActiveNoise(activeNoise === "none" ? null : "none")
706
- }
707
- >
708
- No Noise
709
- </Button>
710
- <Button
711
- size="sm"
712
- variant={activeNoise === "low" ? "primary" : "outline"}
713
- onClick={() =>
714
- setActiveNoise(activeNoise === "low" ? null : "low")
715
- }
716
- >
717
- Low Noise
718
- </Button>
719
- <Button
720
- size="sm"
721
- variant={activeNoise === "medium" ? "primary" : "outline"}
722
- onClick={() =>
723
- setActiveNoise(activeNoise === "medium" ? null : "medium")
724
- }
725
- >
726
- Medium Noise
727
- </Button>
728
- <Button
729
- size="sm"
730
- variant={activeNoise === "high" ? "primary" : "outline"}
731
- onClick={() =>
732
- setActiveNoise(activeNoise === "high" ? null : "high")
733
- }
734
- >
735
- High Noise
736
- </Button>
737
- </div>
738
- {activeNoise && (
739
- <Button
740
- size="sm"
741
- variant="secondary"
742
- onClick={() => setActiveNoise(null)}
743
- className="w-full"
744
- >
745
- Clear Overlay
746
- </Button>
289
+ </Overlay>
747
290
  )}
748
291
  </div>
749
292
  </div>
750
-
751
- {/* Noise Overlays */}
752
- {activeNoise === "none" && (
753
- <Overlay opacity="medium" glass="low" noise="none">
754
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
755
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
756
- No Noise Texture
757
- </h3>
758
- <p className="text-fm-secondary text-sm">
759
- Clean overlay without any texture patterns
760
- </p>
761
- </div>
762
- </Overlay>
763
- )}
764
-
765
- {activeNoise === "low" && (
766
- <Overlay opacity="medium" glass="low" noise="low">
767
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
768
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
769
- Low Noise Texture
770
- </h3>
771
- <p className="text-fm-secondary text-sm">
772
- Subtle noise pattern for added visual interest
773
- </p>
774
- </div>
775
- </Overlay>
776
- )}
777
-
778
- {activeNoise === "medium" && (
779
- <Overlay opacity="medium" glass="low" noise="medium">
780
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
781
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
782
- Medium Noise Texture
783
- </h3>
784
- <p className="text-fm-secondary text-sm">
785
- Balanced noise pattern for enhanced texture
786
- </p>
787
- </div>
788
- </Overlay>
789
- )}
790
-
791
- {activeNoise === "high" && (
792
- <Overlay opacity="medium" glass="low" noise="high">
793
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
794
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
795
- High Noise Texture
796
- </h3>
797
- <p className="text-fm-secondary text-sm">
798
- Prominent noise pattern for dramatic effect
799
- </p>
800
- </div>
801
- </Overlay>
802
- )}
803
293
  </div>
804
294
  )
805
295
  },
@@ -807,144 +297,282 @@ export const NoiseTextureVariants: Story = {
807
297
  docs: {
808
298
  description: {
809
299
  story:
810
- "Demonstration of noise texture variants that add visual interest and depth to overlays with different intensity levels.",
300
+ "Interactive panel to explore every configuration axis independently: opacity levels (40 % / 60 % / 80 % / solid), glass blur (none → high), and noise texture (none strong). Mix and match any combination to preview the live effect over real backing content.",
811
301
  },
812
302
  },
813
303
  },
814
304
  }
815
305
 
816
- // 6. Combined Effects Showcase
817
- export const CombinedEffectsShowcase: Story = {
818
- render: () => {
819
- const [currentCombo, setCurrentCombo] = useState<string | null>(null)
306
+ // ─── 2. Showcase ──────────────────────────────────────────────────────────────
820
307
 
821
- const combinations = [
308
+ export const Showcase: Story = {
309
+ render: () => {
310
+ /**
311
+ * Each card shows the Overlay rendered inside a bounded relative container
312
+ * (position: relative on the wrapper, not fixed on the overlay) so the
313
+ * gallery fits on-canvas. We override `className` to switch from
314
+ * `fixed inset-0` to `absolute inset-0` for in-card previews.
315
+ */
316
+ const combos = [
822
317
  {
823
- id: "minimal",
824
318
  name: "Minimal",
319
+ meta: "opacity: low · glass: none · noise: none",
825
320
  opacity: "low",
826
321
  glass: "none",
827
322
  noise: "none",
323
+ backing: "bg-fm-surface-primary",
828
324
  },
829
325
  {
830
- id: "subtle",
831
326
  name: "Subtle",
327
+ meta: "opacity: medium · glass: low · noise: low",
832
328
  opacity: "medium",
833
329
  glass: "low",
834
330
  noise: "low",
331
+ backing: "bg-fm-surface-secondary",
332
+ },
333
+ {
334
+ name: "Frosted",
335
+ meta: "opacity: medium · glass: high · noise: none",
336
+ opacity: "medium",
337
+ glass: "high",
338
+ noise: "none",
339
+ backing: "bg-fm-surface-contrast",
835
340
  },
836
341
  {
837
- id: "balanced",
838
342
  name: "Balanced",
343
+ meta: "opacity: medium · glass: medium · noise: medium",
839
344
  opacity: "medium",
840
345
  glass: "medium",
841
346
  noise: "medium",
347
+ backing: "bg-fm-surface-secondary",
842
348
  },
843
349
  {
844
- id: "dramatic",
845
350
  name: "Dramatic",
351
+ meta: "opacity: high · glass: high · noise: low",
846
352
  opacity: "high",
847
353
  glass: "high",
848
354
  noise: "low",
355
+ backing: "bg-fm-surface-primary",
849
356
  },
850
357
  {
851
- id: "premium",
852
- name: "Premium",
358
+ name: "Textured",
359
+ meta: "opacity: high · glass: medium · noise: high",
853
360
  opacity: "high",
854
- glass: "high",
361
+ glass: "medium",
855
362
  noise: "high",
363
+ backing: "bg-fm-surface-contrast",
856
364
  },
857
365
  ] as const
858
366
 
859
367
  return (
860
- <div className="relative">
861
- <BackgroundContent />
862
-
863
- {/* Control Panel */}
864
- <div className="fixed top-1/2 left-4 z-50 -translate-y-1/2 transform">
865
- <div className="bg-fm-surface-primary border-fm-divider-secondary space-y-2 rounded-lg border p-4 backdrop-blur-sm">
866
- <h3 className="text-fm-primary text-sm font-medium">
867
- Effect Combinations
868
- </h3>
869
- <div className="space-y-2">
870
- {combinations.map((combo) => (
871
- <Button
872
- key={combo.id}
873
- size="sm"
874
- variant={currentCombo === combo.id ? "primary" : "outline"}
875
- onClick={() =>
876
- setCurrentCombo(currentCombo === combo.id ? null : combo.id)
877
- }
878
- className="w-full justify-start"
368
+ <div className="w-full p-8">
369
+ <div className="mx-auto max-w-4xl space-y-10">
370
+ <div className="grid grid-cols-2 gap-6 sm:grid-cols-3">
371
+ {combos.map((combo) => (
372
+ <div key={combo.name} className="group space-y-3">
373
+ {/* Bounded preview card */}
374
+ <div
375
+ className={`relative h-40 w-full overflow-hidden rounded-xl ${combo.backing}`}
879
376
  >
880
- {combo.name}
881
- </Button>
882
- ))}
883
- </div>
884
- {currentCombo && (
885
- <Button
886
- size="sm"
887
- variant="secondary"
888
- onClick={() => setCurrentCombo(null)}
889
- className="w-full"
890
- >
891
- Clear Overlay
892
- </Button>
893
- )}
894
- </div>
895
- </div>
377
+ {/* Mock content in the "background" */}
378
+ <div className="absolute inset-0 flex flex-col gap-2 p-4">
379
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary h-6 w-3/4 rounded" />
380
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary h-4 w-full rounded" />
381
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary h-4 w-5/6 rounded" />
382
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary mt-auto flex gap-2">
383
+ <div className="h-7 w-16 rounded" />
384
+ <div className="h-7 w-20 rounded" />
385
+ </div>
386
+ </div>
896
387
 
897
- {/* Effect Information Panel */}
898
- {currentCombo && (
899
- <div className="fixed top-1/2 right-4 z-50 -translate-y-1/2 transform">
900
- <div className="bg-fm-surface-primary border-fm-divider-secondary max-w-xs space-y-2 rounded-lg border p-4 backdrop-blur-sm">
901
- <h4 className="text-fm-primary text-sm font-medium">
902
- {combinations.find((c) => c.id === currentCombo)?.name} Effect
903
- </h4>
904
- <div className="text-fm-secondary space-y-1 text-xs">
905
- <div>
906
- Opacity:{" "}
907
- {combinations.find((c) => c.id === currentCombo)?.opacity}
908
- </div>
909
- <div>
910
- Glass:{" "}
911
- {combinations.find((c) => c.id === currentCombo)?.glass}
388
+ {/* Overlay absolute instead of fixed for bounded card display */}
389
+ <Overlay
390
+ opacity={combo.opacity}
391
+ glass={combo.glass}
392
+ noise={combo.noise}
393
+ className="absolute inset-0 z-10"
394
+ />
912
395
  </div>
913
- <div>
914
- Noise:{" "}
915
- {combinations.find((c) => c.id === currentCombo)?.noise}
396
+
397
+ {/* Item metadata */}
398
+ <div className="space-y-0.5 text-center">
399
+ <p className="text-fm-primary font-fm-brand text-fm-md leading-fm-md font-semibold">
400
+ {combo.name}
401
+ </p>
402
+ <p className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
403
+ {combo.meta}
404
+ </p>
916
405
  </div>
917
406
  </div>
918
- </div>
407
+ ))}
919
408
  </div>
920
- )}
921
409
 
922
- {/* Combined Effect Overlays */}
923
- {combinations.map(
924
- (combo) =>
925
- currentCombo === combo.id && (
926
- <Overlay
927
- key={combo.id}
928
- opacity={combo.opacity}
929
- glass={combo.glass}
930
- noise={combo.noise}
931
- >
932
- <div className="border-fm-divider-secondary bg-fm-surface-secondary/80 max-w-md rounded-lg border p-6 backdrop-blur-sm">
933
- <h3 className="text-fm-primary mb-2 text-lg font-semibold">
934
- {combo.name} Overlay
935
- </h3>
936
- <p className="text-fm-secondary mb-4 text-sm">
937
- Combination of {combo.opacity} opacity, {combo.glass} glass
938
- effect, and {combo.noise} noise texture
939
- </p>
940
- <p className="text-fm-tertiary text-sm">
941
- This combination creates a {combo.name.toLowerCase()}{" "}
942
- overlay effect suitable for different design contexts and
943
- user interface needs.
410
+ {/* Info box */}
411
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
412
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
413
+ Each card above uses a design-token background (
414
+ <code className="text-fm-primary font-(--font-fm-mono)">
415
+ bg-fm-surface-primary
416
+ </code>
417
+ ,{" "}
418
+ <code className="text-fm-primary font-(--font-fm-mono)">
419
+ bg-fm-surface-secondary
420
+ </code>
421
+ ,{" "}
422
+ <code className="text-fm-primary font-(--font-fm-mono)">
423
+ bg-fm-surface-contrast
424
+ </code>
425
+ ) no hardcoded gradient colors. The{" "}
426
+ <code className="text-fm-primary font-(--font-fm-mono)">
427
+ className
428
+ </code>{" "}
429
+ prop switches the overlay from{" "}
430
+ <code className="text-fm-primary">fixed</code> to{" "}
431
+ <code className="text-fm-primary">absolute</code> for bounded
432
+ in-card previews.
433
+ </p>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ )
438
+ },
439
+ parameters: {
440
+ layout: "fullscreen",
441
+ docs: {
442
+ description: {
443
+ story:
444
+ "Curated gallery of six overlay combinations — from minimal (low opacity, no glass) to textured (high opacity, medium glass, strong noise). Each card uses design-token backgrounds (bg-fm-surface-primary, bg-fm-surface-secondary, bg-fm-surface-contrast) with no hardcoded Tailwind gradient colors.",
445
+ },
446
+ },
447
+ },
448
+ }
449
+
450
+ // ─── 3. Interactive ───────────────────────────────────────────────────────────
451
+
452
+ export const Interactive: Story = {
453
+ render: () => {
454
+ const [showSearch, setShowSearch] = useState(false)
455
+ const [showConfirm, setShowConfirm] = useState(false)
456
+ const [searchQuery, setSearchQuery] = useState("")
457
+
458
+ return (
459
+ <div className="relative">
460
+ <BackingContent />
461
+
462
+ {/* Trigger buttons */}
463
+ <div className="fixed top-4 left-1/2 z-50 -translate-x-1/2">
464
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary flex gap-3 rounded-xl border p-4">
465
+ <Button size="sm" onClick={() => setShowSearch(true)}>
466
+ Search
467
+ </Button>
468
+ <Button
469
+ size="sm"
470
+ variant="secondary"
471
+ onClick={() => setShowConfirm(true)}
472
+ >
473
+ Confirm action
474
+ </Button>
475
+ </div>
476
+ </div>
477
+
478
+ {/* Search modal */}
479
+ {showSearch && (
480
+ <Overlay opacity="medium" glass="medium" noise="none">
481
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary w-full max-w-lg rounded-xl border p-6">
482
+ <div className="mb-4 flex items-center justify-between">
483
+ <p className="text-fm-primary font-fm-brand text-fm-lg leading-fm-lg font-semibold">
484
+ Search
485
+ </p>
486
+ <button
487
+ onClick={() => {
488
+ setShowSearch(false)
489
+ setSearchQuery("")
490
+ }}
491
+ className="text-fm-secondary hover:text-fm-primary rounded-full p-1 transition-colors"
492
+ aria-label="Close search"
493
+ >
494
+ <CrossIcon className="h-4 w-4" />
495
+ </button>
496
+ </div>
497
+
498
+ <div className="relative mb-4">
499
+ <SearchIcon className="text-fm-tertiary absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
500
+ <input
501
+ type="text"
502
+ placeholder="Search artists, albums, tracks…"
503
+ value={searchQuery}
504
+ onChange={(e) => setSearchQuery(e.target.value)}
505
+ className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-primary placeholder:text-fm-tertiary focus:border-fm-divider-primary font-fm-text text-fm-sm leading-fm-sm w-full rounded-lg border py-3 pr-4 pl-10 focus:outline-none"
506
+ autoFocus
507
+ />
508
+ </div>
509
+
510
+ {searchQuery.length === 0 && (
511
+ <div className="space-y-1">
512
+ <p className="text-fm-secondary font-fm-brand text-fm-sm leading-fm-sm mb-2 font-semibold tracking-widest uppercase">
513
+ Recent
944
514
  </p>
515
+ {["Bicep", "Four Tet", "Floating Points"].map((item) => (
516
+ <button
517
+ key={item}
518
+ onClick={() => setSearchQuery(item)}
519
+ className="hover:bg-fm-surface-tertiary flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors"
520
+ >
521
+ <SearchIcon className="text-fm-tertiary h-4 w-4 shrink-0" />
522
+ <span className="text-fm-primary font-fm-text text-fm-sm leading-fm-sm">
523
+ {item}
524
+ </span>
525
+ </button>
526
+ ))}
945
527
  </div>
946
- </Overlay>
947
- )
528
+ )}
529
+
530
+ {searchQuery.length > 0 && (
531
+ <p className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
532
+ Showing results for{" "}
533
+ <span className="text-fm-primary">"{searchQuery}"</span>…
534
+ </p>
535
+ )}
536
+ </div>
537
+ </Overlay>
538
+ )}
539
+
540
+ {/* Confirmation dialog */}
541
+ {showConfirm && (
542
+ <Overlay opacity="high" glass="high" noise="none">
543
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-sm rounded-xl border p-6 text-center">
544
+ <div className="border-fm-divider-secondary bg-fm-surface-tertiary mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full">
545
+ <span className="text-fm-primary font-fm-brand text-fm-lg leading-fm-lg font-bold">
546
+ !
547
+ </span>
548
+ </div>
549
+ <p className="text-fm-primary font-fm-brand text-fm-lg leading-fm-lg mb-2 font-semibold">
550
+ Remove from library?
551
+ </p>
552
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-xl mb-6">
553
+ This will remove the album from your library. You can always add
554
+ it back later.
555
+ </p>
556
+ <div className="flex gap-3">
557
+ <Button
558
+ variant="outline"
559
+ size="sm"
560
+ onClick={() => setShowConfirm(false)}
561
+ className="flex-1"
562
+ >
563
+ Cancel
564
+ </Button>
565
+ <Button
566
+ variant="secondary"
567
+ size="sm"
568
+ onClick={() => setShowConfirm(false)}
569
+ className="flex-1"
570
+ >
571
+ Remove
572
+ </Button>
573
+ </div>
574
+ </div>
575
+ </Overlay>
948
576
  )}
949
577
  </div>
950
578
  )
@@ -953,7 +581,7 @@ export const CombinedEffectsShowcase: Story = {
953
581
  docs: {
954
582
  description: {
955
583
  story:
956
- "Curated combinations of opacity, glass, and noise effects showing different aesthetic approaches from minimal to premium styling.",
584
+ "Live interaction examples: a search modal (medium opacity, medium glass) and a confirmation dialog (high opacity, high glass). Demonstrates real-world overlay usage with controlled open/close state and accessible focus management.",
957
585
  },
958
586
  },
959
587
  },