aural-ui 4.0.1 → 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 (174) hide show
  1. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
  2. package/dist/components/avatar/Avatar.stories.tsx +219 -235
  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/card/Card.stories.tsx +619 -301
  8. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  9. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  10. package/dist/components/chip/Chip.stories.tsx +362 -168
  11. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
  12. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  13. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  14. package/dist/components/command/Command.stories.tsx +530 -867
  15. package/dist/components/dialog/Dialog.stories.tsx +501 -950
  16. package/dist/components/divider/Divider.stories.tsx +264 -527
  17. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  18. package/dist/components/drawer/Drawer.stories.tsx +659 -1023
  19. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
  20. package/dist/components/form/Form.stories.tsx +560 -274
  21. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  22. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
  23. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  24. package/dist/components/if-else/if-else.stories.tsx +370 -83
  25. package/dist/components/input/Input.stories.tsx +436 -368
  26. package/dist/components/label/Label.stories.tsx +156 -154
  27. package/dist/components/list/List.stories.tsx +484 -835
  28. package/dist/components/marquee/Marquee.stories.tsx +356 -712
  29. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
  30. package/dist/components/overlay/Overlay.stories.tsx +452 -824
  31. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  32. package/dist/components/popover/Popover.stories.tsx +481 -896
  33. package/dist/components/radio/Radio.stories.tsx +432 -124
  34. package/dist/components/resizable/Resizable.stories.tsx +495 -799
  35. package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
  36. package/dist/components/search/Search.stories.tsx +312 -595
  37. package/dist/components/select/Select.stories.tsx +684 -789
  38. package/dist/components/sheet/Sheet.stories.tsx +671 -950
  39. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  40. package/dist/components/slider/Slider.stories.tsx +383 -760
  41. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  42. package/dist/components/switch/Switch.stories.tsx +461 -208
  43. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  44. package/dist/components/table/Table.stories.tsx +770 -916
  45. package/dist/components/tabs/Tabs.stories.tsx +458 -1455
  46. package/dist/components/tag/Tag.stories.tsx +714 -542
  47. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  48. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
  49. package/dist/components/toast/Toast.stories.tsx +452 -1339
  50. package/dist/components/toggle/Toggle.stories.tsx +488 -931
  51. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
  52. package/dist/components/typography/Typography.stories.tsx +406 -89
  53. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  54. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  55. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  56. package/dist/icons/Icons.stories.tsx +0 -12
  57. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
  58. package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
  59. package/dist/icons/all-icons.tsx +37 -16
  60. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
  61. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
  62. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
  63. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
  64. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
  65. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
  66. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
  67. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
  68. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
  69. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
  70. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
  71. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
  72. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
  73. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
  74. package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
  75. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
  76. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
  77. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
  78. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
  79. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
  80. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
  81. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
  82. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
  83. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
  84. package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
  85. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
  86. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
  87. package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
  88. package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
  89. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
  90. package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
  91. package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
  92. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
  93. package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
  94. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
  95. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
  96. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
  97. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
  98. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
  99. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
  100. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
  101. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
  102. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
  103. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
  104. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
  105. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
  106. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
  107. package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
  108. package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
  109. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
  110. package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
  111. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
  112. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
  113. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
  114. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
  115. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
  116. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
  117. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
  118. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
  119. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
  120. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
  121. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
  122. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
  123. package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
  124. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
  125. package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
  126. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
  127. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
  128. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
  129. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
  130. package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
  131. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
  132. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
  133. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
  134. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
  135. package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
  136. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
  137. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
  138. package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
  139. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
  140. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
  141. package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
  142. package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
  143. package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
  144. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
  145. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
  146. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
  147. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
  148. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
  149. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
  150. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
  151. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
  152. package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
  153. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
  154. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
  155. package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
  156. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
  157. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
  158. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
  159. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
  160. package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
  161. package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
  162. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
  163. package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
  164. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
  165. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
  166. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
  167. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
  168. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
  169. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
  170. package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
  171. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
  172. package/dist/index.cjs +1 -1
  173. package/dist/index.js +1 -1
  174. package/package.json +1 -1
@@ -1,25 +1,40 @@
1
1
  import React, { useState } from "react"
2
2
  import type { Meta, StoryObj } from "@storybook/react-vite"
3
3
 
4
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
5
+
4
6
  import TextArea from "."
5
7
 
