@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.
- package/assets/images/noData.webp +0 -0
- package/dist/index.js +15237 -650
- package/dist/index.js.map +1 -1
- package/dist/noData-XPRHKMFR.webp +0 -0
- package/package.json +21 -7
- package/src/ThemeProvider/ThemeContext.tsx +75 -0
- package/src/ThemeProvider/index.ts +7 -0
- package/src/assets/images/noData.webp +0 -0
- package/src/assets.d.ts +10 -0
- package/src/components/Accordion/Accordion.stories.tsx +64 -0
- package/src/components/Accordion/Accordion.tsx +219 -0
- package/src/components/Accordion/Accordion.types.ts +16 -0
- package/src/components/Avatar/Avatar.stories.tsx +304 -0
- package/src/components/Avatar/Avatar.tsx +211 -0
- package/src/components/Avatar/Avatar.types.ts +33 -0
- package/src/components/Badge/Badge.stories.tsx +143 -0
- package/src/components/Badge/Badge.tsx +138 -0
- package/src/components/Badge/Badge.types.ts +24 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
- package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
- package/src/components/Button/Button.stories.tsx +457 -0
- package/src/components/Button/Button.tsx +202 -0
- package/src/components/Button/Button.types.ts +18 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
- package/src/components/Checkbox/Checkbox.tsx +352 -0
- package/src/components/Checkbox/Checkbox.types.ts +123 -0
- package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
- package/src/components/CloseButton/CloseButton.tsx +117 -0
- package/src/components/CloseButton/CloseButton.types.ts +15 -0
- package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
- package/src/components/DataTable/DataTable.stories.tsx +233 -0
- package/src/components/DataTable/DataTable.tsx +968 -0
- package/src/components/DataTable/DataTable.types.ts +49 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
- package/src/components/DatePicker/DatePicker.tsx +669 -0
- package/src/components/DatePicker/DatePicker.types.ts +105 -0
- package/src/components/DatePicker/DateTimePicker.tsx +35 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
- package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
- package/src/components/Drawer/Drawer.stories.tsx +243 -0
- package/src/components/Drawer/Drawer.tsx +196 -0
- package/src/components/Drawer/Drawer.types.ts +60 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
- package/src/components/FileUpload/FileUpload.tsx +216 -0
- package/src/components/FileUpload/FileUpload.types.ts +80 -0
- package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
- package/src/components/FilterPopover/FilterPopover.tsx +310 -0
- package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
- package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
- package/src/components/ImageViewer/useImagePanRotate.js +102 -0
- package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
- package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
- package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
- package/src/components/Loader/Loader.stories.tsx +95 -0
- package/src/components/Loader/Loader.tsx +132 -0
- package/src/components/Loader/Loader.types.ts +6 -0
- package/src/components/Menu/Menu.tsx +143 -0
- package/src/components/Menu/Menu.types.ts +28 -0
- package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
- package/src/components/MenuButton/MenuButton.tsx +45 -0
- package/src/components/MenuButton/MenuButton.types.ts +21 -0
- package/src/components/Modal/Modal.stories.tsx +266 -0
- package/src/components/Modal/Modal.tsx +399 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Modal/types.ts +126 -0
- package/src/components/Pagination/Pagination.stories.tsx +167 -0
- package/src/components/Pagination/Pagination.tsx +266 -0
- package/src/components/Pagination/Pagination.types.ts +20 -0
- package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
- package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
- package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
- package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
- package/src/components/PhoneField/PhoneField.tsx +284 -0
- package/src/components/PhoneField/PhoneField.types.ts +89 -0
- package/src/components/PhoneField/countryCodes.ts +236 -0
- package/src/components/Progress/Progress.stories.tsx +83 -0
- package/src/components/Progress/Progress.tsx +91 -0
- package/src/components/Progress/Progress.types.ts +22 -0
- package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
- package/src/components/RadioButton/RadioButton.tsx +427 -0
- package/src/components/RadioButton/RadioButton.types.ts +117 -0
- package/src/components/ReportCard/ReportCard.tsx +183 -0
- package/src/components/ReportCard/ReportCard.types.ts +17 -0
- package/src/components/SectionIcon/SectionIcon.tsx +87 -0
- package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
- package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
- package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
- package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
- package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
- package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
- package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
- package/src/components/Selector/Selector.stories.tsx +382 -0
- package/src/components/Selector/Selector.tsx +1310 -0
- package/src/components/Selector/Selector.types.ts +71 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
- package/src/components/Skeleton/Skeleton.tsx +20 -0
- package/src/components/Skeleton/Skeleton.types.ts +3 -0
- package/src/components/Spinner/Spinner.stories.tsx +84 -0
- package/src/components/Spinner/Spinner.tsx +79 -0
- package/src/components/Spinner/Spinner.types.ts +8 -0
- package/src/components/StatCard/StatCard.stories.tsx +125 -0
- package/src/components/StatCard/StatCard.tsx +167 -0
- package/src/components/StatCard/StatCard.types.ts +20 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
- package/src/components/StatusBadge/StatusBadge.tsx +74 -0
- package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
- package/src/components/Stepper/Stepper.stories.tsx +85 -0
- package/src/components/Stepper/Stepper.tsx +138 -0
- package/src/components/Stepper/Stepper.types.ts +16 -0
- package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
- package/src/components/SubTabs/SubTabs.tsx +279 -0
- package/src/components/SubTabs/SubTabs.types.ts +44 -0
- package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
- package/src/components/SummaryCard/SummaryCard.tsx +141 -0
- package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
- package/src/components/Switch/Switch.stories.tsx +59 -0
- package/src/components/Switch/Switch.tsx +217 -0
- package/src/components/Switch/Switch.types.ts +22 -0
- package/src/components/Tabs/Tabs.stories.tsx +178 -0
- package/src/components/Tabs/Tabs.tsx +197 -0
- package/src/components/Tabs/Tabs.types.ts +34 -0
- package/src/components/TextField/TextField.stories.tsx +249 -0
- package/src/components/TextField/TextField.tsx +462 -0
- package/src/components/TextField/TextField.types.ts +141 -0
- package/src/components/Toast/Toast.stories.tsx +237 -0
- package/src/components/Toast/Toast.tsx +246 -0
- package/src/components/Toast/Toast.types.ts +31 -0
- package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
- package/src/components/ToggleButton/ToggleButton.tsx +267 -0
- package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
- package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
- package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
- package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
- package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
- package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
- package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
- package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
- package/src/components/index.ts +157 -0
- package/src/icons/ReportDollarIcon.tsx +71 -0
- package/src/icons/index.ts +27 -0
- package/src/index.ts +214 -0
- package/src/storybook/preview.tsx +64 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/types.d.ts +1 -0
- package/src/themes/cpc.ts +4 -0
- package/src/themes/firebird.ts +4 -0
- package/src/tokens/brands/cpc/themes/dark.json +82 -0
- package/src/tokens/brands/cpc/themes/light.json +1 -0
- package/src/tokens/brands/default/brand.json +20 -0
- package/src/tokens/brands/firebird/themes/dark.json +28 -0
- package/src/tokens/brands/firebird/themes/light.json +20 -0
- package/src/tokens/button.ts +24 -0
- package/src/tokens/components/accordion.json +52 -0
- package/src/tokens/components/avatar.json +40 -0
- package/src/tokens/components/badge.json +41 -0
- package/src/tokens/components/breadcrumb.json +13 -0
- package/src/tokens/components/button.json +67 -0
- package/src/tokens/components/card.json +39 -0
- package/src/tokens/components/checkbox.json +64 -0
- package/src/tokens/components/closebutton.json +55 -0
- package/src/tokens/components/datatable.json +40 -0
- package/src/tokens/components/datepicker.json +99 -0
- package/src/tokens/components/drawer.json +23 -0
- package/src/tokens/components/field.json +29 -0
- package/src/tokens/components/fileupload.json +57 -0
- package/src/tokens/components/filterpopover.json +29 -0
- package/src/tokens/components/input.json +31 -0
- package/src/tokens/components/ipwhitelistfield.json +16 -0
- package/src/tokens/components/loader.json +15 -0
- package/src/tokens/components/menu.json +21 -0
- package/src/tokens/components/modal.json +25 -0
- package/src/tokens/components/pagination.json +45 -0
- package/src/tokens/components/phonefield.json +41 -0
- package/src/tokens/components/progress.json +12 -0
- package/src/tokens/components/radio.json +87 -0
- package/src/tokens/components/reportcard.json +17 -0
- package/src/tokens/components/selector.json +87 -0
- package/src/tokens/components/selectscroll.json +65 -0
- package/src/tokens/components/skeleton.json +6 -0
- package/src/tokens/components/stepper.json +38 -0
- package/src/tokens/components/subtabs.json +45 -0
- package/src/tokens/components/switch.json +55 -0
- package/src/tokens/components/tabs.json +38 -0
- package/src/tokens/components/textfield.json +59 -0
- package/src/tokens/components/togglebutton.json +48 -0
- package/src/tokens/components/uploaddropzone.json +61 -0
- package/src/tokens/drawer.ts +37 -0
- package/src/tokens/foundation/breakpoints.json +10 -0
- package/src/tokens/foundation/colors.json +122 -0
- package/src/tokens/foundation/opacity.json +16 -0
- package/src/tokens/foundation/radius.json +14 -0
- package/src/tokens/foundation/size.json +38 -0
- package/src/tokens/foundation/spacing.json +19 -0
- package/src/tokens/foundation/typography.json +43 -0
- package/src/tokens/index.ts +3665 -0
- package/src/tokens/modal.ts +42 -0
- package/src/tokens/semantic/color.json +148 -0
- package/src/tokens/semantic/field.json +28 -0
- package/src/tokens/semantic/opacity.json +5 -0
- package/src/tokens/semantic/size.json +722 -0
- package/src/tokens/semantic/spacing.json +27 -0
- package/src/tokens/semantic/typography.json +87 -0
- package/src/utils/scrollLock.ts +44 -0
- package/dist/index.css +0 -514
- package/dist/index.css.map +0 -1
- 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
|
+
}
|