@altruist03/ui 0.1.0 → 0.2.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 (207) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15237 -650
  3. package/dist/index.js.map +1 -1
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +21 -7
  6. package/src/ThemeProvider/ThemeContext.tsx +75 -0
  7. package/src/ThemeProvider/index.ts +7 -0
  8. package/src/assets/images/noData.webp +0 -0
  9. package/src/assets.d.ts +10 -0
  10. package/src/components/Accordion/Accordion.stories.tsx +64 -0
  11. package/src/components/Accordion/Accordion.tsx +219 -0
  12. package/src/components/Accordion/Accordion.types.ts +16 -0
  13. package/src/components/Avatar/Avatar.stories.tsx +304 -0
  14. package/src/components/Avatar/Avatar.tsx +211 -0
  15. package/src/components/Avatar/Avatar.types.ts +33 -0
  16. package/src/components/Badge/Badge.stories.tsx +143 -0
  17. package/src/components/Badge/Badge.tsx +138 -0
  18. package/src/components/Badge/Badge.types.ts +24 -0
  19. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  20. package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
  21. package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
  22. package/src/components/Button/Button.stories.tsx +457 -0
  23. package/src/components/Button/Button.tsx +202 -0
  24. package/src/components/Button/Button.types.ts +18 -0
  25. package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
  26. package/src/components/Checkbox/Checkbox.tsx +352 -0
  27. package/src/components/Checkbox/Checkbox.types.ts +123 -0
  28. package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
  29. package/src/components/CloseButton/CloseButton.tsx +117 -0
  30. package/src/components/CloseButton/CloseButton.types.ts +15 -0
  31. package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
  32. package/src/components/DataTable/DataTable.stories.tsx +233 -0
  33. package/src/components/DataTable/DataTable.tsx +968 -0
  34. package/src/components/DataTable/DataTable.types.ts +49 -0
  35. package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
  36. package/src/components/DatePicker/DatePicker.tsx +669 -0
  37. package/src/components/DatePicker/DatePicker.types.ts +105 -0
  38. package/src/components/DatePicker/DateTimePicker.tsx +35 -0
  39. package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
  40. package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
  41. package/src/components/Drawer/Drawer.stories.tsx +243 -0
  42. package/src/components/Drawer/Drawer.tsx +196 -0
  43. package/src/components/Drawer/Drawer.types.ts +60 -0
  44. package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
  45. package/src/components/FileUpload/FileUpload.tsx +216 -0
  46. package/src/components/FileUpload/FileUpload.types.ts +80 -0
  47. package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
  48. package/src/components/FilterPopover/FilterPopover.tsx +310 -0
  49. package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
  50. package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
  51. package/src/components/ImageViewer/useImagePanRotate.js +102 -0
  52. package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
  53. package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
  54. package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
  55. package/src/components/Loader/Loader.stories.tsx +95 -0
  56. package/src/components/Loader/Loader.tsx +132 -0
  57. package/src/components/Loader/Loader.types.ts +6 -0
  58. package/src/components/Menu/Menu.tsx +143 -0
  59. package/src/components/Menu/Menu.types.ts +28 -0
  60. package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
  61. package/src/components/MenuButton/MenuButton.tsx +45 -0
  62. package/src/components/MenuButton/MenuButton.types.ts +21 -0
  63. package/src/components/Modal/Modal.stories.tsx +266 -0
  64. package/src/components/Modal/Modal.tsx +399 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/types.ts +126 -0
  67. package/src/components/Pagination/Pagination.stories.tsx +167 -0
  68. package/src/components/Pagination/Pagination.tsx +266 -0
  69. package/src/components/Pagination/Pagination.types.ts +20 -0
  70. package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
  71. package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
  72. package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
  73. package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
  74. package/src/components/PhoneField/PhoneField.tsx +284 -0
  75. package/src/components/PhoneField/PhoneField.types.ts +89 -0
  76. package/src/components/PhoneField/countryCodes.ts +236 -0
  77. package/src/components/Progress/Progress.stories.tsx +83 -0
  78. package/src/components/Progress/Progress.tsx +91 -0
  79. package/src/components/Progress/Progress.types.ts +22 -0
  80. package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
  81. package/src/components/RadioButton/RadioButton.tsx +427 -0
  82. package/src/components/RadioButton/RadioButton.types.ts +117 -0
  83. package/src/components/ReportCard/ReportCard.tsx +183 -0
  84. package/src/components/ReportCard/ReportCard.types.ts +17 -0
  85. package/src/components/SectionIcon/SectionIcon.tsx +87 -0
  86. package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
  87. package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
  88. package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
  89. package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
  90. package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
  91. package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
  92. package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
  93. package/src/components/Selector/Selector.stories.tsx +382 -0
  94. package/src/components/Selector/Selector.tsx +1310 -0
  95. package/src/components/Selector/Selector.types.ts +71 -0
  96. package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
  97. package/src/components/Skeleton/Skeleton.tsx +20 -0
  98. package/src/components/Skeleton/Skeleton.types.ts +3 -0
  99. package/src/components/Spinner/Spinner.stories.tsx +84 -0
  100. package/src/components/Spinner/Spinner.tsx +79 -0
  101. package/src/components/Spinner/Spinner.types.ts +8 -0
  102. package/src/components/StatCard/StatCard.stories.tsx +125 -0
  103. package/src/components/StatCard/StatCard.tsx +167 -0
  104. package/src/components/StatCard/StatCard.types.ts +20 -0
  105. package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
  106. package/src/components/StatusBadge/StatusBadge.tsx +74 -0
  107. package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
  108. package/src/components/Stepper/Stepper.stories.tsx +85 -0
  109. package/src/components/Stepper/Stepper.tsx +138 -0
  110. package/src/components/Stepper/Stepper.types.ts +16 -0
  111. package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
  112. package/src/components/SubTabs/SubTabs.tsx +279 -0
  113. package/src/components/SubTabs/SubTabs.types.ts +44 -0
  114. package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
  115. package/src/components/SummaryCard/SummaryCard.tsx +141 -0
  116. package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
  117. package/src/components/Switch/Switch.stories.tsx +59 -0
  118. package/src/components/Switch/Switch.tsx +217 -0
  119. package/src/components/Switch/Switch.types.ts +22 -0
  120. package/src/components/Tabs/Tabs.stories.tsx +178 -0
  121. package/src/components/Tabs/Tabs.tsx +197 -0
  122. package/src/components/Tabs/Tabs.types.ts +34 -0
  123. package/src/components/TextField/TextField.stories.tsx +249 -0
  124. package/src/components/TextField/TextField.tsx +462 -0
  125. package/src/components/TextField/TextField.types.ts +141 -0
  126. package/src/components/Toast/Toast.stories.tsx +237 -0
  127. package/src/components/Toast/Toast.tsx +246 -0
  128. package/src/components/Toast/Toast.types.ts +31 -0
  129. package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
  130. package/src/components/ToggleButton/ToggleButton.tsx +267 -0
  131. package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
  132. package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
  133. package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
  134. package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
  135. package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
  136. package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
  137. package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
  138. package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
  139. package/src/components/index.ts +157 -0
  140. package/src/icons/ReportDollarIcon.tsx +71 -0
  141. package/src/icons/index.ts +27 -0
  142. package/src/index.ts +214 -0
  143. package/src/storybook/preview.tsx +64 -0
  144. package/src/theme/index.ts +4 -0
  145. package/src/theme/types.d.ts +1 -0
  146. package/src/themes/cpc.ts +4 -0
  147. package/src/themes/firebird.ts +4 -0
  148. package/src/tokens/brands/cpc/themes/dark.json +82 -0
  149. package/src/tokens/brands/cpc/themes/light.json +1 -0
  150. package/src/tokens/brands/default/brand.json +20 -0
  151. package/src/tokens/brands/firebird/themes/dark.json +28 -0
  152. package/src/tokens/brands/firebird/themes/light.json +20 -0
  153. package/src/tokens/button.ts +24 -0
  154. package/src/tokens/components/accordion.json +52 -0
  155. package/src/tokens/components/avatar.json +40 -0
  156. package/src/tokens/components/badge.json +41 -0
  157. package/src/tokens/components/breadcrumb.json +13 -0
  158. package/src/tokens/components/button.json +67 -0
  159. package/src/tokens/components/card.json +39 -0
  160. package/src/tokens/components/checkbox.json +64 -0
  161. package/src/tokens/components/closebutton.json +55 -0
  162. package/src/tokens/components/datatable.json +40 -0
  163. package/src/tokens/components/datepicker.json +99 -0
  164. package/src/tokens/components/drawer.json +23 -0
  165. package/src/tokens/components/field.json +29 -0
  166. package/src/tokens/components/fileupload.json +57 -0
  167. package/src/tokens/components/filterpopover.json +29 -0
  168. package/src/tokens/components/input.json +31 -0
  169. package/src/tokens/components/ipwhitelistfield.json +16 -0
  170. package/src/tokens/components/loader.json +15 -0
  171. package/src/tokens/components/menu.json +21 -0
  172. package/src/tokens/components/modal.json +25 -0
  173. package/src/tokens/components/pagination.json +45 -0
  174. package/src/tokens/components/phonefield.json +41 -0
  175. package/src/tokens/components/progress.json +12 -0
  176. package/src/tokens/components/radio.json +87 -0
  177. package/src/tokens/components/reportcard.json +17 -0
  178. package/src/tokens/components/selector.json +87 -0
  179. package/src/tokens/components/selectscroll.json +65 -0
  180. package/src/tokens/components/skeleton.json +6 -0
  181. package/src/tokens/components/stepper.json +38 -0
  182. package/src/tokens/components/subtabs.json +45 -0
  183. package/src/tokens/components/switch.json +55 -0
  184. package/src/tokens/components/tabs.json +38 -0
  185. package/src/tokens/components/textfield.json +59 -0
  186. package/src/tokens/components/togglebutton.json +48 -0
  187. package/src/tokens/components/uploaddropzone.json +61 -0
  188. package/src/tokens/drawer.ts +37 -0
  189. package/src/tokens/foundation/breakpoints.json +10 -0
  190. package/src/tokens/foundation/colors.json +122 -0
  191. package/src/tokens/foundation/opacity.json +16 -0
  192. package/src/tokens/foundation/radius.json +14 -0
  193. package/src/tokens/foundation/size.json +38 -0
  194. package/src/tokens/foundation/spacing.json +19 -0
  195. package/src/tokens/foundation/typography.json +43 -0
  196. package/src/tokens/index.ts +3665 -0
  197. package/src/tokens/modal.ts +42 -0
  198. package/src/tokens/semantic/color.json +148 -0
  199. package/src/tokens/semantic/field.json +28 -0
  200. package/src/tokens/semantic/opacity.json +5 -0
  201. package/src/tokens/semantic/size.json +722 -0
  202. package/src/tokens/semantic/spacing.json +27 -0
  203. package/src/tokens/semantic/typography.json +87 -0
  204. package/src/utils/scrollLock.ts +44 -0
  205. package/dist/index.css +0 -514
  206. package/dist/index.css.map +0 -1
  207. package/dist/index.d.ts +0 -289