8
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
9
+
6
10
  const meta: Meta<typeof TextArea> = {
7
11
  title: "Components/UI/TextArea",
8
12
  component: TextArea,
9
13
  parameters: {
10
14
  layout: "centered",
11
- backgrounds: {
12
- default: "dark",
13
- values: [
14
- { name: "dark", value: "#0a0a0a" },
15
- { name: "light", value: "#ffffff" },
16
- ],
17
- },
18
15
  docs: {
19
16
  description: {
20
17
  component:
21
- "A flexible textarea component built with atomic design principles. Supports multiple decoration styles (underline, outline, filled), auto-growing height, character counting, and comprehensive accessibility features. Can be used as a complete component or composed from individual atomic parts.",
18
+ "A flexible textarea built with atomic design principles. Supports three decoration styles (underline, outline, filled), four validation variants, auto-growing height, character counting, and full ARIA accessibility. Can be used as a single convenience component or composed from individual atomic parts.",
22
19
  },
20
+ page: () => (
21
+ <AuralComponentDocsPage
22
+ features={[
23
+ {
24
+ title: "4 Validation States",
25
+ description: "Default to success",
26
+ },
27
+ {
28
+ title: "Auto-growing Height",
29
+ description: "Expands with content",
30
+ },
31
+ {
32
+ title: "3 Decoration Styles",
33
+ description: "Underline, outline, filled",
34
+ },
35
+ ]}
36
+ />
37
+ ),
23
38
  },
24
39
  },
25
40
  tags: ["autodocs"],
@@ -27,704 +42,748 @@ const meta: Meta<typeof TextArea> = {
27
42
  label: {
28
43
  control: { type: "text" },
29
44
  description: "Label text for the textarea",
30
- table: {
31
- type: { summary: "ReactNode" },
32
- defaultValue: { summary: "undefined" },
33
- },
34
45
  },
35
46
  placeholder: {
36
47
  control: { type: "text" },
37
- description: "Placeholder text shown when textarea is empty",
38
- table: {
39
- type: { summary: "string" },
40
- defaultValue: { summary: '""' },
41
- },
48
+ description: "Placeholder shown when the textarea is empty",
42
49
  },
43
50
  helperText: {
44
51
  control: { type: "text" },
45
52
  description: "Helper text displayed below the textarea",
46
- table: {
47
- type: { summary: "ReactNode" },
48
- defaultValue: { summary: "undefined" },
49
- },
50
53
  },
51
54
  variant: {
52
55
  control: { type: "select" },
53
56
  options: ["default", "error", "warning", "success"],
54
- description: "Visual variant affecting border and helper text colors",
55
- table: {
56
- type: { summary: '"default" | "error" | "warning" | "success"' },
57
- defaultValue: { summary: '"default"' },
58
- },
57
+ description: "Visual validation state",
59
58
  },
60
59
  decoration: {
61
60
  control: { type: "select" },
62
61
  options: ["underline", "outline", "filled"],
63
- description: "Visual style of the textarea border and background",
64
- table: {
65
- type: { summary: '"underline" | "outline" | "filled"' },
66
- defaultValue: { summary: '"filled"' },
67
- },
62
+ description: "Border and background style",
68
63
  },
69
64
  disabled: {
70
65
  control: { type: "boolean" },
71
- description: "Whether the textarea is disabled and non-interactive",
72
- table: {
73
- type: { summary: "boolean" },
74
- defaultValue: { summary: "false" },
75
- },
66
+ description: "Disables the textarea",
76
67
  },
77
68
  required: {
78
69
  control: { type: "boolean" },
79
- description: "Whether the textarea is required (adds asterisk to label)",
80
- table: {
81
- type: { summary: "boolean" },
82
- defaultValue: { summary: "false" },
83
- },
70
+ description: "Adds asterisk to label and aria-required",
84
71
  },
85
72
  fullWidth: {
86
73
  control: { type: "boolean" },
87
- description: "Whether the textarea takes full width of its container",
88
- table: {
89
- type: { summary: "boolean" },
90
- defaultValue: { summary: "false" },
91
- },
74
+ description: "Expands the textarea to fill its container",
92
75
  },
93
76
  showCharCount: {
94
77
  control: { type: "boolean" },
95
- description: "Whether to show character count (requires maxLength)",
96
- table: {
97
- type: { summary: "boolean" },
98
- defaultValue: { summary: "false" },
99
- },
100
- },
101
- value: {
102
- control: { type: "text" },
103
- description: "Controlled value of the textarea",
104
- table: {
105
- type: { summary: "string" },
106
- defaultValue: { summary: "undefined" },
107
- },
78
+ description: "Shows remaining character count (requires maxLength)",
108
79
  },
109
80
  maxLength: {
110
81
  control: { type: "number" },
111
82
  description: "Maximum number of characters allowed",
112
- table: {
113
- type: { summary: "number" },
114
- defaultValue: { summary: "undefined" },
115
- },
116
83
  },
117
84
  rows: {
118
85
  control: { type: "number", min: 1, max: 10 },
119
- description: "Number of visible rows when autoGrow is false",
120
- table: {
121
- type: { summary: "number" },
122
- defaultValue: { summary: "3" },
123
- },
86
+ description: "Visible rows when autoGrow is false",
124
87
  },
125
88
  autoGrow: {
126
89
  control: { type: "boolean" },
127
- description:
128
- "Whether the textarea should automatically grow/shrink based on content",
129
- table: {
130
- type: { summary: "boolean" },
131
- defaultValue: { summary: "true" },
132
- },
90
+ description: "Textarea grows/shrinks automatically with content",
133
91
  },
134
92
  minHeight: {
135
93
  control: { type: "number" },
136
- description: "Minimum height in pixels (only with autoGrow)",
137
- table: {
138
- type: { summary: "number" },
139
- defaultValue: { summary: "undefined" },
140
- },
94
+ description: "Minimum height in pixels (autoGrow only)",
141
95
  },
142
96
  maxHeight: {
143
97
  control: { type: "number" },
144
- description: "Maximum height in pixels (only with autoGrow)",
145
- table: {
146
- type: { summary: "number" },
147
- defaultValue: { summary: "undefined" },
148
- },
149
- },
150
- onChange: {
151
- action: "changed",
152
- description: "Callback fired when the value changes",
153
- table: {
154
- type: {
155
- summary: "(e: React.ChangeEvent<HTMLTextAreaElement>) => void",
156
- },
157
- },
158
- },
159
- onFocus: {
160
- action: "focused",
161
- description: "Callback fired when the textarea receives focus",
162
- table: {
163
- type: { summary: "(e: React.FocusEvent<HTMLTextAreaElement>) => void" },
164
- },
165
- },
166
- onBlur: {
167
- action: "blurred",
168
- description: "Callback fired when the textarea loses focus",
169
- table: {
170
- type: { summary: "(e: React.FocusEvent<HTMLTextAreaElement>) => void" },
171
- },
172
- },
173
- unstyled: {
174
- control: { type: "boolean" },
175
- description: "Whether to remove all default styling",
176
- table: {
177
- type: { summary: "boolean" },
178
- defaultValue: { summary: "false" },
179
- },
180
- },
181
- classes: {
182
- control: { type: "object" },
183
- description: "Override classes for different parts of the component",
184
- table: {
185
- type: {
186
- summary:
187
- "{ root?: string; label?: string; wrapper?: string; textarea?: string; helperText?: string; charCount?: string }",
188
- },
189
- defaultValue: { summary: "{}" },
190
- },
98
+ description: "Maximum height in pixels (autoGrow only)",
191
99
  },
192
100
  },
193
- }
194
-
195
- export default meta
196
- type Story = StoryObj<typeof meta>
197
-
198
- // Default story
199
- export const Default: Story = {
200
101
  args: {
201
- label: "Message",
202
- placeholder: "Enter your message here...",
102
+ variant: "default",
203
103
  decoration: "filled",
204
- fullWidth: true,
104
+ disabled: false,
105
+ required: false,
106
+ fullWidth: false,
107
+ showCharCount: false,
108
+ autoGrow: true,
109
+ label: "Label",
110
+ placeholder: "Enter text…",
205
111
  },
206
112
  }
