aural-ui 3.0.7 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1199
  2. package/dist/components/avatar/Avatar.stories.tsx +235 -237
  3. package/dist/components/badge/Badge.stories.tsx +379 -116
  4. package/dist/components/banner/Banner.stories.tsx +445 -391
  5. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  6. package/dist/components/button/Button.stories.tsx +585 -230
  7. package/dist/components/button/index.tsx +7 -7
  8. package/dist/components/card/Card.stories.tsx +619 -301
  9. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  10. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  11. package/dist/components/chip/Chip.stories.tsx +362 -168
  12. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -620
  13. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  14. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  15. package/dist/components/command/Command.stories.tsx +533 -856
  16. package/dist/components/dialog/Dialog.stories.tsx +505 -949
  17. package/dist/components/divider/Divider.stories.tsx +265 -502
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -993
  20. package/dist/components/drawer/index.tsx +3 -3
  21. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1018
  22. package/dist/components/form/Form.stories.tsx +560 -274
  23. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  24. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1221
  25. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  26. package/dist/components/if-else/if-else.stories.tsx +370 -83
  27. package/dist/components/input/Input.stories.tsx +436 -368
  28. package/dist/components/label/Label.stories.tsx +156 -154
  29. package/dist/components/list/List.stories.tsx +485 -822
  30. package/dist/components/marquee/Marquee.stories.tsx +356 -694
  31. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -410
  32. package/dist/components/overlay/Overlay.stories.tsx +452 -818
  33. package/dist/components/overlay/index.tsx +4 -4
  34. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  35. package/dist/components/popover/Popover.stories.tsx +484 -873
  36. package/dist/components/radio/Radio.stories.tsx +432 -124
  37. package/dist/components/resizable/Resizable.stories.tsx +496 -752
  38. package/dist/components/scroll-area/ScrollArea.stories.tsx +384 -1006
  39. package/dist/components/search/Search.stories.tsx +314 -575
  40. package/dist/components/select/Select.stories.tsx +684 -787
  41. package/dist/components/sheet/Sheet.stories.tsx +671 -936
  42. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  43. package/dist/components/slider/Slider.stories.tsx +384 -737
  44. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  45. package/dist/components/switch/Switch.stories.tsx +461 -208
  46. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  47. package/dist/components/table/Table.stories.tsx +770 -914
  48. package/dist/components/tabs/Tabs.stories.tsx +459 -1400
  49. package/dist/components/tag/Tag.stories.tsx +714 -542
  50. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  51. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -148
  52. package/dist/components/toast/Toast.stories.tsx +452 -1333
  53. package/dist/components/toggle/Toggle.stories.tsx +488 -909
  54. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1372
  55. package/dist/components/typography/Typography.stories.tsx +406 -89
  56. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  57. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  58. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  59. package/dist/icons/Icons.stories.tsx +0 -12
  60. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +226 -1013
  61. package/dist/icons/alert-icon/AlertIcon.stories.tsx +109 -929
  62. package/dist/icons/all-icons.tsx +124 -87
  63. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +140 -971
  64. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +148 -888
  65. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +135 -1019
  66. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +137 -953
  67. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +138 -997
  68. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +136 -942
  69. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +148 -1092
  70. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +146 -1211
  71. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +126 -615
  72. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +144 -1164
  73. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +167 -985
  74. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +122 -1179
  75. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +124 -1168
  76. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +119 -850
  77. package/dist/icons/camera-icon/CameraIcon.stories.tsx +112 -1213
  78. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +117 -934
  79. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +160 -961
  80. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +163 -961
  81. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +144 -942
  82. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +129 -966
  83. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +147 -964
  84. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +145 -975
  85. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +150 -1142
  86. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +114 -461
  87. package/dist/icons/coin-icon/CoinIcon.stories.tsx +124 -1322
  88. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +117 -1318
  89. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +114 -903
  90. package/dist/icons/command-icon/CommandIcon.stories.tsx +127 -1042
  91. package/dist/icons/copy-icon/CopyIcon.stories.tsx +123 -962
  92. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +147 -999
  93. package/dist/icons/cross-icon/CrossIcon.stories.tsx +139 -960
  94. package/dist/icons/download-icon/DownloadIcon.stories.tsx +126 -820
  95. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +124 -1031
  96. package/dist/icons/email-icon/EmailIcon.stories.tsx +115 -936
  97. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +112 -1111
  98. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +144 -1025
  99. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +143 -1036
  100. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +127 -1011
  101. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +126 -1056
  102. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +125 -614
  103. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +119 -1050
  104. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +169 -989
  105. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +115 -1145
  106. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +115 -1122
  107. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +130 -313
  108. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +145 -940
  109. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +119 -1174
  110. package/dist/icons/head-icon/HeadIcon.stories.tsx +111 -916
  111. package/dist/icons/heart-icon/HeartIcon.stories.tsx +120 -1019
  112. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +119 -683
  113. package/dist/icons/image-icon/ImageIcon.stories.tsx +105 -1121
  114. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +111 -1192
  115. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +136 -1256
  116. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +159 -962
  117. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +161 -1385
  118. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +124 -972
  119. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +119 -948
  120. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +119 -942
  121. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +108 -1215
  122. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +154 -1517
  123. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +110 -1188
  124. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +119 -678
  125. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +123 -1184
  126. package/dist/icons/message-icon/MessageIcon.stories.tsx +114 -538
  127. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +116 -1158
  128. package/dist/icons/moon-icon/MoonIcon.stories.tsx +120 -536
  129. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +109 -1184
  130. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +115 -1134
  131. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +119 -971
  132. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +111 -1100
  133. package/dist/icons/notes-icon/NotesIcon.stories.tsx +119 -1101
  134. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +109 -1111
  135. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +122 -684
  136. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +113 -954
  137. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +112 -877
  138. package/dist/icons/pause-icon/PauseIcon.stories.tsx +113 -1000
  139. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +115 -1070
  140. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +115 -978
  141. package/dist/icons/plus-icon/PlusIcon.stories.tsx +106 -1093
  142. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +107 -829
  143. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +102 -469
  144. package/dist/icons/search-icon/SearchIcon.stories.tsx +111 -1124
  145. package/dist/icons/setting-icon/SettingIcon.stories.tsx +107 -970
  146. package/dist/icons/share-icon/ShareIcon.stories.tsx +120 -1025
  147. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +117 -931
  148. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +137 -1104
  149. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +172 -982
  150. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +164 -983
  151. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +105 -958
  152. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +158 -580
  153. package/dist/icons/spinner-gradient-icon/index.tsx +6 -1
  154. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +158 -587
  155. package/dist/icons/spinner-solid-icon/index.tsx +6 -1
  156. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +146 -682
  157. package/dist/icons/spinner-solid-neutral-icon/index.tsx +1 -1
  158. package/dist/icons/star-icon/StarIcon.stories.tsx +124 -904
  159. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +112 -964
  160. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +116 -852
  161. package/dist/icons/sun-icon/SunIcon.stories.tsx +120 -831
  162. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +116 -950
  163. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +123 -980
  164. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +156 -1427
  165. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +146 -1142
  166. package/dist/icons/tick-icon/TickIcon.stories.tsx +145 -1276
  167. package/dist/icons/trash-icon/TrashIcon.stories.tsx +108 -933
  168. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +157 -1402
  169. package/dist/icons/upload-icon/UploadIcon.stories.tsx +115 -889
  170. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +118 -984
  171. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +125 -1049
  172. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +123 -1356
  173. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +110 -1171
  174. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +112 -1093
  175. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +115 -1087
  176. package/dist/icons/warning-icon/WarningIcon.stories.tsx +122 -1046
  177. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +161 -936
  178. package/dist/index.cjs +84 -84
  179. package/dist/index.js +84 -84
  180. package/dist/styles/aural-all-theme.css +1222 -0
  181. package/dist/styles/{aural-theme.css → aural-dark-theme.css} +15 -3
  182. package/dist/styles/aural-light-theme.css +1047 -0
  183. package/package.json +1 -1
@@ -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-gray-300 p-4 rounded-lg bg-gray-50",
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
  }