tharaday 0.1.1

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 (247) hide show
  1. package/.gitignore +30 -0
  2. package/.prettierrc.json +10 -0
  3. package/.storybook/main.ts +8 -0
  4. package/.storybook/preview.ts +50 -0
  5. package/.versionrc.json +6 -0
  6. package/README.md +73 -0
  7. package/dist/components/Accordion/Accordion.d.ts +2 -0
  8. package/dist/components/Accordion/Accordion.stories.d.ts +14 -0
  9. package/dist/components/Accordion/Accordion.types.d.ts +18 -0
  10. package/dist/components/Avatar/Avatar.d.ts +2 -0
  11. package/dist/components/Avatar/Avatar.stories.d.ts +14 -0
  12. package/dist/components/Avatar/Avatar.types.d.ts +10 -0
  13. package/dist/components/Badge/Badge.d.ts +2 -0
  14. package/dist/components/Badge/Badge.stories.d.ts +33 -0
  15. package/dist/components/Badge/Badge.types.d.ts +9 -0
  16. package/dist/components/Box/Box.d.ts +2 -0
  17. package/dist/components/Box/Box.stories.d.ts +38 -0
  18. package/dist/components/Box/Box.types.d.ts +49 -0
  19. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +3 -0
  20. package/dist/components/Breadcrumbs/Breadcrumbs.stories.d.ts +13 -0
  21. package/dist/components/Breadcrumbs/Breadcrumbs.types.d.ts +11 -0
  22. package/dist/components/Button/Button.d.ts +2 -0
  23. package/dist/components/Button/Button.stories.d.ts +22 -0
  24. package/dist/components/Button/Button.types.d.ts +12 -0
  25. package/dist/components/Card/Card.d.ts +5 -0
  26. package/dist/components/Card/Card.stories.d.ts +27 -0
  27. package/dist/components/Card/Card.types.d.ts +15 -0
  28. package/dist/components/Checkbox/Checkbox.d.ts +2 -0
  29. package/dist/components/Checkbox/Checkbox.stories.d.ts +17 -0
  30. package/dist/components/Checkbox/Checkbox.types.d.ts +6 -0
  31. package/dist/components/Dropdown/Dropdown.d.ts +2 -0
  32. package/dist/components/Dropdown/Dropdown.stories.d.ts +12 -0
  33. package/dist/components/Dropdown/Dropdown.types.d.ts +24 -0
  34. package/dist/components/Header/Header.d.ts +2 -0
  35. package/dist/components/Header/Header.stories.d.ts +18 -0
  36. package/dist/components/Header/Header.types.d.ts +14 -0
  37. package/dist/components/Input/Input.d.ts +2 -0
  38. package/dist/components/Input/Input.stories.d.ts +29 -0
  39. package/dist/components/Input/Input.types.d.ts +8 -0
  40. package/dist/components/Loader/Loader.d.ts +2 -0
  41. package/dist/components/Loader/Loader.stories.d.ts +25 -0
  42. package/dist/components/Loader/Loader.types.d.ts +8 -0
  43. package/dist/components/Modal/Modal.d.ts +2 -0
  44. package/dist/components/Modal/Modal.stories.d.ts +22 -0
  45. package/dist/components/Modal/Modal.types.d.ts +12 -0
  46. package/dist/components/NavBar/NavBar.d.ts +6 -0
  47. package/dist/components/NavBar/NavBar.stories.d.ts +8 -0
  48. package/dist/components/NavBar/NavBar.types.d.ts +34 -0
  49. package/dist/components/Notification/Notification.d.ts +2 -0
  50. package/dist/components/Notification/Notification.stories.d.ts +26 -0
  51. package/dist/components/Notification/Notification.types.d.ts +8 -0
  52. package/dist/components/Pagination/Pagination.d.ts +2 -0
  53. package/dist/components/Pagination/Pagination.stories.d.ts +21 -0
  54. package/dist/components/Pagination/Pagination.types.d.ts +34 -0
  55. package/dist/components/ProgressBar/ProgressBar.d.ts +2 -0
  56. package/dist/components/ProgressBar/ProgressBar.stories.d.ts +32 -0
  57. package/dist/components/ProgressBar/ProgressBar.types.d.ts +12 -0
  58. package/dist/components/RadioButton/RadioButton.d.ts +2 -0
  59. package/dist/components/RadioButton/RadioButton.stories.d.ts +30 -0
  60. package/dist/components/RadioButton/RadioButton.types.d.ts +8 -0
  61. package/dist/components/Select/Select.d.ts +2 -0
  62. package/dist/components/Select/Select.stories.d.ts +29 -0
  63. package/dist/components/Select/Select.types.d.ts +15 -0
  64. package/dist/components/Skeleton/Skeleton.d.ts +2 -0
  65. package/dist/components/Skeleton/Skeleton.stories.d.ts +15 -0
  66. package/dist/components/Skeleton/Skeleton.types.d.ts +8 -0
  67. package/dist/components/Stepper/Step.d.ts +2 -0
  68. package/dist/components/Stepper/Step.types.d.ts +17 -0
  69. package/dist/components/Stepper/Stepper.d.ts +2 -0
  70. package/dist/components/Stepper/Stepper.stories.d.ts +15 -0
  71. package/dist/components/Stepper/Stepper.types.d.ts +13 -0
  72. package/dist/components/Stepper/stepper.utils.d.ts +3 -0
  73. package/dist/components/Switch/Switch.d.ts +2 -0
  74. package/dist/components/Switch/Switch.stories.d.ts +16 -0
  75. package/dist/components/Switch/Switch.types.d.ts +5 -0
  76. package/dist/components/Table/Table.d.ts +8 -0
  77. package/dist/components/Table/Table.stories.d.ts +27 -0
  78. package/dist/components/Table/Table.types.d.ts +17 -0
  79. package/dist/components/Tabs/Tabs.d.ts +2 -0
  80. package/dist/components/Tabs/Tabs.stories.d.ts +14 -0
  81. package/dist/components/Tabs/Tabs.types.d.ts +16 -0
  82. package/dist/components/Text/Text.d.ts +2 -0
  83. package/dist/components/Text/Text.stories.d.ts +35 -0
  84. package/dist/components/Text/Text.types.d.ts +21 -0
  85. package/dist/components/Textarea/Textarea.d.ts +2 -0
  86. package/dist/components/Textarea/Textarea.stories.d.ts +29 -0
  87. package/dist/components/Textarea/Textarea.types.d.ts +9 -0
  88. package/dist/components/Tooltip/Tooltip.d.ts +2 -0
  89. package/dist/components/Tooltip/Tooltip.stories.d.ts +10 -0
  90. package/dist/components/Tooltip/Tooltip.types.d.ts +12 -0
  91. package/dist/ds.css +1 -0
  92. package/dist/ds.js +1930 -0
  93. package/dist/ds.umd.cjs +1 -0
  94. package/dist/index.d.ts +63 -0
  95. package/dist/layouts/AppLayout/AppLayout.d.ts +8 -0
  96. package/dist/layouts/AppLayout/AppLayout.stories.d.ts +19 -0
  97. package/dist/layouts/AppLayout/AppLayout.types.d.ts +50 -0
  98. package/dist/layouts/AuthLayout/AuthLayout.d.ts +2 -0
  99. package/dist/layouts/AuthLayout/AuthLayout.stories.d.ts +12 -0
  100. package/dist/layouts/AuthLayout/AuthLayout.types.d.ts +7 -0
  101. package/dist/layouts/DashboardLayout/DashboardLayout.d.ts +2 -0
  102. package/dist/layouts/DashboardLayout/DashboardLayout.stories.d.ts +17 -0
  103. package/dist/layouts/DashboardLayout/DashboardLayout.types.d.ts +15 -0
  104. package/dist/layouts/SettingsLayout/SettingsLayout.d.ts +2 -0
  105. package/dist/layouts/SettingsLayout/SettingsLayout.stories.d.ts +17 -0
  106. package/dist/layouts/SettingsLayout/SettingsLayout.types.d.ts +14 -0
  107. package/eslint.config.js +45 -0
  108. package/package.json +100 -0
  109. package/src/components/Accordion/Accordion.module.css +158 -0
  110. package/src/components/Accordion/Accordion.stories.tsx +133 -0
  111. package/src/components/Accordion/Accordion.tsx +68 -0
  112. package/src/components/Accordion/Accordion.types.ts +21 -0
  113. package/src/components/Avatar/Avatar.module.css +58 -0
  114. package/src/components/Avatar/Avatar.stories.tsx +41 -0
  115. package/src/components/Avatar/Avatar.tsx +64 -0
  116. package/src/components/Avatar/Avatar.types.ts +12 -0
  117. package/src/components/Badge/Badge.module.css +98 -0
  118. package/src/components/Badge/Badge.stories.tsx +108 -0
  119. package/src/components/Badge/Badge.tsx +22 -0
  120. package/src/components/Badge/Badge.types.ts +11 -0
  121. package/src/components/Box/Box.module.css +638 -0
  122. package/src/components/Box/Box.stories.tsx +109 -0
  123. package/src/components/Box/Box.tsx +95 -0
  124. package/src/components/Box/Box.types.ts +65 -0
  125. package/src/components/Breadcrumbs/Breadcrumbs.module.css +41 -0
  126. package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +40 -0
  127. package/src/components/Breadcrumbs/Breadcrumbs.tsx +54 -0
  128. package/src/components/Breadcrumbs/Breadcrumbs.types.ts +13 -0
  129. package/src/components/Button/Button.module.css +247 -0
  130. package/src/components/Button/Button.stories.tsx +93 -0
  131. package/src/components/Button/Button.tsx +31 -0
  132. package/src/components/Button/Button.types.ts +14 -0
  133. package/src/components/Card/Card.module.css +112 -0
  134. package/src/components/Card/Card.stories.tsx +86 -0
  135. package/src/components/Card/Card.tsx +70 -0
  136. package/src/components/Card/Card.types.ts +20 -0
  137. package/src/components/Checkbox/Checkbox.module.css +88 -0
  138. package/src/components/Checkbox/Checkbox.stories.tsx +65 -0
  139. package/src/components/Checkbox/Checkbox.tsx +57 -0
  140. package/src/components/Checkbox/Checkbox.types.ts +7 -0
  141. package/src/components/Dropdown/Dropdown.module.css +140 -0
  142. package/src/components/Dropdown/Dropdown.stories.tsx +86 -0
  143. package/src/components/Dropdown/Dropdown.tsx +251 -0
  144. package/src/components/Dropdown/Dropdown.types.ts +27 -0
  145. package/src/components/Header/Header.module.css +38 -0
  146. package/src/components/Header/Header.stories.tsx +53 -0
  147. package/src/components/Header/Header.tsx +49 -0
  148. package/src/components/Header/Header.types.ts +15 -0
  149. package/src/components/Input/Input.module.css +87 -0
  150. package/src/components/Input/Input.stories.tsx +101 -0
  151. package/src/components/Input/Input.tsx +41 -0
  152. package/src/components/Input/Input.types.ts +10 -0
  153. package/src/components/Loader/Loader.module.css +49 -0
  154. package/src/components/Loader/Loader.stories.tsx +75 -0
  155. package/src/components/Loader/Loader.tsx +15 -0
  156. package/src/components/Loader/Loader.types.ts +9 -0
  157. package/src/components/Modal/Modal.module.css +88 -0
  158. package/src/components/Modal/Modal.stories.tsx +94 -0
  159. package/src/components/Modal/Modal.tsx +115 -0
  160. package/src/components/Modal/Modal.types.ts +13 -0
  161. package/src/components/NavBar/NavBar.module.css +77 -0
  162. package/src/components/NavBar/NavBar.stories.tsx +55 -0
  163. package/src/components/NavBar/NavBar.tsx +50 -0
  164. package/src/components/NavBar/NavBar.types.ts +36 -0
  165. package/src/components/Notification/Notification.module.css +72 -0
  166. package/src/components/Notification/Notification.stories.tsx +81 -0
  167. package/src/components/Notification/Notification.tsx +34 -0
  168. package/src/components/Notification/Notification.types.ts +10 -0
  169. package/src/components/Pagination/Pagination.module.css +31 -0
  170. package/src/components/Pagination/Pagination.stories.tsx +128 -0
  171. package/src/components/Pagination/Pagination.tsx +245 -0
  172. package/src/components/Pagination/Pagination.types.ts +37 -0
  173. package/src/components/ProgressBar/ProgressBar.module.css +67 -0
  174. package/src/components/ProgressBar/ProgressBar.stories.tsx +91 -0
  175. package/src/components/ProgressBar/ProgressBar.tsx +49 -0
  176. package/src/components/ProgressBar/ProgressBar.types.ts +13 -0
  177. package/src/components/RadioButton/RadioButton.module.css +162 -0
  178. package/src/components/RadioButton/RadioButton.stories.tsx +114 -0
  179. package/src/components/RadioButton/RadioButton.tsx +43 -0
  180. package/src/components/RadioButton/RadioButton.types.ts +10 -0
  181. package/src/components/Select/Select.module.css +88 -0
  182. package/src/components/Select/Select.stories.tsx +99 -0
  183. package/src/components/Select/Select.tsx +51 -0
  184. package/src/components/Select/Select.types.ts +18 -0
  185. package/src/components/Skeleton/Skeleton.module.css +71 -0
  186. package/src/components/Skeleton/Skeleton.stories.tsx +54 -0
  187. package/src/components/Skeleton/Skeleton.tsx +32 -0
  188. package/src/components/Skeleton/Skeleton.types.ts +10 -0
  189. package/src/components/Stepper/Step.module.css +162 -0
  190. package/src/components/Stepper/Step.tsx +62 -0
  191. package/src/components/Stepper/Step.types.ts +19 -0
  192. package/src/components/Stepper/Stepper.module.css +39 -0
  193. package/src/components/Stepper/Stepper.stories.tsx +88 -0
  194. package/src/components/Stepper/Stepper.tsx +47 -0
  195. package/src/components/Stepper/Stepper.types.ts +16 -0
  196. package/src/components/Stepper/stepper.utils.ts +41 -0
  197. package/src/components/Switch/Switch.module.css +74 -0
  198. package/src/components/Switch/Switch.stories.tsx +56 -0
  199. package/src/components/Switch/Switch.tsx +36 -0
  200. package/src/components/Switch/Switch.types.ts +6 -0
  201. package/src/components/Table/Table.module.css +78 -0
  202. package/src/components/Table/Table.stories.tsx +124 -0
  203. package/src/components/Table/Table.tsx +75 -0
  204. package/src/components/Table/Table.types.ts +29 -0
  205. package/src/components/Tabs/Tabs.module.css +74 -0
  206. package/src/components/Tabs/Tabs.stories.tsx +48 -0
  207. package/src/components/Tabs/Tabs.tsx +113 -0
  208. package/src/components/Tabs/Tabs.types.ts +18 -0
  209. package/src/components/Text/Text.module.css +126 -0
  210. package/src/components/Text/Text.stories.tsx +128 -0
  211. package/src/components/Text/Text.tsx +50 -0
  212. package/src/components/Text/Text.types.ts +43 -0
  213. package/src/components/Textarea/Textarea.module.css +82 -0
  214. package/src/components/Textarea/Textarea.stories.tsx +99 -0
  215. package/src/components/Textarea/Textarea.tsx +43 -0
  216. package/src/components/Textarea/Textarea.types.ts +11 -0
  217. package/src/components/Tooltip/Tooltip.module.css +125 -0
  218. package/src/components/Tooltip/Tooltip.stories.tsx +68 -0
  219. package/src/components/Tooltip/Tooltip.tsx +87 -0
  220. package/src/components/Tooltip/Tooltip.types.ts +14 -0
  221. package/src/index.ts +132 -0
  222. package/src/layouts/AppLayout/AppLayout.module.css +17 -0
  223. package/src/layouts/AppLayout/AppLayout.stories.tsx +124 -0
  224. package/src/layouts/AppLayout/AppLayout.tsx +46 -0
  225. package/src/layouts/AppLayout/AppLayout.types.ts +50 -0
  226. package/src/layouts/AuthLayout/AuthLayout.module.css +49 -0
  227. package/src/layouts/AuthLayout/AuthLayout.stories.tsx +109 -0
  228. package/src/layouts/AuthLayout/AuthLayout.tsx +27 -0
  229. package/src/layouts/AuthLayout/AuthLayout.types.tsx +8 -0
  230. package/src/layouts/DashboardLayout/DashboardLayout.module.css +37 -0
  231. package/src/layouts/DashboardLayout/DashboardLayout.stories.tsx +144 -0
  232. package/src/layouts/DashboardLayout/DashboardLayout.tsx +41 -0
  233. package/src/layouts/DashboardLayout/DashboardLayout.types.tsx +14 -0
  234. package/src/layouts/SettingsLayout/SettingsLayout.module.css +39 -0
  235. package/src/layouts/SettingsLayout/SettingsLayout.stories.tsx +103 -0
  236. package/src/layouts/SettingsLayout/SettingsLayout.tsx +37 -0
  237. package/src/layouts/SettingsLayout/SettingsLayout.types.tsx +13 -0
  238. package/src/styles/ds.css +12 -0
  239. package/src/styles/semantic.css +56 -0
  240. package/src/styles/themes/dark.css +58 -0
  241. package/src/styles/themes/light.css +58 -0
  242. package/src/styles/themes/retro.css +58 -0
  243. package/src/styles/tokens.css +138 -0
  244. package/tsconfig.app.json +28 -0
  245. package/tsconfig.json +7 -0
  246. package/tsconfig.node.json +26 -0
  247. package/vite.config.ts +33 -0
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Tabs } from './Tabs.tsx';
4
+
5
+ const meta = {
6
+ title: 'Components/Tabs',
7
+ component: Tabs,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ } satisfies Meta<typeof Tabs>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ const items = [
18
+ { id: 'account', label: 'Account', content: 'Account settings and information.' },
19
+ { id: 'password', label: 'Password', content: 'Change your password and security settings.' },
20
+ {
21
+ id: 'notifications',
22
+ label: 'Notifications',
23
+ content: 'Manage your email and push notifications.',
24
+ },
25
+ ];
26
+
27
+ export const Line: Story = {
28
+ args: {
29
+ variant: 'line',
30
+ items,
31
+ },
32
+ };
33
+
34
+ export const Pill: Story = {
35
+ args: {
36
+ variant: 'pill',
37
+ items,
38
+ },
39
+ };
40
+
41
+ export const WithDisabled: Story = {
42
+ args: {
43
+ items: [
44
+ ...items,
45
+ { id: 'billing', label: 'Billing', content: 'Billing history and methods.', disabled: true },
46
+ ],
47
+ },
48
+ };
@@ -0,0 +1,113 @@
1
+ import clsx from 'clsx';
2
+ import { useId, useState, useRef, type KeyboardEvent } from 'react';
3
+
4
+ import styles from './Tabs.module.css';
5
+ import type { TabsProps } from './Tabs.types.ts';
6
+
7
+ export const Tabs = ({
8
+ items,
9
+ defaultActiveId,
10
+ activeId: controlledActiveId,
11
+ onChange,
12
+ className,
13
+ variant = 'line',
14
+ id,
15
+ }: TabsProps) => {
16
+ const baseId = useId();
17
+ const componentId = id ?? `ds-tabs-${baseId}`;
18
+ const [uncontrolledActiveId, setUncontrolledActiveId] = useState(
19
+ defaultActiveId || (items.length > 0 ? items[0].id : '')
20
+ );
21
+
22
+ const activeId = controlledActiveId !== undefined ? controlledActiveId : uncontrolledActiveId;
23
+ const tabListRef = useRef<HTMLDivElement>(null);
24
+
25
+ const handleTabClick = (id: string, disabled?: boolean) => {
26
+ if (disabled) {
27
+ return;
28
+ }
29
+ if (controlledActiveId === undefined) {
30
+ setUncontrolledActiveId(id);
31
+ }
32
+ onChange?.(id);
33
+ };
34
+
35
+ const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
36
+ const enabledTabs = items.filter((item) => !item.disabled);
37
+ const currentIndex = enabledTabs.findIndex((item) => item.id === activeId);
38
+
39
+ let nextIndex = -1;
40
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
41
+ nextIndex = (currentIndex + 1) % enabledTabs.length;
42
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
43
+ nextIndex = (currentIndex - 1 + enabledTabs.length) % enabledTabs.length;
44
+ } else if (e.key === 'Home') {
45
+ nextIndex = 0;
46
+ } else if (e.key === 'End') {
47
+ nextIndex = enabledTabs.length - 1;
48
+ }
49
+
50
+ if (nextIndex !== -1) {
51
+ e.preventDefault();
52
+ const nextId = enabledTabs[nextIndex].id;
53
+ handleTabClick(nextId);
54
+
55
+ // Focus the new tab
56
+ const tabElements = tabListRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]');
57
+ const nextTabElement = Array.from(tabElements || []).find(
58
+ (el) => el.getAttribute('data-id') === nextId
59
+ );
60
+ nextTabElement?.focus();
61
+ }
62
+ };
63
+
64
+ const activeTab = items.find((item) => item.id === activeId);
65
+
66
+ return (
67
+ <div id={componentId} className={clsx(styles.root, className)}>
68
+ <div
69
+ role="tablist"
70
+ ref={tabListRef}
71
+ className={clsx(styles.tabList, variant === 'pill' && styles.pillList)}
72
+ onKeyDown={handleKeyDown}
73
+ >
74
+ {items.map((item) => {
75
+ const isSelected = item.id === activeId;
76
+ const isDisabled = Boolean(item.disabled);
77
+ const tabIndex = isDisabled ? -1 : isSelected ? 0 : -1;
78
+ return (
79
+ <button
80
+ key={item.id}
81
+ role="tab"
82
+ id={`${componentId}-tab-${item.id}`}
83
+ aria-selected={isSelected}
84
+ aria-controls={`${componentId}-panel-${item.id}`}
85
+ aria-disabled={isDisabled}
86
+ tabIndex={tabIndex}
87
+ disabled={isDisabled}
88
+ data-id={item.id}
89
+ className={clsx(
90
+ styles.tabItem,
91
+ variant === 'pill' && styles.pillItem,
92
+ isSelected && styles.active,
93
+ isDisabled && styles.disabled
94
+ )}
95
+ onClick={() => handleTabClick(item.id, isDisabled)}
96
+ >
97
+ {item.label}
98
+ </button>
99
+ );
100
+ })}
101
+ </div>
102
+ <div
103
+ role="tabpanel"
104
+ id={`${componentId}-panel-${activeId}`}
105
+ aria-labelledby={`${componentId}-tab-${activeId}`}
106
+ className={styles.tabPanel}
107
+ tabIndex={0}
108
+ >
109
+ {activeTab?.content}
110
+ </div>
111
+ </div>
112
+ );
113
+ };
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface TabItem {
4
+ id: string;
5
+ label: ReactNode;
6
+ content: ReactNode;
7
+ disabled?: boolean;
8
+ }
9
+
10
+ export interface TabsProps {
11
+ id?: string;
12
+ items: TabItem[];
13
+ defaultActiveId?: string;
14
+ activeId?: string;
15
+ onChange?: (id: string) => void;
16
+ className?: string;
17
+ variant?: 'line' | 'pill';
18
+ }
@@ -0,0 +1,126 @@
1
+ .root {
2
+ margin: 0;
3
+ font-family: var(--ds-font-family-base);
4
+ line-height: var(--ds-line-height-base);
5
+ color: inherit;
6
+ }
7
+
8
+ /* Variants */
9
+ .h1 {
10
+ font-size: var(--ds-font-size-2xl);
11
+ font-weight: var(--ds-font-weight-bold);
12
+ line-height: var(--ds-line-height-tight);
13
+ }
14
+
15
+ .h2 {
16
+ font-size: var(--ds-font-size-xl);
17
+ font-weight: var(--ds-font-weight-bold);
18
+ line-height: var(--ds-line-height-tight);
19
+ }
20
+
21
+ .h3 {
22
+ font-size: var(--ds-font-size-lg);
23
+ font-weight: var(--ds-font-weight-bold);
24
+ line-height: var(--ds-line-height-tight);
25
+ }
26
+
27
+ .h4 {
28
+ font-size: var(--ds-font-size-base);
29
+ font-weight: var(--ds-font-weight-bold);
30
+ }
31
+
32
+ .h5 {
33
+ font-size: var(--ds-font-size-sm);
34
+ font-weight: var(--ds-font-weight-bold);
35
+ }
36
+
37
+ .h6 {
38
+ font-size: var(--ds-font-size-xs);
39
+ font-weight: var(--ds-font-weight-bold);
40
+ }
41
+
42
+ .body-lg {
43
+ font-size: var(--ds-font-size-lg);
44
+ }
45
+
46
+ .body-md {
47
+ font-size: var(--ds-font-size-base);
48
+ }
49
+
50
+ .body-sm {
51
+ font-size: var(--ds-font-size-sm);
52
+ }
53
+
54
+ .label {
55
+ font-size: var(--ds-font-size-xs);
56
+ font-weight: var(--ds-font-weight-medium);
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.025em;
59
+ }
60
+
61
+ .code {
62
+ font-family: var(--ds-font-family-mono);
63
+ font-size: 0.9em;
64
+ background-color: var(--ds-surface-1);
65
+ padding: 0.2em 0.4em;
66
+ border-radius: var(--ds-radius-sm);
67
+ }
68
+
69
+ /* Alignments */
70
+ .left {
71
+ text-align: left;
72
+ }
73
+ .center {
74
+ text-align: center;
75
+ }
76
+ .right {
77
+ text-align: right;
78
+ }
79
+ .justify {
80
+ text-align: justify;
81
+ }
82
+
83
+ /* Weights */
84
+ .regular {
85
+ font-weight: var(--ds-font-weight-regular);
86
+ }
87
+ .medium {
88
+ font-weight: var(--ds-font-weight-medium);
89
+ }
90
+ .bold {
91
+ font-weight: var(--ds-font-weight-bold);
92
+ }
93
+
94
+ /* Colors */
95
+ .color-main {
96
+ color: var(--ds-text-1);
97
+ }
98
+ .color-subtle {
99
+ color: var(--ds-text-2);
100
+ }
101
+ .color-subtle {
102
+ color: var(--ds-text-2);
103
+ }
104
+ .color-on-brand {
105
+ color: var(--ds-text-on-brand);
106
+ }
107
+ .color-danger {
108
+ color: var(--ds-danger);
109
+ }
110
+ .color-success {
111
+ color: var(--ds-success);
112
+ }
113
+ .color-warning {
114
+ color: var(--ds-warning);
115
+ }
116
+ .color-info {
117
+ color: var(--ds-info);
118
+ }
119
+
120
+ /* Utils */
121
+ .noWrap {
122
+ white-space: nowrap;
123
+ overflow: hidden;
124
+ text-overflow: ellipsis;
125
+ width: 100%;
126
+ }
@@ -0,0 +1,128 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Text } from './Text.tsx';
4
+ import { Box } from '../Box/Box.tsx';
5
+
6
+ const meta = {
7
+ title: 'Components/Text',
8
+ component: Text,
9
+ tags: ['autodocs'],
10
+ argTypes: {
11
+ variant: {
12
+ control: 'select',
13
+ options: [
14
+ 'h1',
15
+ 'h2',
16
+ 'h3',
17
+ 'h4',
18
+ 'h5',
19
+ 'h6',
20
+ 'body-lg',
21
+ 'body-md',
22
+ 'body-sm',
23
+ 'label',
24
+ 'code',
25
+ ],
26
+ },
27
+ align: {
28
+ control: 'select',
29
+ options: ['left', 'center', 'right', 'justify'],
30
+ },
31
+ weight: {
32
+ control: 'select',
33
+ options: ['regular', 'medium', 'bold'],
34
+ },
35
+ color: {
36
+ control: 'select',
37
+ options: ['main', 'subtle', 'subtle', 'on-brand', 'danger', 'success', 'warning', 'info'],
38
+ },
39
+ as: {
40
+ control: 'text',
41
+ },
42
+ },
43
+ } satisfies Meta<typeof Text>;
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof meta>;
47
+
48
+ export const Default: Story = {
49
+ args: {
50
+ children: 'Tharaday Typography',
51
+ variant: 'body-md',
52
+ },
53
+ };
54
+
55
+ export const Headings: Story = {
56
+ args: {
57
+ children: '',
58
+ },
59
+ render: () => (
60
+ <Box display="flex" flexDirection="column" gap={4}>
61
+ <Text variant="h1">Heading 1</Text>
62
+ <Text variant="h2">Heading 2</Text>
63
+ <Text variant="h3">Heading 3</Text>
64
+ <Text variant="h4">Heading 4</Text>
65
+ <Text variant="h5">Heading 5</Text>
66
+ <Text variant="h6">Heading 6</Text>
67
+ </Box>
68
+ ),
69
+ };
70
+
71
+ export const Body: Story = {
72
+ args: {
73
+ children: '',
74
+ },
75
+ render: () => (
76
+ <Box display="flex" flexDirection="column" gap={4}>
77
+ <Text variant="body-lg">Body Large: The quick brown fox jumps over the lazy dog.</Text>
78
+ <Text variant="body-md">
79
+ Body Medium (Default): The quick brown fox jumps over the lazy dog.
80
+ </Text>
81
+ <Text variant="body-sm">Body Small: The quick brown fox jumps over the lazy dog.</Text>
82
+ </Box>
83
+ ),
84
+ };
85
+
86
+ export const Other: Story = {
87
+ args: {
88
+ children: '',
89
+ },
90
+ render: () => (
91
+ <Box display="flex" flexDirection="column" gap={4}>
92
+ <Text variant="label">Label / Overline Text</Text>
93
+ <Text variant="code">const typography = 'awesome';</Text>
94
+ </Box>
95
+ ),
96
+ };
97
+
98
+ export const Colors: Story = {
99
+ args: {
100
+ children: '',
101
+ },
102
+ render: () => (
103
+ <Box display="flex" flexDirection="column" gap={2}>
104
+ <Text color="main">Main Text Color</Text>
105
+ <Text color="subtle">Subtle Text Color</Text>
106
+ <Text color="info">Info Text Color</Text>
107
+ <Text color="success">Success Text Color</Text>
108
+ <Text color="warning">Warning Text Color</Text>
109
+ <Text color="danger">Danger Text Color</Text>
110
+ <Box padding={2} backgroundColor="main" borderRadius="sm">
111
+ <Text color="on-brand">On Brand Text Color</Text>
112
+ </Box>
113
+ </Box>
114
+ ),
115
+ };
116
+
117
+ export const NoWrap: Story = {
118
+ args: {
119
+ children:
120
+ 'This is a very long text that should be truncated when the container is small. It will show ellipsis at the end.',
121
+ noWrap: true,
122
+ },
123
+ render: (args) => (
124
+ <Box width="200px" border padding={2}>
125
+ <Text {...args} />
126
+ </Box>
127
+ ),
128
+ };
@@ -0,0 +1,50 @@
1
+ import clsx from 'clsx';
2
+ import type { ElementType } from 'react';
3
+
4
+ import styles from './Text.module.css';
5
+ import type { TextProps, TextVariant } from './Text.types.ts';
6
+
7
+ const variantElementMap: Record<TextVariant, ElementType> = {
8
+ h1: 'h1',
9
+ h2: 'h2',
10
+ h3: 'h3',
11
+ h4: 'h4',
12
+ h5: 'h5',
13
+ h6: 'h6',
14
+ 'body-lg': 'p',
15
+ 'body-md': 'p',
16
+ 'body-sm': 'p',
17
+ label: 'span',
18
+ code: 'code',
19
+ };
20
+
21
+ export const Text = ({
22
+ children,
23
+ variant = 'body-md',
24
+ as,
25
+ align,
26
+ weight,
27
+ color,
28
+ noWrap,
29
+ className,
30
+ ...props
31
+ }: TextProps) => {
32
+ const Component = as || variantElementMap[variant] || 'span';
33
+
34
+ return (
35
+ <Component
36
+ className={clsx(
37
+ styles.root,
38
+ styles[variant],
39
+ align && styles[align],
40
+ weight && styles[weight],
41
+ color && styles[`color-${color}`],
42
+ noWrap && styles.noWrap,
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ </Component>
49
+ );
50
+ };
@@ -0,0 +1,43 @@
1
+ import type { ReactNode, ElementType, HTMLAttributes } from 'react';
2
+
3
+ export type TextVariant =
4
+ | 'h1'
5
+ | 'h2'
6
+ | 'h3'
7
+ | 'h4'
8
+ | 'h5'
9
+ | 'h6'
10
+ | 'body-lg'
11
+ | 'body-md'
12
+ | 'body-sm'
13
+ | 'label'
14
+ | 'code';
15
+
16
+ export type TextAlign = 'left' | 'center' | 'right' | 'justify';
17
+ export type FontWeight = 'regular' | 'medium' | 'bold';
18
+ export type TextColor =
19
+ | 'main'
20
+ | 'subtle'
21
+ | 'subtle'
22
+ | 'on-brand'
23
+ | 'danger'
24
+ | 'success'
25
+ | 'warning'
26
+ | 'info';
27
+
28
+ export interface TextProps extends HTMLAttributes<HTMLElement> {
29
+ /** The content to display */
30
+ children: ReactNode;
31
+ /** The typographic style to apply */
32
+ variant?: TextVariant;
33
+ /** The semantic HTML element to render */
34
+ as?: ElementType;
35
+ /** Text alignment */
36
+ align?: TextAlign;
37
+ /** Font weight override */
38
+ weight?: FontWeight;
39
+ /** Text color based on semantic tokens */
40
+ color?: TextColor;
41
+ /** If true, the text will not wrap and show ellipsis */
42
+ noWrap?: boolean;
43
+ }
@@ -0,0 +1,82 @@
1
+ .wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--ds-space-1);
5
+ width: 100%;
6
+ }
7
+
8
+ .label {
9
+ font-family: var(--ds-font-family-base);
10
+ font-size: var(--ds-font-size-sm);
11
+ font-weight: var(--ds-font-weight-medium);
12
+ color: var(--ds-text-1);
13
+ }
14
+
15
+ .textareaRoot {
16
+ box-sizing: border-box;
17
+ width: 100%;
18
+ font-family: var(--ds-font-family-base);
19
+ border: 1px solid var(--ds-border-1);
20
+ border-radius: var(--ds-radius-md);
21
+ background-color: var(--ds-surface-0);
22
+ color: var(--ds-text-1);
23
+ padding: var(--ds-space-2) var(--ds-space-3);
24
+ transition:
25
+ border-color var(--ds-transition-fast),
26
+ box-shadow var(--ds-transition-fast);
27
+ resize: vertical;
28
+ }
29
+
30
+ .textareaRoot:focus {
31
+ outline: none;
32
+ border-color: var(--ds-ring);
33
+ box-shadow:
34
+ 0 0 0 2px var(--ds-ring-offset),
35
+ 0 0 0 4px var(--ds-ring);
36
+ }
37
+
38
+ .textareaRoot::placeholder {
39
+ color: var(--ds-text-2);
40
+ }
41
+
42
+ .textareaRoot:disabled {
43
+ background-color: var(--ds-surface-1);
44
+ cursor: not-allowed;
45
+ opacity: 0.7;
46
+ }
47
+
48
+ /* Sizes */
49
+ .sm {
50
+ font-size: var(--ds-font-size-xs);
51
+ padding: var(--ds-space-1) var(--ds-space-2);
52
+ }
53
+
54
+ .md {
55
+ font-size: var(--ds-font-size-sm);
56
+ padding: var(--ds-space-2) var(--ds-space-3);
57
+ }
58
+
59
+ .lg {
60
+ font-size: var(--ds-font-size-base);
61
+ padding: var(--ds-space-3) var(--ds-space-4);
62
+ }
63
+
64
+ /* Error state */
65
+ .error {
66
+ border-color: var(--ds-danger);
67
+ }
68
+
69
+ .error:focus {
70
+ border-color: var(--ds-danger);
71
+ box-shadow: 0 0 0 0.125rem var(--ds-danger-subtle);
72
+ }
73
+
74
+ .helperText {
75
+ font-family: var(--ds-font-family-base);
76
+ font-size: var(--ds-font-size-xs);
77
+ color: var(--ds-text-2);
78
+ }
79
+
80
+ .helperText.errorText {
81
+ color: var(--ds-danger);
82
+ }
@@ -0,0 +1,99 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Textarea } from './Textarea.tsx';
4
+ import { Box } from '../Box/Box.tsx';
5
+
6
+ const meta = {
7
+ title: 'Components/Textarea',
8
+ component: Textarea,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ argTypes: {
14
+ size: {
15
+ control: 'select',
16
+ options: ['sm', 'md', 'lg'],
17
+ },
18
+ error: {
19
+ control: 'boolean',
20
+ },
21
+ disabled: {
22
+ control: 'boolean',
23
+ },
24
+ },
25
+ } satisfies Meta<typeof Textarea>;
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ args: {
32
+ label: 'Bio',
33
+ placeholder: 'Tell us about yourself',
34
+ helperText: 'Brief description for your profile.',
35
+ },
36
+ };
37
+
38
+ export const WithError: Story = {
39
+ args: {
40
+ label: 'Comments',
41
+ placeholder: 'Enter your comments',
42
+ error: true,
43
+ helperText: 'This field is required.',
44
+ },
45
+ };
46
+
47
+ export const Disabled: Story = {
48
+ args: {
49
+ label: 'Notes',
50
+ placeholder: 'Read-only notes',
51
+ disabled: true,
52
+ defaultValue: 'This note cannot be edited.',
53
+ },
54
+ };
55
+
56
+ export const Small: Story = {
57
+ args: {
58
+ size: 'sm',
59
+ label: 'Small Textarea',
60
+ placeholder: 'Small textarea',
61
+ },
62
+ };
63
+
64
+ export const Large: Story = {
65
+ args: {
66
+ size: 'lg',
67
+ label: 'Large Textarea',
68
+ placeholder: 'Large textarea',
69
+ },
70
+ };
71
+
72
+ export const Gallery: Story = {
73
+ render: () => (
74
+ <Box display="flex" flexDirection="column" gap={8} width="400px">
75
+ {(['sm', 'md', 'lg'] as const).map((size) => (
76
+ <Box key={size} display="flex" flexDirection="column" gap={4}>
77
+ <Box as="h3" m={0} style={{ textTransform: 'capitalize' }}>
78
+ Size: {size}
79
+ </Box>
80
+ <Textarea size={size} label="Default" placeholder="Default placeholder" />
81
+ <Textarea
82
+ size={size}
83
+ label="With Error"
84
+ placeholder="Error placeholder"
85
+ error
86
+ helperText="This field is required"
87
+ />
88
+ <Textarea
89
+ size={size}
90
+ label="Disabled"
91
+ placeholder="Disabled placeholder"
92
+ disabled
93
+ defaultValue="Some value"
94
+ />
95
+ </Box>
96
+ ))}
97
+ </Box>
98
+ ),
99
+ };