207
113
 
208
- // Decoration variants
209
- export const DecorationVariants: Story = {
210
- render: () => (
211
- <div className="max-w-lg space-y-6">
212
- <div>
213
- <h3 className="text-fm-primary mb-2 text-sm font-medium">Underline</h3>
214
- <TextArea
215
- label="Underline Style"
216
- placeholder="Minimalist underline style..."
217
- decoration="underline"
218
- fullWidth
219
- />
220
- </div>
114
+ export default meta
115
+ type Story = StoryObj<typeof meta>
221
116
 
222
- <div>
223
- <h3 className="text-fm-primary mb-2 text-sm font-medium">Outline</h3>
224
- <TextArea
225
- label="Outline Style"
226
- placeholder="Traditional outlined style..."
227
- decoration="outline"
228
- fullWidth
229
- />
230
- </div>
117
+ // ─── 1. Playground ───────────────────────────────────────────────────────────
231
118
 
232
- <div>
233
- <h3 className="text-fm-primary mb-2 text-sm font-medium">Filled</h3>
234
- <TextArea
235
- label="Filled Style"
236
- placeholder="Modern filled style..."
237
- decoration="filled"
238
- fullWidth
239
- />
119
+ export const Playground: Story = {
120
+ parameters: {
121
+ docs: {
122
+ description: {
123
+ story:
124
+ "Use the Storybook controls panel to configure every prop. The textarea below reflects your selections live.",
125
+ },
126
+ },
127
+ },
128
+ render: (args) => (
129
+ <div className="w-96 space-y-4">
130
+ <TextArea {...args} fullWidth />
131
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
132
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
133
+ {`<TextArea variant="${args.variant}" decoration="${args.decoration}"${args.disabled ? " disabled" : ""}${args.required ? " required" : ""}${args.showCharCount ? " showCharCount" : ""} />`}
134
+ </code>
240
135
  </div>
241
136
  </div>
242
137
  ),
138
+ }
139
+
140
+ // ─── 2. AllVariants ──────────────────────────────────────────────────────────
141
+
142
+ export const AllVariants: Story = {
243
143
  parameters: {
244
144
  docs: {
245
145
  description: {
246
146
  story:
247
- "Three decoration styles available: underline (minimalist), outline (traditional), and filled (modern with background).",
147
+ "Full matrix of all four validation variants (default, error, warning, success) across all three decoration styles (underline, outline, filled).",
248
148
  },
249
149
  },
250
150
  },
251
- }
151
+ render: () => {
152
+ const variants = [
153
+ {
154
+ variant: "default" as const,
155
+ label: "Default",
156
+ helperText: "Helper text",
157
+ },
158
+ {
159
+ variant: "error" as const,
160
+ label: "Error",
161
+ helperText: "Something went wrong",
162
+ },
163
+ {
164
+ variant: "warning" as const,
165
+ label: "Warning",
166
+ helperText: "Double-check this value",
167
+ },
168
+ {
169
+ variant: "success" as const,
170
+ label: "Success",
171
+ helperText: "Looks great!",
172
+ },
173
+ ]
174
+ const decorations = [
175
+ { decoration: "underline" as const, label: "Underline" },
176
+ { decoration: "outline" as const, label: "Outline" },
177
+ { decoration: "filled" as const, label: "Filled" },
178
+ ]
252
179
 
253
- // Controlled component story
254
- export const Controlled: Story = {
255
- render: (args) => {
256
- const [value, setValue] = useState("")
257
180
  return (
258
- <TextArea
259
- {...args}
260
- value={value}
261
- onChange={(e) => setValue(e.target.value)}
262
- />
181
+ <div className="space-y-10">
182
+ {decorations.map(({ decoration, label: decLabel }) => (
183
+ <div key={decoration}>
184
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
185
+ {decLabel}
186
+ </h4>
187
+ <div className="flex flex-wrap gap-6">
188
+ {variants.map(({ variant, label, helperText }) => (
189
+ <div key={variant} className="w-52 space-y-2 text-center">
190
+ <TextArea
191
+ label={label}
192
+ placeholder={`${label} state…`}
193
+ variant={variant}
194
+ decoration={decoration}
195
+ helperText={helperText}
196
+ fullWidth
197
+ />
198
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
199
+ {label}
200
+ </p>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ ))}
206
+ </div>
263
207
  )
264
208
  },
265
- args: {
266
- label: "Controlled TextArea",
267
- placeholder: "Type something...",
268
- decoration: "filled",
269
- fullWidth: true,
270
- },
271
- }
272
-
273
- // With helper text
274
- export const WithHelperText: Story = {
275
- args: {
276
- label: "Description",
277
- placeholder: "Enter a description...",
278
- helperText: "Please provide a detailed description",
279
- decoration: "outline",
280
- fullWidth: true,
281
- },
282
209
  }
283
210
 
284
- // With character count
285
- export const WithCharCount: Story = {
286
- args: {
287
- label: "Comment",
288
- placeholder: "Share your thoughts...",
289
- maxLength: 280,
290
- showCharCount: true,
291
- decoration: "filled",
292
- fullWidth: true,
293
- },
294
- }
211
+ // ─── 3. Configurations ───────────────────────────────────────────────────────
295
212
 
296
- // Error state with different decorations
297
- export const ErrorStates: Story = {
298
- render: () => (
299
- <div className="max-w-lg space-y-4">
300
- <TextArea
301
- label="Underline Error"
302
- placeholder="Error with underline..."
303
- variant="error"
304
- decoration="underline"
305
- helperText="This field is required"
306
- fullWidth
307
- />
308
- <TextArea
309
- label="Outline Error"
310
- placeholder="Error with outline..."
311
- variant="error"
312
- decoration="outline"
313
- helperText="This field is required"
314
- fullWidth
315
- />
316
- <TextArea
317
- label="Filled Error"
318
- placeholder="Error with filled..."
319
- variant="error"
320
- decoration="filled"
321
- helperText="This field is required"
322
- fullWidth
323
- />
324
- </div>
325
- ),
213
+ export const Configurations: Story = {
326
214
  parameters: {
327
215
  docs: {
328
216
  description: {
329
- story: "Error state examples across all decoration variants.",
217
+ story:
218
+ "Non-variant configuration axes: character count with maxLength, fixed height with autoGrow disabled, height constraints (minHeight + maxHeight), and required field indicator.",
330
219
  },
331
220
  },
332
221
  },
333
- }
222
+ render: () => {
223
+ const [tweetValue, setTweetValue] = useState("")
224
+ const [bioValue, setBioValue] = useState("")
334
225
 
335
- // Warning state
336
- export const WarningState: Story = {
337
- args: {
338
- label: "Message",
339
- placeholder: "Enter your message...",
340
- variant: "warning",
341
- decoration: "outline",
342
- helperText: "Please double-check your input",
343
- fullWidth: true,
344
- },
345
- }
226
+ return (
227
+ <div className="w-full max-w-sm space-y-10">
228
+ {/* Character count */}
229
+ <div>
230
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
231
+ Character Count
232
+ </h4>
233
+ <div className="space-y-2 text-center">
234
+ <TextArea
235
+ label="Tweet"
236
+ placeholder="What's happening?"
237
+ value={tweetValue}
238
+ onChange={(e) => setTweetValue(e.target.value)}
239
+ maxLength={280}
240
+ showCharCount
241
+ decoration="underline"
242
+ fullWidth
243
+ />
244
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
245
+ showCharCount + maxLength={280}
246
+ </p>
247
+ </div>
248
+ </div>
346
249
 
347
- // Success state
348
- export const SuccessState: Story = {
349
- args: {
350
- label: "Message",
351
- placeholder: "Enter your message...",
352
- variant: "success",
353
- decoration: "filled",
354
- helperText: "Looks good!",
355
- fullWidth: true,
356
- },
357
- }
250
+ {/* Fixed height */}
251
+ <div>
252
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
253
+ Fixed Height
254
+ </h4>
255
+ <div className="space-y-2 text-center">
256
+ <TextArea
257
+ label="Notes"
258
+ placeholder="Type your notes here…"
259
+ autoGrow={false}
260
+ rows={5}
261
+ decoration="outline"
262
+ fullWidth
263
+ />
264
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
265
+ autoGrow=false + rows={5}
266
+ </p>
267
+ </div>
268
+ </div>
358
269
 
359
- // Disabled state
360
- export const Disabled: Story = {
361
- args: {
362
- label: "Message",
363
- placeholder: "Enter your message...",
364
- disabled: true,
365
- value: "This textarea is disabled",
366
- decoration: "filled",
367
- fullWidth: true,
368
- },
369
- }
270
+ {/* Height constraints */}
271
+ <div>
272
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
273
+ Height Constraints
274
+ </h4>
275
+ <div className="space-y-2 text-center">
276
+ <TextArea
277
+ label="Bio"
278
+ placeholder="Tell us about yourself (grows up to 200 px)…"
279
+ value={bioValue}
280
+ onChange={(e) => setBioValue(e.target.value)}
281
+ autoGrow
282
+ minHeight={60}
283
+ maxHeight={200}
284
+ decoration="filled"
285
+ fullWidth
286
+ />
287
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
288
+ autoGrow + minHeight=60 + maxHeight=200
289
+ </p>
290
+ </div>
291
+ </div>
370
292
 
371
- // Required field
372
- export const Required: Story = {
373
- args: {
374
- label: "Required Message",
375
- placeholder: "This field is required...",
376
- required: true,
377
- helperText: "This field is required",
378
- decoration: "outline",
379
- fullWidth: true,
293
+ {/* Required */}
294
+ <div>
295
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
296
+ Required
297
+ </h4>
298
+ <div className="space-y-2 text-center">
299
+ <TextArea
300
+ label="Feedback"
301
+ placeholder="Your feedback is required…"
302
+ required
303
+ helperText="This field is required"
304
+ decoration="outline"
305
+ fullWidth
306
+ />
307
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
308
+ required — asterisk added to label
309
+ </p>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ )
380
314
  },
381
315
  }
382
316
 
383
- // With auto-grow disabled
384
- export const FixedHeight: Story = {
385
- args: {
386
- label: "Fixed Height TextArea",
387
- placeholder: "This textarea has a fixed height...",
388
- autoGrow: false,
389
- rows: 5,
390
- decoration: "outline",
391
- fullWidth: true,
392
- },
393
- }
317
+ // ─── 4. States ───────────────────────────────────────────────────────────────
394
318
 
395
- // With min and max height
396
- export const WithHeightLimits: Story = {
397
- args: {
398
- label: "Height Limited TextArea",
399
- placeholder: "This textarea has height constraints...",
400
- autoGrow: true,
401
- minHeight: 80,
402
- maxHeight: 200,
403
- decoration: "filled",
404
- fullWidth: true,
319
+ export const States: Story = {
320
+ parameters: {
321
+ docs: {
322
+ description: {
323
+ story:
324
+ "All five textarea states side by side: default (idle), disabled (non-interactive with pre-filled content), error, warning, and success.",
325
+ },
326
+ },
405
327
  },
328
+ render: () => (
329
+ <div className="w-full max-w-sm space-y-8">
330
+ {[
331
+ {
332
+ label: "Default",
333
+ props: {
334
+ label: "Default",
335
+ placeholder: "Enter your message…",
336
+ variant: "default" as const,
337
+ helperText: "Helper text",
338
+ },
339
+ },
340
+ {
341
+ label: "Disabled",
342
+ props: {
343
+ label: "Disabled",
344
+ value: "This textarea cannot be edited",
345
+ variant: "default" as const,
346
+ disabled: true,
347
+ helperText: "Read-only content",
348
+ },
349
+ },
350
+ {
351
+ label: "Error",
352
+ props: {
353
+ label: "Error",
354
+ placeholder: "Enter your message…",
355
+ variant: "error" as const,
356
+ helperText: "This field is required",
357
+ },
358
+ },
359
+ {
360
+ label: "Warning",
361
+ props: {
362
+ label: "Warning",
363
+ value: "Short",
364
+ variant: "warning" as const,
365
+ helperText: "Please provide more detail",
366
+ },
367
+ },
368
+ {
369
+ label: "Success",
370
+ props: {
371
+ label: "Success",
372
+ value:
373
+ "This is a detailed and valid message that meets all requirements.",
374
+ variant: "success" as const,
375
+ helperText: "Looks great!",
376
+ },
377
+ },
378
+ ].map(({ label, props }) => (
379
+ <div key={label}>
380
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
381
+ {label}
382
+ </h4>
383
+ <TextArea decoration="outline" fullWidth {...props} />
384
+ </div>
385
+ ))}
386
+ </div>
387
+ ),
406
388
  }
407
389
 
408
- // With character limit and warning
409
- export const CharacterLimitWithWarning: Story = {
410
- render: (args) => {
411
- const [value, setValue] = useState("")
412
- const isNearLimit = value.length > 250
413
- const isOverHalfway = value.length > 140
390
+ // ─── 5. Interactive ──────────────────────────────────────────────────────────
414
391
 
415
- return (
416
- <TextArea
417
- {...args}
418
- value={value}
419
- onChange={(e) => setValue(e.target.value)}
420
- variant={
421
- isNearLimit ? "warning" : isOverHalfway ? "default" : "default"
422
- }
423
- helperText={
424
- isNearLimit
425
- ? "You're approaching the character limit"
426
- : "Share what's on your mind"
427
- }
428
- />
429
- )
430
- },
431
- args: {
432
- label: "Tweet",
433
- placeholder: "What's happening?",
434
- maxLength: 280,
435
- showCharCount: true,
436
- decoration: "underline",
437
- fullWidth: true,
392
+ export const Interactive: Story = {
393
+ parameters: {
394
+ docs: {
395
+ description: {
396
+ story:
397
+ "Live textarea demo with real-time character counting, dynamic variant changes based on content length, and preset buttons for quickly populating different content lengths. The variant and helper text update as you type.",
398
+ },
399
+ },
438
400
  },
439
- }
440
-
441
- // Atomic composition story - demonstrates how to use individual components
442
- export const AtomicComposition: Story = {
443
- render: () => {
401
+ render: function InteractiveTextArea() {
444
402
  const [value, setValue] = useState("")
445
- const maxLength = 100
403
+ const [decoration, setDecoration] = useState<
404
+ "underline" | "outline" | "filled"
405
+ >("outline")
406
+ const maxLength = 280
407
+
408
+ const variant =
409
+ value.length === 0
410
+ ? "default"
411
+ : value.length > 240
412
+ ? "warning"
413
+ : value.length > 10
414
+ ? "success"
415
+ : "default"
416
+
417
+ const helperText =
418
+ value.length === 0
419
+ ? "Start typing…"
420
+ : value.length > 240
421
+ ? "Approaching the character limit"
422
+ : "Looks good!"
423
+
424
+ const presets = [
425
+ { label: "Short", text: "Hello world." },
426
+ {
427
+ label: "Medium",
428
+ text: "This is a medium-length message that provides some context and detail about the topic at hand.",
429
+ },
430
+ {
431
+ label: "Long",
432
+ text: "This is a longer message that demonstrates what happens when content approaches the character limit. Keep writing to see the warning state activate as you get closer to 280 characters. Almost there now — just a few more words should do it to trigger the warning.",
433
+ },
434
+ { label: "Clear", text: "" },
435
+ ]
446
436
 
447
437
  return (
448
- <TextArea.Root fullWidth className="max-w-md">
449
- <TextArea.Label htmlFor="atomic-textarea" required>
450
- Custom Composed TextArea
451
- </TextArea.Label>
452
-
453
- <TextArea.Wrapper>
454
- <TextArea.Base
455
- id="atomic-textarea"
456
- placeholder="Built with atomic components..."
457
- value={value}
458
- onChange={(e) => setValue(e.target.value)}
459
- maxLength={maxLength}
460
- variant="default"
461
- decoration="outline"
462
- />
463
- </TextArea.Wrapper>
464
-
465
- <div className="flex items-center justify-between gap-2">
466
- <TextArea.HelperText variant="default">
467
- This is built using atomic components
468
- </TextArea.HelperText>
469
-
470
- <TextArea.CharCount
471
- currentLength={value.length}
472
- maxLength={maxLength}
473
- />
438
+ <div className="w-full p-8">
439
+ <div className="mx-auto max-w-3xl space-y-6">
440
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
441
+ {/* Controls panel */}
442
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
443
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
444
+ Presets
445
+ </p>
446
+ <div className="space-y-2">
447
+ {presets.map(({ label, text }) => (
448
+ <button
449
+ key={label}
450
+ onClick={() => setValue(text)}
451
+ className="border-fm-divider-secondary text-fm-primary font-fm-text text-fm-sm leading-fm-sm hover:bg-fm-surface-primary w-full rounded-lg border px-3 py-2 text-left transition-colors"
452
+ >
453
+ {label}
454
+ </button>
455
+ ))}
456
+ </div>
457
+ <div className="border-fm-divider-secondary border-t pt-4" />
458
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
459
+ Decoration
460
+ </p>
461
+ <div className="space-y-2">
462
+ {(["underline", "outline", "filled"] as const).map((d) => (
463
+ <button
464
+ key={d}
465
+ onClick={() => setDecoration(d)}
466
+ className={`border-fm-divider-secondary text-fm-primary font-fm-text text-fm-sm leading-fm-sm w-full rounded-lg border px-3 py-2 text-left capitalize transition-colors ${decoration === d ? "bg-fm-surface-primary" : "hover:bg-fm-surface-primary"}`}
467
+ >
468
+ {d}
469
+ </button>
470
+ ))}
471
+ </div>
472
+ <div className="border-fm-divider-secondary border-t pt-4" />
473
+ <div className="border-fm-divider-secondary bg-fm-surface-primary rounded-lg border p-3">
474
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-1 font-medium">
475
+ Characters
476
+ </p>
477
+ <p className="text-fm-primary text-fm-sm leading-fm-sm font-(--font-fm-mono)">
478
+ {value.length} / {maxLength}
479
+ </p>
480
+ </div>
481
+ </div>
482
+
483
+ {/* Preview stage */}
484
+ <div className="flex flex-col gap-3 lg:col-span-2">
485
+ <TextArea
486
+ label="Message"
487
+ placeholder="Start typing to see real-time validation…"
488
+ value={value}
489
+ onChange={(e) => setValue(e.target.value)}
490
+ variant={variant as "default" | "warning" | "success"}
491
+ helperText={helperText}
492
+ decoration={decoration}
493
+ maxLength={maxLength}
494
+ showCharCount
495
+ autoGrow
496
+ minHeight={80}
497
+ fullWidth
498
+ />
499
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
500
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
501
+ {`variant="${variant}" | ${value.length}/${maxLength} chars`}
502
+ </code>
503
+ </div>
504
+ </div>
505
+ </div>
474
506
  </div>
475
- </TextArea.Root>
507
+ </div>
476
508
  )
477
509
  },
510
+ }
511
+
512
+ // ─── 6. Parts ────────────────────────────────────────────────────────────────
513
+
514
+ export const Parts: Story = {
478
515
  parameters: {
479
516
  docs: {
480
517
  description: {
481
518
  story:
482
- "This example shows how to compose the TextArea using individual atomic components for maximum flexibility. This pattern allows for custom layouts and advanced use cases.",
519
+ "Each atomic sub-component shown individually: TextArea (convenience wrapper), and the atomic TextArea.Root + TextArea.Label + TextArea.Wrapper + TextArea.Base + TextArea.HelperText + TextArea.CharCount composition for full layout control.",
483
520
  },
484
521
  },
485
522
  },
486
- }
487
-
488
- // Complex form example
489
- export const ComplexFormExample: Story = {
490
523
  render: () => {
491
- const [feedback, setFeedback] = useState("")
492
- const [description, setDescription] = useState("")
524
+ const [atomicValue, setAtomicValue] = useState("")
525
+ const maxLength = 100
493
526
 
494
527
  return (
495
- <div className="max-w-lg space-y-6">
496
- <TextArea
497
- label="Product Description"
498
- placeholder="Describe your product..."
499
- value={description}
500
- onChange={(e) => setDescription(e.target.value)}
501
- helperText="Provide a clear and concise description"
502
- maxLength={500}
503
- showCharCount
504
- decoration="outline"
505
- fullWidth
506
- />
528
+ <div className="w-full max-w-lg space-y-10">
529
+ {/* Convenience component */}
530
+ <div>
531
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
532
+ TextArea — convenience wrapper
533
+ </h4>
534
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
535
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-3">
536
+ The default export combines all atomic parts into a single
537
+ component with label, textarea, helper text, and optional
538
+ character count.
539
+ </p>
540
+ <TextArea
541
+ label="Description"
542
+ placeholder="Tell us more…"
543
+ helperText="Provide a clear description"
544
+ maxLength={200}
545
+ showCharCount
546
+ decoration="outline"
547
+ fullWidth
548
+ />
549
+ </div>
550
+ </div>
507
551
 
508
- <TextArea
509
- label="Customer Feedback"
510
- placeholder="Share your experience..."
511
- value={feedback}
512
- onChange={(e) => setFeedback(e.target.value)}
513
- variant={feedback.length > 10 ? "success" : "default"}
514
- helperText={
515
- feedback.length > 10
516
- ? "Thank you for the detailed feedback!"
517
- : "Please provide detailed feedback"
518
- }
519
- required
520
- autoGrow
521
- minHeight={60}
522
- maxHeight={150}
523
- decoration="filled"
524
- fullWidth
525
- />
552
+ {/* Atomic composition */}
553
+ <div>
554
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
555
+ Atomic Composition
556
+ </h4>
557
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
558
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-3">
559
+ Build the same result using individual primitives for full control
560
+ over layout and positioning.
561
+ </p>
562
+ <TextArea.Root fullWidth>
563
+ <TextArea.Label htmlFor="atomic-textarea" required>
564
+ Custom Composed TextArea
565
+ </TextArea.Label>
566
+ <TextArea.Wrapper>
567
+ <TextArea.Base
568
+ id="atomic-textarea"
569
+ placeholder="Built with atomic components…"
570
+ value={atomicValue}
571
+ onChange={(e) => setAtomicValue(e.target.value)}
572
+ maxLength={maxLength}
573
+ variant="default"
574
+ decoration="outline"
575
+ />
576
+ </TextArea.Wrapper>
577
+ <div className="flex items-center justify-between gap-2">
578
+ <TextArea.HelperText variant="default">
579
+ Full control via atomic parts
580
+ </TextArea.HelperText>
581
+ <TextArea.CharCount
582
+ currentLength={atomicValue.length}
583
+ maxLength={maxLength}
584
+ />
585
+ </div>
586
+ </TextArea.Root>
587
+ </div>
588
+ </div>
526
589
  </div>
527
590
  )
528
591
  },
592
+ }
593
+
594
+ // ─── 7. UseCases ─────────────────────────────────────────────────────────────
595
+
596
+ export const UseCases: Story = {
529
597
  parameters: {
530
598
  docs: {
531
599
  description: {
532
600
  story:
533
- "An example showing multiple TextArea components in a form with different configurations, decorations, and dynamic states based on user input.",
601
+ "Real product-shaped examples: a social post composer with character counter and dynamic warnings, a support ticket form with required feedback, and a profile bio editor with success validation.",
534
602
  },
535
603
  },
536
604
  },
537
- }
605
+ render: function UseCasesTextArea() {
606
+ const [post, setPost] = useState("")
607
+ const [ticket, setTicket] = useState("")
608
+ const [bio, setBio] = useState("")
609
+
610
+ const postVariant =
611
+ post.length === 0 ? "default" : post.length > 240 ? "warning" : "success"
612
+ const postHelper =
613
+ post.length > 240
614
+ ? "Approaching the 280-character limit"
615
+ : post.length > 0
616
+ ? "Ready to post"
617
+ : "What's on your mind?"
618
+
619
+ const bioVariant =
620
+ bio.length > 10 ? "success" : bio.length > 0 ? "default" : "default"
621
+ const bioHelper =
622
+ bio.length > 10 ? "Looking good!" : "Tell listeners a bit about yourself"
538
623
 
539
- // Sizing variations with different decorations
540
- export const SizingVariations: Story = {
541
- render: () => (
542
- <div className="text-fm-primary space-y-4">
543
- <div>
544
- <h3 className="mb-2 text-sm font-medium">Small (3 rows) - Underline</h3>
545
- <TextArea
546
- placeholder="Small textarea..."
547
- rows={3}
548
- autoGrow={false}
549
- decoration="underline"
550
- className="max-w-sm"
551
- />
552
- </div>
624
+ return (
625
+ <div className="mx-auto max-w-3xl space-y-8 p-8">
626
+ {/* Social post composer */}
627
+ <div className="space-y-4">
628
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
629
+ Post Composer
630
+ </h4>
631
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md rounded-xl border p-5">
632
+ <TextArea
633
+ label="New Post"
634
+ placeholder="What's happening?"
635
+ value={post}
636
+ onChange={(e) => setPost(e.target.value)}
637
+ variant={postVariant}
638
+ helperText={postHelper}
639
+ maxLength={280}
640
+ showCharCount
641
+ decoration="underline"
642
+ fullWidth
643
+ />
644
+ </div>
645
+ </div>
553
646
 
554
- <div>
555
- <h3 className="mb-2 text-sm font-medium">Medium (5 rows) - Outline</h3>
556
- <TextArea
557
- placeholder="Medium textarea..."
558
- rows={5}
559
- autoGrow={false}
560
- decoration="outline"
561
- className="max-w-md"
562
- />
563
- </div>
647
+ {/* Support ticket */}
648
+ <div className="space-y-4">
649
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
650
+ Support Ticket
651
+ </h4>
652
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md rounded-xl border p-5">
653
+ <TextArea
654
+ label="Describe your issue"
655
+ placeholder="Please describe what happened in as much detail as possible…"
656
+ value={ticket}
657
+ onChange={(e) => setTicket(e.target.value)}
658
+ variant={
659
+ ticket.length > 20
660
+ ? "success"
661
+ : ticket.length > 0
662
+ ? "default"
663
+ : "default"
664
+ }
665
+ helperText={
666
+ ticket.length > 20
667
+ ? "Thank you — our team will review this"
668
+ : "Required — minimum 20 characters for a useful report"
669
+ }
670
+ required
671
+ autoGrow
672
+ minHeight={80}
673
+ maxHeight={240}
674
+ decoration="outline"
675
+ fullWidth
676
+ />
677
+ </div>
678
+ </div>
564
679
 
565
- <div>
566
- <h3 className="mb-2 text-sm font-medium">Large (8 rows) - Filled</h3>
567
- <TextArea
568
- placeholder="Large textarea..."
569
- rows={8}
570
- autoGrow={false}
571
- decoration="filled"
572
- className="max-w-lg"
573
- />
680
+ {/* Profile bio */}
681
+ <div className="space-y-4">
682
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
683
+ Profile Bio Editor
684
+ </h4>
685
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md rounded-xl border p-5">
686
+ <TextArea
687
+ label="Bio"
688
+ placeholder="I'm a podcast creator who loves…"
689
+ value={bio}
690
+ onChange={(e) => setBio(e.target.value)}
691
+ variant={bioVariant}
692
+ helperText={bioHelper}
693
+ maxLength={160}
694
+ showCharCount
695
+ decoration="filled"
696
+ fullWidth
697
+ />
698
+ </div>
699
+ </div>
574
700
  </div>
575
- </div>
576
- ),
701
+ )
702
+ },
703
+ }
704
+
705
+ // ─── 8. Accessibility ────────────────────────────────────────────────────────
706
+
707
+ export const Accessibility: Story = {
577
708
  parameters: {
578
709
  docs: {
579
710
  description: {
580
711
  story:
581
- "Different sizing options for the TextArea component with fixed heights, showcasing different decoration styles.",
712
+ "Accessibility-focused examples covering ARIA attributes, required field announcements, error state with aria-invalid, and the character count linked via aria-describedby. All interactive elements are keyboard-reachable and screen-reader friendly.",
582
713
  },
583
714
  },
584
715
  },
585
- }
586
-
587
- // All variants with all decorations
588
- export const AllVariantsAllDecorations: Story = {
589
716
  render: () => (
590
- <div className="grid max-w-6xl grid-cols-1 gap-6 lg:grid-cols-3">
591
- {/* Underline decoration */}
592
- <div className="space-y-4">
593
- <h3 className="text-fm-primary text-lg font-semibold">Underline</h3>
594
- <TextArea
595
- label="Default"
596
- placeholder="Default underline..."
597
- variant="default"
598
- decoration="underline"
599
- helperText="Default variant"
600
- fullWidth
601
- />
602
- <TextArea
603
- label="Error"
604
- placeholder="Error underline..."
605
- variant="error"
606
- decoration="underline"
607
- helperText="Something went wrong"
608
- fullWidth
609
- />
610
- <TextArea
611
- label="Warning"
612
- placeholder="Warning underline..."
613
- variant="warning"
614
- decoration="underline"
615
- helperText="Please check input"
616
- fullWidth
617
- />
717
+ <div className="w-full max-w-lg space-y-8">
718
+ {/* Required */}
719
+ <div>
720
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
721
+ Required Field
722
+ </h4>
618
723
  <TextArea
619
- label="Success"
620
- placeholder="Success underline..."
621
- variant="success"
622
- decoration="underline"
623
- helperText="Looks good!"
724
+ label="Feedback"
725
+ placeholder="Your feedback is required…"
726
+ required
727
+ helperText="Required — helps us improve the product"
728
+ decoration="outline"
624
729
  fullWidth
625
730
  />
626
731
  </div>
627
732
 
628
- {/* Outline decoration */}
629
- <div className="space-y-4">
630
- <h3 className="text-fm-primary text-lg font-semibold">Outline</h3>
631
- <TextArea
632
- label="Default"
633
- placeholder="Default outline..."
634
- variant="default"
635
- decoration="outline"
636
- helperText="Default variant"
637
- fullWidth
638
- />
733
+ {/* Error + aria-invalid */}
734
+ <div>
735
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
736
+ Error State (aria-invalid)
737
+ </h4>
639
738
  <TextArea
640
- label="Error"
641
- placeholder="Error outline..."
739
+ label="Description"
740
+ placeholder="Enter a description…"
642
741
  variant="error"
742
+ helperText="This field cannot be empty"
643
743
  decoration="outline"
644
- helperText="Something went wrong"
645
- fullWidth
646
- />
647
- <TextArea
648
- label="Warning"
649
- placeholder="Warning outline..."
650
- variant="warning"
651
- decoration="outline"
652
- helperText="Please check input"
653
744
  fullWidth
654
745
  />
746
+ </div>
747
+
748
+ {/* Character count + aria-describedby */}
749
+ <div>
750
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
751
+ Character Count (aria-describedby)
752
+ </h4>
655
753
  <TextArea
656
- label="Success"
657
- placeholder="Success outline..."
658
- variant="success"
754
+ label="Short Bio"
755
+ placeholder="A brief introduction…"
756
+ maxLength={160}
757
+ showCharCount
758
+ helperText="The character count is announced to screen readers via aria-describedby"
659
759
  decoration="outline"
660
- helperText="Looks good!"
661
760
  fullWidth
662
761
  />
663
762
  </div>
664
763
 
665
- {/* Filled decoration */}
666
- <div className="space-y-4">
667
- <h3 className="text-fm-primary text-lg font-semibold">Filled</h3>
668
- <TextArea
669
- label="Default"
670
- placeholder="Default filled..."
671
- variant="default"
672
- decoration="filled"
673
- helperText="Default variant"
674
- fullWidth
675
- />
676
- <TextArea
677
- label="Error"
678
- placeholder="Error filled..."
679
- variant="error"
680
- decoration="filled"
681
- helperText="Something went wrong"
682
- fullWidth
683
- />
684
- <TextArea
685
- label="Warning"
686
- placeholder="Warning filled..."
687
- variant="warning"
688
- decoration="filled"
689
- helperText="Please check input"
690
- fullWidth
691
- />
692
- <TextArea
693
- label="Success"
694
- placeholder="Success filled..."
695
- variant="success"
696
- decoration="filled"
697
- helperText="Looks good!"
698
- fullWidth
699
- />
764
+ {/* Accessibility best practices */}
765
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
766
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-2 font-medium">
767
+ Accessibility Best Practices
768
+ </p>
769
+ <ul className="space-y-1">
770
+ {[
771
+ "Always provide a label — the Label is linked via htmlFor/id",
772
+ "Use helperText to add context; it is linked via aria-describedby",
773
+ "The error variant sets aria-invalid automatically",
774
+ "required adds aria-required and an asterisk to the label",
775
+ "The character count element is also linked via aria-describedby",
776
+ "Tab moves focus into the textarea; Shift+Tab moves out",
777
+ ].map((item) => (
778
+ <li
779
+ key={item}
780
+ className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm"
781
+ >
782
+ {item}
783
+ </li>
784
+ ))}
785
+ </ul>
700
786
  </div>
701
787
  </div>
702
788
  ),
703
- parameters: {
704
- docs: {
705
- description: {
706
- story:
707
- "Complete showcase of all visual variants across all decoration styles.",
708
- },
709
- },
710
- },
711
- }
712
-
713
- // Unstyled example
714
- export const Unstyled: Story = {
715
- args: {
716
- placeholder: "Completely unstyled textarea...",
717
- unstyled: true,
718
- className:
719
- "border-2 border-dashed border-fm-divider-secondary p-4 rounded-lg bg-fm-surface-secondary",
720
- fullWidth: true,
721
- },
722
- parameters: {
723
- docs: {
724
- description: {
725
- story:
726
- "Example of using the unstyled prop to create completely custom styling.",
727
- },
728
- },
729
- },
730
789
  }