@altruist03/ui 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/noData.webp +0 -0
- package/dist/index.js +15318 -0
- package/dist/index.js.map +1 -0
- package/dist/noData-XPRHKMFR.webp +0 -0
- package/package.json +51 -4
- 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/README.md +0 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export type SelectorOption = string | { label?: string; [key: string]: unknown };
|
|
4
|
+
|
|
5
|
+
// ----- Unified props: multiple=false => value T|null, multiple=true => value T[] -----
|
|
6
|
+
export interface SelectorProps<T extends SelectorOption> {
|
|
7
|
+
/** When true, multi-select with checkboxes and chips; when false, single select. */
|
|
8
|
+
multiple?: boolean;
|
|
9
|
+
options: T[];
|
|
10
|
+
/** Single: T | null. Multiple: T[]. */
|
|
11
|
+
value: T | null | T[];
|
|
12
|
+
/** Single: (value: T | null) => void. Multiple: (value: T[]) => void. */
|
|
13
|
+
onChange?: (value: T | null | T[]) => void;
|
|
14
|
+
label?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
error?: boolean | string;
|
|
18
|
+
helperText?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
21
|
+
/** Background color variant */
|
|
22
|
+
color?: 'grey' | 'white';
|
|
23
|
+
noOptionsText?: string;
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
loadingText?: string;
|
|
26
|
+
isOptionDisabled?: (option: T) => boolean;
|
|
27
|
+
getOptionLabel?: (option: T) => string;
|
|
28
|
+
renderOption?: (option: T, state: { index: number }) => React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Group options by a string key (similar to MUI Autocomplete `groupBy`).
|
|
31
|
+
* When provided, the listbox will render grouped sections.
|
|
32
|
+
*/
|
|
33
|
+
groupBy?: (option: T) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Custom group renderer (similar to MUI Autocomplete `renderGroup`).
|
|
36
|
+
* Called only when `groupBy` is provided.
|
|
37
|
+
* The heading div with background is always rendered by Selector; return only the list content (e.g. ul with children).
|
|
38
|
+
*/
|
|
39
|
+
renderGroup?: (params: {
|
|
40
|
+
key: string;
|
|
41
|
+
group: string;
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
}) => React.ReactNode;
|
|
44
|
+
id?: string;
|
|
45
|
+
/** Only when multiple: max chips shown before "+N". -1 = no limit. */
|
|
46
|
+
limitTags?: number;
|
|
47
|
+
/** Show clear button on hover/select. Default true. */
|
|
48
|
+
clearable?: boolean;
|
|
49
|
+
filterOptions?: (
|
|
50
|
+
options: T[],
|
|
51
|
+
state: { inputValue: string; getOptionLabel: (option: T) => string }
|
|
52
|
+
) => T[];
|
|
53
|
+
/** Handler for scroll events on the listbox */
|
|
54
|
+
onListboxScroll?: (event: React.UIEvent<HTMLUListElement>) => void;
|
|
55
|
+
/** Called when the dropdown opens or closes */
|
|
56
|
+
onOpenChange?: (open: boolean) => void;
|
|
57
|
+
/** Optional external trigger for opening/closing the dropdown. */
|
|
58
|
+
open?: boolean;
|
|
59
|
+
/** Loading state - shows skeleton instead of component */
|
|
60
|
+
skeletonLoading?: boolean;
|
|
61
|
+
onInput?: React.FormEventHandler<HTMLInputElement>;
|
|
62
|
+
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
|
63
|
+
/** When true, render country flag (from option.iso) before each option label. */
|
|
64
|
+
showFlag?: boolean;
|
|
65
|
+
/** Browser autocomplete hint for the underlying input. */
|
|
66
|
+
autoComplete?: string;
|
|
67
|
+
/** Minimum width (px) for the dropdown listbox. */
|
|
68
|
+
listboxMinWidth?: number;
|
|
69
|
+
/** Custom equality check between an option and a selected value. Defaults to reference equality. */
|
|
70
|
+
isOptionEqualToValue?: (option: T, value: T) => boolean;
|
|
71
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Skeleton } from "./Skeleton";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Skeleton> = {
|
|
5
|
+
title: "Design System/Skeleton",
|
|
6
|
+
component: Skeleton,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
decorators: [
|
|
9
|
+
(Story) => (
|
|
10
|
+
<div style={{ padding: "24px", width: "520px", maxWidth: "100%" }}>
|
|
11
|
+
<Story />
|
|
12
|
+
</div>
|
|
13
|
+
),
|
|
14
|
+
],
|
|
15
|
+
args: {
|
|
16
|
+
height: 20,
|
|
17
|
+
width: "100%",
|
|
18
|
+
borderRadius: 8,
|
|
19
|
+
enableAnimation: true,
|
|
20
|
+
},
|
|
21
|
+
argTypes: {
|
|
22
|
+
count: {
|
|
23
|
+
control: { type: "number", min: 1 },
|
|
24
|
+
description: "Number of skeleton placeholders to render",
|
|
25
|
+
table: { type: { summary: "number" } },
|
|
26
|
+
},
|
|
27
|
+
height: {
|
|
28
|
+
control: { type: "number" },
|
|
29
|
+
description: "Height (px when number)",
|
|
30
|
+
table: { type: { summary: "string | number" } },
|
|
31
|
+
},
|
|
32
|
+
width: {
|
|
33
|
+
control: { type: "text" },
|
|
34
|
+
description: "Width (number or CSS string)",
|
|
35
|
+
table: { type: { summary: "string | number" } },
|
|
36
|
+
},
|
|
37
|
+
borderRadius: {
|
|
38
|
+
control: { type: "text" },
|
|
39
|
+
description: "Border radius (number or CSS string)",
|
|
40
|
+
table: { type: { summary: "string | number" } },
|
|
41
|
+
},
|
|
42
|
+
circle: {
|
|
43
|
+
control: { type: "boolean" },
|
|
44
|
+
description: "Render as a circle (useful for avatars)",
|
|
45
|
+
table: { type: { summary: "boolean" } },
|
|
46
|
+
},
|
|
47
|
+
enableAnimation: {
|
|
48
|
+
control: { type: "boolean" },
|
|
49
|
+
description: "Enable/disable shimmer animation",
|
|
50
|
+
table: { type: { summary: "boolean" } },
|
|
51
|
+
},
|
|
52
|
+
duration: {
|
|
53
|
+
control: { type: "number", min: 0 },
|
|
54
|
+
description: "Animation duration (seconds)",
|
|
55
|
+
table: { type: { summary: "number" } },
|
|
56
|
+
},
|
|
57
|
+
direction: {
|
|
58
|
+
control: { type: "radio" },
|
|
59
|
+
options: ["ltr", "rtl"],
|
|
60
|
+
description: "Animation direction",
|
|
61
|
+
table: { type: { summary: '"ltr" | "rtl"' } },
|
|
62
|
+
},
|
|
63
|
+
inline: {
|
|
64
|
+
control: { type: "boolean" },
|
|
65
|
+
description: "Render inline (useful alongside text)",
|
|
66
|
+
table: { type: { summary: "boolean" } },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default meta;
|
|
72
|
+
|
|
73
|
+
type Story = StoryObj<typeof Skeleton>;
|
|
74
|
+
|
|
75
|
+
export const Playground: Story = {
|
|
76
|
+
args: {},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const ShowcaseComponent = () => {
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
82
|
+
{/* Card-sized block (matches dashboard usage) */}
|
|
83
|
+
<div>
|
|
84
|
+
<div style={{ fontSize: "12px", marginBottom: "8px" }}>
|
|
85
|
+
Card placeholder
|
|
86
|
+
</div>
|
|
87
|
+
<Skeleton height={108} borderRadius={16} />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Text lines */}
|
|
91
|
+
<div>
|
|
92
|
+
<div style={{ fontSize: "12px", marginBottom: "8px" }}>Text lines</div>
|
|
93
|
+
<Skeleton height={14} width="100%" style={{ marginBottom: 8 }} />
|
|
94
|
+
<Skeleton height={14} width="92%" style={{ marginBottom: 8 }} />
|
|
95
|
+
<Skeleton height={14} width="76%" />
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Avatar + text row */}
|
|
99
|
+
<div>
|
|
100
|
+
<div style={{ fontSize: "12px", marginBottom: "8px" }}>
|
|
101
|
+
Avatar + text
|
|
102
|
+
</div>
|
|
103
|
+
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
|
104
|
+
<Skeleton circle width={44} height={44} />
|
|
105
|
+
<div style={{ flex: 1 }}>
|
|
106
|
+
<Skeleton height={14} width="60%" style={{ marginBottom: 8 }} />
|
|
107
|
+
<Skeleton height={12} width="40%" />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Multiple items */}
|
|
113
|
+
<div>
|
|
114
|
+
<div style={{ fontSize: "12px", marginBottom: "8px" }}>
|
|
115
|
+
Multiple items
|
|
116
|
+
</div>
|
|
117
|
+
<Skeleton count={4} height={12} style={{ marginBottom: 8 }} />
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* No animation */}
|
|
121
|
+
<div>
|
|
122
|
+
<div style={{ fontSize: "12px", marginBottom: "8px" }}>
|
|
123
|
+
No animation
|
|
124
|
+
</div>
|
|
125
|
+
<Skeleton height={40} enableAnimation={false} />
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const Showcase: Story = {
|
|
132
|
+
render: () => <ShowcaseComponent />,
|
|
133
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import PluginSkeleton from 'react-loading-skeleton';
|
|
2
|
+
import 'react-loading-skeleton/dist/skeleton.css';
|
|
3
|
+
|
|
4
|
+
import { useTokens } from '../../ThemeProvider/ThemeContext';
|
|
5
|
+
import { getSkeletonTokensFromResolved } from '../../tokens';
|
|
6
|
+
import type { SkeletonProps } from './Skeleton.types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Design-system Skeleton component. Wraps react-loading-skeleton and applies
|
|
10
|
+
* design tokens for baseColor and highlightColor by default. All props are
|
|
11
|
+
* passed through to the plugin; component props override token defaults.
|
|
12
|
+
*/
|
|
13
|
+
export const Skeleton = (props: SkeletonProps) => {
|
|
14
|
+
const resolvedTokens = useTokens();
|
|
15
|
+
const tokens = getSkeletonTokensFromResolved(resolvedTokens);
|
|
16
|
+
|
|
17
|
+
const { baseColor = tokens.baseColor, highlightColor = tokens.highlightColor, ...rest } = props;
|
|
18
|
+
|
|
19
|
+
return <PluginSkeleton baseColor={baseColor} highlightColor={highlightColor} {...rest} />;
|
|
20
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { Spinner } from "./Spinner";
|
|
4
|
+
import type { SpinnerProps } from "./Spinner.types";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Spinner> = {
|
|
7
|
+
title: "Design System/Spinner",
|
|
8
|
+
component: Spinner,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<div style={{ padding: "24px" }}>
|
|
13
|
+
<Story />
|
|
14
|
+
</div>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
args: {
|
|
18
|
+
size: undefined,
|
|
19
|
+
color: "currentColor",
|
|
20
|
+
} as SpinnerProps,
|
|
21
|
+
argTypes: {
|
|
22
|
+
size: {
|
|
23
|
+
control: "text",
|
|
24
|
+
description: "Size of the spinner (e.g., '24px', '2rem', 24)",
|
|
25
|
+
},
|
|
26
|
+
color: {
|
|
27
|
+
control: "color",
|
|
28
|
+
description: "Color of the spinner. Defaults to 'currentColor'.",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
|
|
35
|
+
type Story = StoryObj<typeof Spinner>;
|
|
36
|
+
|
|
37
|
+
export const Playground: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
size: 24,
|
|
40
|
+
color: "#ff5722",
|
|
41
|
+
},
|
|
42
|
+
render: (args) => <Spinner {...args} />,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const ShowcaseComponent = () => {
|
|
46
|
+
return (
|
|
47
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px", color: "#333" }}>
|
|
48
|
+
<div>
|
|
49
|
+
<h4 style={{ margin: "0 0 8px 0", fontSize: "14px", fontWeight: 600 }}>Default</h4>
|
|
50
|
+
<Spinner />
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div>
|
|
54
|
+
<h4 style={{ margin: "0 0 8px 0", fontSize: "14px", fontWeight: 600 }}>Custom Size</h4>
|
|
55
|
+
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
|
|
56
|
+
<Spinner size={16} />
|
|
57
|
+
<Spinner size={32} />
|
|
58
|
+
<Spinner size={48} />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div>
|
|
63
|
+
<h4 style={{ margin: "0 0 8px 0", fontSize: "14px", fontWeight: 600 }}>Custom Color</h4>
|
|
64
|
+
<div style={{ display: "flex", gap: "16px", alignItems: "center" }}>
|
|
65
|
+
<Spinner color="#f44336" />
|
|
66
|
+
<Spinner color="#4caf50" />
|
|
67
|
+
<Spinner color="#2196f3" />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div>
|
|
72
|
+
<h4 style={{ margin: "0 0 8px 0", fontSize: "14px", fontWeight: 600 }}>Inherited Color</h4>
|
|
73
|
+
<div style={{ color: "#9c27b0", display: "flex", alignItems: "center", gap: "8px" }}>
|
|
74
|
+
<span>Parent text color is purple</span>
|
|
75
|
+
<Spinner />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const Showcase: Story = {
|
|
83
|
+
render: () => <ShowcaseComponent />,
|
|
84
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useTokens } from "../../ThemeProvider/ThemeContext";
|
|
3
|
+
import { getButtonTokensFromResolved } from "../../tokens";
|
|
4
|
+
import type { SpinnerProps } from "./Spinner.types";
|
|
5
|
+
|
|
6
|
+
const KEYFRAMES_ID = "ds-spinner-keyframes";
|
|
7
|
+
|
|
8
|
+
function ensureKeyframes() {
|
|
9
|
+
if (typeof document === "undefined") return;
|
|
10
|
+
if (document.getElementById(KEYFRAMES_ID)) return;
|
|
11
|
+
|
|
12
|
+
const css = `
|
|
13
|
+
@keyframes dsSpinnerSpin {
|
|
14
|
+
0% { transform: rotate(0deg); }
|
|
15
|
+
100% { transform: rotate(360deg); }
|
|
16
|
+
}`;
|
|
17
|
+
|
|
18
|
+
const style = document.createElement("style");
|
|
19
|
+
style.id = KEYFRAMES_ID;
|
|
20
|
+
style.textContent = css;
|
|
21
|
+
document.head.appendChild(style);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Spinner = ({
|
|
25
|
+
size,
|
|
26
|
+
color = "currentColor",
|
|
27
|
+
style,
|
|
28
|
+
}: SpinnerProps) => {
|
|
29
|
+
const resolvedTokens = useTokens();
|
|
30
|
+
const { sizes } = getButtonTokensFromResolved(resolvedTokens);
|
|
31
|
+
|
|
32
|
+
// Default to md button icon size
|
|
33
|
+
const defaultSize = sizes.md.fontSize;
|
|
34
|
+
const finalSize = size ?? defaultSize;
|
|
35
|
+
|
|
36
|
+
const injectedRef = useRef(false);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!injectedRef.current) {
|
|
40
|
+
ensureKeyframes();
|
|
41
|
+
injectedRef.current = true;
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<svg
|
|
47
|
+
width={finalSize}
|
|
48
|
+
height={finalSize}
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
fill="none"
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
style={{
|
|
53
|
+
animation: "dsSpinnerSpin 1s linear infinite",
|
|
54
|
+
display: "block",
|
|
55
|
+
transformOrigin: "center",
|
|
56
|
+
...style,
|
|
57
|
+
fill: "transparent",
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<circle
|
|
61
|
+
cx="12"
|
|
62
|
+
cy="12"
|
|
63
|
+
r="10"
|
|
64
|
+
stroke={color}
|
|
65
|
+
strokeOpacity="0.25"
|
|
66
|
+
strokeWidth="3"
|
|
67
|
+
/>
|
|
68
|
+
<circle
|
|
69
|
+
cx="12"
|
|
70
|
+
cy="12"
|
|
71
|
+
r="10"
|
|
72
|
+
stroke={color}
|
|
73
|
+
strokeWidth="3"
|
|
74
|
+
strokeLinecap="round"
|
|
75
|
+
strokeDasharray="20 60"
|
|
76
|
+
/>
|
|
77
|
+
</svg>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SpinnerProps {
|
|
2
|
+
/** Size of the spinner, typically inheriting the font size */
|
|
3
|
+
size?: number | string;
|
|
4
|
+
/** Color of the spinner, inherits current color by default */
|
|
5
|
+
color?: string;
|
|
6
|
+
/** Additional styles for the SVG element */
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { StatCard } from './StatCard';
|
|
4
|
+
import type { StatCardProps } from './StatCard.types';
|
|
5
|
+
|
|
6
|
+
function CheckIcon() {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
width={14}
|
|
10
|
+
height={14}
|
|
11
|
+
viewBox="0 0 14 14"
|
|
12
|
+
fill="none"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
aria-hidden
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M2.5 7L5.5 10L11.5 4"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth={2}
|
|
20
|
+
strokeLinecap="round"
|
|
21
|
+
strokeLinejoin="round"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AlertIcon() {
|
|
28
|
+
return (
|
|
29
|
+
<svg width={14} height={14} viewBox="0 0 14 14" fill="none" aria-hidden>
|
|
30
|
+
<path d="M7 3.5V8" stroke="currentColor" strokeWidth={2} strokeLinecap="round" />
|
|
31
|
+
<circle cx="7" cy="10.5" r="1" fill="currentColor" />
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const meta: Meta<typeof StatCard> = {
|
|
37
|
+
title: 'Design System/StatCard',
|
|
38
|
+
component: StatCard,
|
|
39
|
+
tags: [],
|
|
40
|
+
decorators: [
|
|
41
|
+
(Story) => (
|
|
42
|
+
<div style={{ padding: 24, maxWidth: 420, backgroundColor: '#E5E9EB' }}>
|
|
43
|
+
<Story />
|
|
44
|
+
</div>
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
args: {
|
|
48
|
+
label: 'Active',
|
|
49
|
+
infoNote: 'Rider is currently active and available for assignments',
|
|
50
|
+
value: '972',
|
|
51
|
+
accentColor: '#22C55E',
|
|
52
|
+
iconBackgroundColor: '#DDF3E3',
|
|
53
|
+
icon: <CheckIcon />,
|
|
54
|
+
} satisfies StatCardProps,
|
|
55
|
+
argTypes: {
|
|
56
|
+
label: { control: { type: 'text' } },
|
|
57
|
+
infoNote: { control: { type: 'text' } },
|
|
58
|
+
value: { control: { type: 'text' } },
|
|
59
|
+
accentColor: { control: { type: 'color' } },
|
|
60
|
+
iconBackgroundColor: { control: { type: 'color' } },
|
|
61
|
+
ariaLabel: { control: { type: 'text' } },
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default meta;
|
|
66
|
+
|
|
67
|
+
type Story = StoryObj<typeof StatCard>;
|
|
68
|
+
|
|
69
|
+
export const Playground: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
label: 'Active',
|
|
72
|
+
infoNote: 'Rider is currently active and available for assignments',
|
|
73
|
+
value: '972',
|
|
74
|
+
accentColor: '#22C55E',
|
|
75
|
+
iconBackgroundColor: '#DDF3E3',
|
|
76
|
+
icon: <CheckIcon />,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Showcase: Story = {
|
|
81
|
+
render: () => (
|
|
82
|
+
<div
|
|
83
|
+
style={{
|
|
84
|
+
display: 'flex',
|
|
85
|
+
flexDirection: 'column',
|
|
86
|
+
gap: 16,
|
|
87
|
+
maxWidth: 400,
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<StatCard
|
|
91
|
+
label="Active"
|
|
92
|
+
infoNote="Rider is currently active and available for assignments"
|
|
93
|
+
value="972"
|
|
94
|
+
accentColor="#22C55E"
|
|
95
|
+
iconBackgroundColor="#DDF3E3"
|
|
96
|
+
icon={<CheckIcon />}
|
|
97
|
+
/>
|
|
98
|
+
<StatCard
|
|
99
|
+
label="Pending"
|
|
100
|
+
infoNote="Riders waiting for the next approval step"
|
|
101
|
+
value="14"
|
|
102
|
+
accentColor="#F97316"
|
|
103
|
+
iconBackgroundColor="#FFE7D6"
|
|
104
|
+
icon={<AlertIcon />}
|
|
105
|
+
/>
|
|
106
|
+
<StatCard
|
|
107
|
+
label="Completed deliveries"
|
|
108
|
+
infoNote="Total number of successfully completed delivery orders"
|
|
109
|
+
value={12840}
|
|
110
|
+
accentColor="#16A34A"
|
|
111
|
+
iconBackgroundColor="#DDF3E3"
|
|
112
|
+
icon={<CheckIcon />}
|
|
113
|
+
ariaLabel="Completed deliveries: 12840"
|
|
114
|
+
/>
|
|
115
|
+
<StatCard
|
|
116
|
+
label="At risk"
|
|
117
|
+
infoNote="Riders requiring immediate review or intervention"
|
|
118
|
+
value="3"
|
|
119
|
+
accentColor="#DC2626"
|
|
120
|
+
iconBackgroundColor="#FBDCDC"
|
|
121
|
+
icon={<AlertIcon />}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import { useTokens } from '../../ThemeProvider/ThemeContext';
|
|
3
|
+
import { getStatCardTokensFromResolved } from '../../tokens';
|
|
4
|
+
import type { StatCardProps } from './StatCard.types';
|
|
5
|
+
|
|
6
|
+
function InfoIcon() {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
width="12"
|
|
10
|
+
height="12"
|
|
11
|
+
viewBox="0 0 12 12"
|
|
12
|
+
fill="none"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
aria-hidden
|
|
15
|
+
>
|
|
16
|
+
<circle cx="6" cy="6" r="5.25" stroke="currentColor" strokeWidth="1.5" />
|
|
17
|
+
<path d="M6 5.2V8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
18
|
+
<circle cx="6" cy="3.55" r="0.75" fill="currentColor" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function defaultAriaLabel(
|
|
24
|
+
label: StatCardProps['label'],
|
|
25
|
+
value: StatCardProps['value'],
|
|
26
|
+
infoNote?: string
|
|
27
|
+
) {
|
|
28
|
+
if (typeof label === 'string' || typeof label === 'number') {
|
|
29
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
30
|
+
return infoNote ? `${label}: ${value}. ${infoNote}` : `${label}: ${value}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const StatCard = ({
|
|
37
|
+
label,
|
|
38
|
+
infoNote,
|
|
39
|
+
value,
|
|
40
|
+
accentColor,
|
|
41
|
+
iconBackgroundColor,
|
|
42
|
+
icon,
|
|
43
|
+
ariaLabel,
|
|
44
|
+
className,
|
|
45
|
+
style,
|
|
46
|
+
}: StatCardProps) => {
|
|
47
|
+
const resolvedTokens = useTokens();
|
|
48
|
+
const t = getStatCardTokensFromResolved(resolvedTokens);
|
|
49
|
+
|
|
50
|
+
const mixPercent = `${t.iconWellAccentMixPercent}%`;
|
|
51
|
+
const iconWellStyle = {
|
|
52
|
+
['--stat-accent' as string]: accentColor,
|
|
53
|
+
['--stat-well-base' as string]: t.surfaceBackground,
|
|
54
|
+
width: t.iconWellSize,
|
|
55
|
+
height: t.iconWellSize,
|
|
56
|
+
borderRadius: t.iconWellBorderRadius,
|
|
57
|
+
flexShrink: 0,
|
|
58
|
+
display: 'flex',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
justifyContent: 'center',
|
|
61
|
+
marginLeft: '-2px',
|
|
62
|
+
background:
|
|
63
|
+
iconBackgroundColor ??
|
|
64
|
+
`color-mix(in srgb, var(--stat-accent) ${mixPercent}, var(--stat-well-base))`,
|
|
65
|
+
} as CSSProperties;
|
|
66
|
+
|
|
67
|
+
const computedAria = ariaLabel ?? defaultAriaLabel(label, value, infoNote);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={className}
|
|
72
|
+
role="group"
|
|
73
|
+
aria-label={computedAria}
|
|
74
|
+
style={{
|
|
75
|
+
boxSizing: 'border-box',
|
|
76
|
+
display: 'flex',
|
|
77
|
+
flexDirection: 'row',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
gap: t.gap,
|
|
80
|
+
padding: t.padding,
|
|
81
|
+
backgroundColor: t.surfaceBackground,
|
|
82
|
+
border: t.border,
|
|
83
|
+
borderRadius: t.borderRadius,
|
|
84
|
+
...style,
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<div style={iconWellStyle}>
|
|
88
|
+
<div
|
|
89
|
+
style={{
|
|
90
|
+
display: 'flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
justifyContent: 'center',
|
|
93
|
+
color: accentColor,
|
|
94
|
+
lineHeight: 0,
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{icon}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div
|
|
101
|
+
style={{
|
|
102
|
+
display: 'flex',
|
|
103
|
+
flexDirection: 'column',
|
|
104
|
+
flex: 1,
|
|
105
|
+
alignItems: 'flex-start',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
minWidth: 0,
|
|
108
|
+
fontFamily: resolvedTokens.semantic?.typography?.body?.medium?.fontFamily,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
display: 'flex',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
gap: '4px',
|
|
116
|
+
width: '100%',
|
|
117
|
+
minWidth: 0,
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<span
|
|
121
|
+
style={{
|
|
122
|
+
flex: 1,
|
|
123
|
+
fontSize: t.labelFontSize,
|
|
124
|
+
fontWeight: t.labelFontWeight,
|
|
125
|
+
color: t.labelColor,
|
|
126
|
+
lineHeight: 1.2,
|
|
127
|
+
whiteSpace: 'nowrap',
|
|
128
|
+
overflow: 'hidden',
|
|
129
|
+
textOverflow: 'ellipsis',
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{label}
|
|
133
|
+
</span>
|
|
134
|
+
{infoNote ?
|
|
135
|
+
<span
|
|
136
|
+
title={infoNote}
|
|
137
|
+
aria-label={infoNote}
|
|
138
|
+
tabIndex={0}
|
|
139
|
+
style={{
|
|
140
|
+
display: 'inline-flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
justifyContent: 'center',
|
|
143
|
+
color: t.labelColor,
|
|
144
|
+
opacity: 0.72,
|
|
145
|
+
cursor: 'pointer',
|
|
146
|
+
flexShrink: 0,
|
|
147
|
+
lineHeight: 0,
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
<InfoIcon />
|
|
151
|
+
</span>
|
|
152
|
+
: null}
|
|
153
|
+
</div>
|
|
154
|
+
<span
|
|
155
|
+
style={{
|
|
156
|
+
fontSize: t.valueFontSize,
|
|
157
|
+
fontWeight: t.valueFontWeight,
|
|
158
|
+
color: t.valueColor,
|
|
159
|
+
lineHeight: 1.2,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{value}
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
};
|