@@ -0,0 +1,141 @@
1
+ export type TextFieldSize = 'sm' | 'md' | 'lg';
2
+
3
+ export interface TextFieldProps {
4
+ /**
5
+ * Field label shown above the input
6
+ */
7
+ label?: string;
8
+ /**
9
+ * Current value (for controlled usage)
10
+ */
11
+ value?: string;
12
+ /**
13
+ * Default value (for uncontrolled usage)
14
+ */
15
+ defaultValue?: string;
16
+ /**
17
+ * Placeholder text inside the input
18
+ */
19
+ placeholder?: string;
20
+ /**
21
+ * Helper or error text displayed below the field
22
+ */
23
+ helperText?: string;
24
+ /**
25
+ * Marks the field as required and appends "*" to the label
26
+ */
27
+ required?: boolean;
28
+ /**
29
+ * Shows error state and styles
30
+ */
31
+ error?: boolean;
32
+ /**
33
+ * Disables the field
34
+ */
35
+ disabled?: boolean;
36
+ /**
37
+ * Makes the field stretch to full container width
38
+ */
39
+ fullWidth?: boolean;
40
+ /**
41
+ * Input type (text, number, password, etc.)
42
+ */
43
+ type?: React.InputHTMLAttributes<HTMLInputElement>['type'];
44
+ /**
45
+ * When true, renders a multiline textarea-style field
46
+ */
47
+ multiline?: boolean;
48
+ /**
49
+ * Number of visible text rows when `multiline` is true
50
+ */
51
+ rows?: number;
52
+ /**
53
+ * Maximum number of rows when multiline
54
+ */
55
+ maxRows?: number;
56
+ /**
57
+ * Minimum number of rows when multiline
58
+ */
59
+ minRows?: number;
60
+ /**
61
+ * Size of the text field
62
+ */
63
+ size?: TextFieldSize;
64
+ /**
65
+ * Background color variant
66
+ */
67
+ color?: 'grey' | 'white';
68
+ /**
69
+ * Input name attribute
70
+ */
71
+ name?: string;
72
+ /**
73
+ * HTML id attribute (used to link label and input)
74
+ */
75
+ id?: string;
76
+ /**
77
+ * Auto focus the input on mount
78
+ */
79
+ autoFocus?: boolean;
80
+ /**
81
+ * Auto complete attribute
82
+ */
83
+ autoComplete?: string;
84
+ /**
85
+ * Loading state
86
+ */
87
+ skeletonLoading?: boolean;
88
+
89
+ /**
90
+ * Additional input props
91
+ */
92
+ inputProps?:
93
+ | React.InputHTMLAttributes<HTMLInputElement>
94
+ | React.TextareaHTMLAttributes<HTMLTextAreaElement>;
95
+ /**
96
+ * Input ref
97
+ */
98
+ inputRef?: React.Ref<HTMLInputElement | HTMLTextAreaElement>;
99
+ /**
100
+ * onChange handler
101
+ */
102
+ onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
103
+ /**
104
+ * onBlur handler
105
+ */
106
+ onBlur?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
107
+ /**
108
+ * onFocus handler
109
+ */
110
+ onFocus?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
111
+ /**
112
+ * Fixed prefix rendered flush inside the input on the left (e.g. currency code).
113
+ * Uses a full-height grey segment with a right divider; takes precedence over startIcon.
114
+ */
115
+ prefix?: React.ReactNode;
116
+ /**
117
+ * Fixed suffix rendered flush inside the input on the right (e.g. unit).
118
+ * Uses a full-height grey segment with a left divider; takes precedence over endIcon.
119
+ */
120
+ suffix?: React.ReactNode;
121
+ /**
122
+ * Icon component rendered at the start (left) of the input.
123
+ * Only visible when passed. Takes precedence over InputProps.startAdornment.
124
+ * Pass any SVG component — MUI icon (e.g. <SearchIcon />) or firebird-icon-lib (e.g. <Icon name="search" />).
125
+ */
126
+ startIcon?: React.ReactNode;
127
+ /**
128
+ * Icon component rendered at the end (right) of the input.
129
+ * Only visible when passed. Takes precedence over InputProps.endAdornment.
130
+ * Pass any SVG component — MUI icon (e.g. <ClearIcon />) or firebird-icon-lib (e.g. <Icon name="close" />).
131
+ */
132
+ endIcon?: React.ReactNode;
133
+ /**
134
+ * Props applied to the Input element. Supports endAdornment and startAdornment for icons/buttons.
135
+ * startIcon/endIcon take precedence when both are provided.
136
+ */
137
+ InputProps?: {
138
+ endAdornment?: React.ReactNode;
139
+ startAdornment?: React.ReactNode;
140
+ };
141
+ }
@@ -0,0 +1,237 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Toast, ToastProvider, useToastDS } from './Toast';
4
+ import { Button } from '../Button/Button';
5
+
6
+ const meta: Meta<typeof Toast> = {
7
+ title: 'Design System/Toast',
8
+ component: Toast,
9
+ tags: [],
10
+ decorators: [(Story) => <Story />],
11
+ args: {
12
+ message: 'Something went wrong. Please try again after some time.',
13
+ variant: 'error',
14
+ duration: 0,
15
+ },
16
+ argTypes: {
17
+ variant: {
18
+ control: { type: 'select' },
19
+ options: ['success', 'warning', 'error', 'info'],
20
+ description: 'Toast variant — controls the icon and accent color',
21
+ table: {
22
+ type: { summary: 'success | warning | error | info' },
23
+ defaultValue: { summary: 'info' },
24
+ },
25
+ },
26
+ message: {
27
+ control: { type: 'text' },
28
+ description: 'Message displayed inside the toast',
29
+ table: {
30
+ type: { summary: 'string' },
31
+ },
32
+ },
33
+ duration: {
34
+ control: { type: 'number' },
35
+ description: 'Auto-dismiss delay in ms. Set to 0 to disable auto-dismiss.',
36
+ table: {
37
+ type: { summary: 'number' },
38
+ defaultValue: { summary: '4000' },
39
+ },
40
+ },
41
+ onClose: { action: 'closed' },
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+
47
+ type Story = StoryObj<typeof Toast>;
48
+
49
+ // ─── Playground ───────────────────────────────────────────────────────────────
50
+
51
+ export const Playground: Story = {
52
+ args: {
53
+ message: 'Something went wrong. Please try again after some time.',
54
+ variant: 'error',
55
+ duration: 0,
56
+ },
57
+ render: (args) => (
58
+ // key forces remount when args change so the toast is always visible
59
+ <div style={{ padding: 24 }}>
60
+ <Toast key={`${args.variant}-${args.message}`} {...args} />
61
+ </div>
62
+ ),
63
+ };
64
+
65
+ // ─── All Variants ─────────────────────────────────────────────────────────────
66
+
67
+ export const AllVariants: Story = {
68
+ render: () => (
69
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12, padding: 24 }}>
70
+ <Toast
71
+ variant="error"
72
+ message="Something went wrong. Please try again after some time."
73
+ duration={0}
74
+ />
75
+ <Toast variant="success" message="Fleet created successfully." duration={0} />
76
+ <Toast
77
+ variant="warning"
78
+ message="Your session is about to expire. Please save your work."
79
+ duration={0}
80
+ />
81
+ <Toast variant="info" message="A new version is available. Refresh to update." duration={0} />
82
+ </div>
83
+ ),
84
+ };
85
+
86
+ // ─── Interactive with ToastProvider ──────────────────────────────────────────
87
+
88
+ const TriggerButtons = () => {
89
+ const { showToast } = useToastDS();
90
+ return (
91
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
92
+ <Button
93
+ variant="red"
94
+ size="sm"
95
+ onClick={() =>
96
+ showToast('Something went wrong. Please try again after some time.', 'error')
97
+ }
98
+ >
99
+ Show Error
100
+ </Button>
101
+ <Button
102
+ variant="blue"
103
+ size="sm"
104
+ onClick={() => showToast('Fleet created successfully.', 'success')}
105
+ >
106
+ Show Success
107
+ </Button>
108
+ <Button
109
+ variant="grey"
110
+ size="sm"
111
+ onClick={() =>
112
+ showToast('Your session is about to expire. Please save your work.', 'warning')
113
+ }
114
+ >
115
+ Show Warning
116
+ </Button>
117
+ <Button
118
+ variant="blue"
119
+ size="sm"
120
+ onClick={() => showToast('A new version is available. Refresh to update.', 'info')}
121
+ >
122
+ Show Info
123
+ </Button>
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export const WithProvider: Story = {
129
+ render: () => (
130
+ <ToastProvider position="top-right">
131
+ <div style={{ padding: 24 }}>
132
+ <p style={{ marginBottom: 16, fontSize: 14, color: '#64748B' }}>
133
+ Click a button to trigger a toast notification at the top-right corner.
134
+ </p>
135
+ <TriggerButtons />
136
+ </div>
137
+ </ToastProvider>
138
+ ),
139
+ };
140
+
141
+ // ─── All Positions ────────────────────────────────────────────────────────────
142
+
143
+ const PositionDemo = ({ position }: { position: string }) => {
144
+ const { showToast } = useToastDS();
145
+ return (
146
+ <Button
147
+ variant="blue"
148
+ size="sm"
149
+ onClick={() => showToast(`Toast at ${position}`, 'info', 3000)}
150
+ >
151
+ {position}
152
+ </Button>
153
+ );
154
+ };
155
+
156
+ export const AllPositions: Story = {
157
+ render: () => {
158
+ const positions = [
159
+ 'top-right',
160
+ 'top-left',
161
+ 'top-center',
162
+ 'bottom-right',
163
+ 'bottom-left',
164
+ 'bottom-center',
165
+ ] as const;
166
+
167
+ return (
168
+ <div style={{ padding: 24 }}>
169
+ <p style={{ marginBottom: 16, fontSize: 14, color: '#64748B' }}>
170
+ Each button triggers a toast at a different screen position.
171
+ </p>
172
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
173
+ {positions.map((pos) => (
174
+ <ToastProvider key={pos} position={pos}>
175
+ <PositionDemo position={pos} />
176
+ </ToastProvider>
177
+ ))}
178
+ </div>
179
+ </div>
180
+ );
181
+ },
182
+ };
183
+
184
+ // ─── Showcase ─────────────────────────────────────────────────────────────────
185
+
186
+ export const Showcase: Story = {
187
+ render: () => (
188
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 32, padding: 24 }}>
189
+ {/* Variants */}
190
+ <div>
191
+ <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 14, color: '#1E293B' }}>
192
+ Variants
193
+ </h3>
194
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
195
+ <Toast
196
+ variant="error"
197
+ message="Something went wrong. Please try again after some time."
198
+ duration={0}
199
+ />
200
+ <Toast variant="success" message="Fleet created successfully." duration={0} />
201
+ <Toast
202
+ variant="warning"
203
+ message="Your session is about to expire. Please save your work."
204
+ duration={0}
205
+ />
206
+ <Toast
207
+ variant="info"
208
+ message="A new version is available. Refresh to update."
209
+ duration={0}
210
+ />
211
+ </div>
212
+ </div>
213
+
214
+ {/* Long message */}
215
+ <div>
216
+ <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 14, color: '#1E293B' }}>
217
+ Long Message
218
+ </h3>
219
+ <Toast
220
+ variant="warning"
221
+ message="Your account has limited permissions. Contact your administrator to request access to the Fleet Management module before proceeding."
222
+ duration={0}
223
+ />
224
+ </div>
225
+
226
+ {/* Interactive */}
227
+ <div>
228
+ <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 14, color: '#1E293B' }}>
229
+ Interactive (auto-dismiss after 3s)
230
+ </h3>
231
+ <ToastProvider position="top-right">
232
+ <TriggerButtons />
233
+ </ToastProvider>
234
+ </div>
235
+ </div>
236
+ ),
237
+ };
@@ -0,0 +1,246 @@
1
+ import React, {
2
+ ReactNode,
3
+ cloneElement,
4
+ isValidElement,
5
+ createContext,
6
+ useContext,
7
+ useState,
8
+ useEffect,
9
+ useRef,
10
+ useCallback,
11
+ } from 'react';
12
+ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
13
+ import HighlightOffIcon from '@mui/icons-material/HighlightOff';
14
+ import WarningAmberIcon from '@mui/icons-material/WarningAmber';
15
+ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
16
+ import CloseIcon from '@mui/icons-material/Close';
17
+ import type {
18
+ ToastVariant,
19
+ ToastPosition,
20
+ ToastItem,
21
+ ToastProps,
22
+ ToastContainerProps,
23
+ ToastContextValue,
24
+ } from './Toast.types';
25
+
26
+ // ─── Variant config ──────────────────────────────────────────────────────────
27
+
28
+ interface VariantConfig {
29
+ icon: ReactNode;
30
+ iconBg: string;
31
+ iconColor: string;
32
+ accentColor: string;
33
+ bgTint: string;
34
+ }
35
+
36
+ const VARIANT_CONFIG: Record<ToastVariant, VariantConfig> = {
37
+ success: {
38
+ icon: <CheckCircleOutlineIcon />,
39
+ iconBg: '#DCFCE7',
40
+ iconColor: '#16A34A',
41
+ accentColor: '#16A34A',
42
+ bgTint: '#F0FDF4',
43
+ },
44
+ error: {
45
+ icon: <HighlightOffIcon />,
46
+ iconBg: '#FEE2E2',
47
+ iconColor: '#DC2626',
48
+ accentColor: '#DC2626',
49
+ bgTint: '#FFF5F5',
50
+ },
51
+ warning: {
52
+ icon: <WarningAmberIcon />,
53
+ iconBg: '#FEF9C3',
54
+ iconColor: '#CA8A04',
55
+ accentColor: '#CA8A04',
56
+ bgTint: '#FFFBEB',
57
+ },
58
+ info: {
59
+ icon: <InfoOutlinedIcon />,
60
+ iconBg: '#DBEAFE',
61
+ iconColor: '#2563EB',
62
+ accentColor: '#2563EB',
63
+ bgTint: '#EFF6FF',
64
+ },
65
+ };
66
+
67
+ // ─── Position styles ──────────────────────────────────────────────────────────
68
+
69
+ const POSITION_STYLES: Record<ToastPosition, React.CSSProperties> = {
70
+ 'top-right': { top: 20, right: 20, alignItems: 'flex-end' },
71
+ 'top-left': { top: 20, left: 20, alignItems: 'flex-start' },
72
+ 'top-center': { top: 20, left: '50%', transform: 'translateX(-50%)', alignItems: 'center' },
73
+ 'bottom-right': { bottom: 20, right: 20, alignItems: 'flex-end' },
74
+ 'bottom-left': { bottom: 20, left: 20, alignItems: 'flex-start' },
75
+ 'bottom-center': { bottom: 20, left: '50%', transform: 'translateX(-50%)', alignItems: 'center' },
76
+ };
77
+
78
+ // ─── Single Toast item ────────────────────────────────────────────────────────
79
+
80
+ export const Toast = ({ message, variant = 'info', duration = 4000, onClose }: ToastProps) => {
81
+ const [visible, setVisible] = useState(true);
82
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
83
+
84
+ const dismiss = useCallback(() => {
85
+ setVisible(false);
86
+ onClose?.();
87
+ }, [onClose]);
88
+
89
+ useEffect(() => {
90
+ if (duration > 0) {
91
+ timerRef.current = setTimeout(dismiss, duration);
92
+ }
93
+ return () => {
94
+ if (timerRef.current) clearTimeout(timerRef.current);
95
+ };
96
+ }, [duration, dismiss]);
97
+
98
+ if (!visible) return null;
99
+
100
+ const config = VARIANT_CONFIG[variant];
101
+
102
+ const iconEl =
103
+ isValidElement(config.icon) ?
104
+ cloneElement(config.icon as React.ReactElement<{ style?: React.CSSProperties }>, {
105
+ style: { fontSize: 20, color: config.iconColor, display: 'block' },
106
+ })
107
+ : config.icon;
108
+
109
+ return (
110
+ <div
111
+ role="alert"
112
+ aria-live="assertive"
113
+ style={{
114
+ display: 'flex',
115
+ alignItems: 'center',
116
+ gap: 12,
117
+ background: config.bgTint,
118
+ border: `1px solid ${config.accentColor}33`,
119
+ borderLeft: `4px solid ${config.accentColor}`,
120
+ borderRadius: 8,
121
+ padding: '12px 14px',
122
+ boxShadow: `0 6px 24px rgba(0, 0, 0, 0.12), 0 0 0 1px ${config.accentColor}1A`,
123
+ minWidth: 280,
124
+ maxWidth: 440,
125
+ fontFamily: '"Roboto", sans-serif',
126
+ pointerEvents: 'all',
127
+ }}
128
+ >
129
+ {/* Icon circle */}
130
+ <span
131
+ style={{
132
+ flexShrink: 0,
133
+ width: 36,
134
+ height: 36,
135
+ borderRadius: '50%',
136
+ background: config.iconBg,
137
+ display: 'flex',
138
+ alignItems: 'center',
139
+ justifyContent: 'center',
140
+ }}
141
+ >
142
+ {iconEl}
143
+ </span>
144
+
145
+ {/* Message */}
146
+ <span
147
+ style={{
148
+ flex: 1,
149
+ fontSize: 14,
150
+ fontWeight: 600,
151
+ lineHeight: 1.5,
152
+ color: '#0F172A',
153
+ wordBreak: 'break-word',
154
+ letterSpacing: '0.01em',
155
+ }}
156
+ >
157
+ {message}
158
+ </span>
159
+
160
+ {/* Close button */}
161
+ <button
162
+ aria-label="Close notification"
163
+ onClick={dismiss}
164
+ style={{
165
+ flexShrink: 0,
166
+ display: 'flex',
167
+ alignItems: 'center',
168
+ justifyContent: 'center',
169
+ background: 'none',
170
+ border: 'none',
171
+ cursor: 'pointer',
172
+ padding: 2,
173
+ borderRadius: 4,
174
+ color: '#9CA3AF',
175
+ lineHeight: 0,
176
+ }}
177
+ >
178
+ <CloseIcon style={{ fontSize: 16, color: '#9CA3AF' }} />
179
+ </button>
180
+ </div>
181
+ );
182
+ };
183
+
184
+ // ─── Toast Container ──────────────────────────────────────────────────────────
185
+
186
+ interface InternalToastItem extends ToastItem {
187
+ key: string;
188
+ }
189
+
190
+ const ToastContext = createContext<ToastContextValue | null>(null);
191
+
192
+ const noopToast: ToastContextValue = { showToast: () => {} };
193
+
194
+ export const useToastDS = (): ToastContextValue => {
195
+ const ctx = useContext(ToastContext);
196
+ return ctx ?? noopToast;
197
+ };
198
+
199
+ export const ToastProvider = ({
200
+ children,
201
+ position = 'top-right',
202
+ }: React.PropsWithChildren<ToastContainerProps>) => {
203
+ const [toasts, setToasts] = useState<InternalToastItem[]>([]);
204
+
205
+ const showToast = useCallback(
206
+ (message: string, variant: ToastVariant = 'info', duration = 4000) => {
207
+ const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2)}`;
208
+ setToasts((prev) => [...prev, { id, key: id, message, variant, duration }]);
209
+ },
210
+ []
211
+ );
212
+
213
+ const removeToast = useCallback((id: string) => {
214
+ setToasts((prev) => prev.filter((t) => t.id !== id));
215
+ }, []);
216
+
217
+ const posStyle = POSITION_STYLES[position];
218
+
219
+ return (
220
+ <ToastContext.Provider value={{ showToast }}>
221
+ {children}
222
+ <div
223
+ aria-label="Notifications"
224
+ style={{
225
+ position: 'fixed',
226
+ zIndex: 9999,
227
+ display: 'flex',
228
+ flexDirection: 'column',
229
+ gap: 10,
230
+ pointerEvents: 'none',
231
+ ...posStyle,
232
+ }}
233
+ >
234
+ {toasts.map((toast) => (
235
+ <Toast
236
+ key={toast.key}
237
+ message={toast.message}
238
+ variant={toast.variant}
239
+ duration={toast.duration}
240
+ onClose={() => removeToast(toast.id)}
241
+ />
242
+ ))}
243
+ </div>
244
+ </ToastContext.Provider>
245
+ );
246
+ };
@@ -0,0 +1,31 @@
1
+ export type ToastVariant = 'success' | 'warning' | 'error' | 'info';
2
+
3
+ export type ToastPosition =
4
+ | 'top-right'
5
+ | 'top-left'
6
+ | 'top-center'
7
+ | 'bottom-right'
8
+ | 'bottom-left'
9
+ | 'bottom-center';
10
+
11
+ export interface ToastItem {
12
+ id: string;
13
+ message: string;
14
+ variant: ToastVariant;
15
+ duration: number;
16
+ }
17
+
18
+ export interface ToastProps {
19
+ message: string;
20
+ variant?: ToastVariant;
21
+ duration?: number;
22
+ onClose?: () => void;
23
+ }
24
+
25
+ export interface ToastContainerProps {
26
+ position?: ToastPosition;
27
+ }
28
+
29
+ export interface ToastContextValue {
30
+ showToast: (message: string, variant?: ToastVariant, duration?: number) => void;
31
+ }