sangam-ui 0.1.8 → 0.1.10
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/README.md +36 -1
- package/dist/index.d.ts +32 -68
- package/dist/index.js +229 -55
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ module.exports = {
|
|
|
107
107
|
### 5.2 Configure Tailwind — use the Sangam preset
|
|
108
108
|
|
|
109
109
|
> [!IMPORTANT]
|
|
110
|
-
> At your **project root**, Always spread `...sangamTailwind` and extend its `content` array — **never replace it**. Replacing it strips the Sangam theme (colors, spacing, radii, shadows, darkMode) and component class paths, causing broken styles.
|
|
110
|
+
> At your **project root**, create a `tailwind.config.cjs` file. Always spread `...sangamTailwind` and extend its `content` array — **never replace it**. Replacing it strips the Sangam theme (colors, spacing, radii, shadows, darkMode) and component class paths, causing broken styles.
|
|
111
111
|
|
|
112
112
|
```js
|
|
113
113
|
// tailwind.config.cjs
|
|
@@ -127,6 +127,41 @@ module.exports = {
|
|
|
127
127
|
|
|
128
128
|
Spreading `...sangamTailwind` gives you the Sangam theme (colors, spacing, radii, shadows), `darkMode` setting, and any plugins — without having to duplicate them.
|
|
129
129
|
|
|
130
|
+
#### Base44: using `.js` config files
|
|
131
|
+
|
|
132
|
+
If your project (e.g. **Base44**) expects `postcss.config.js` and `tailwind.config.js` instead of `.cjs`, use the same content with the `.js` extension:
|
|
133
|
+
|
|
134
|
+
**postcss.config.js**
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
// postcss.config.js
|
|
138
|
+
export default {
|
|
139
|
+
plugins: {
|
|
140
|
+
"postcss-import": {}, // MUST be first: inlines @import before Tailwind sees the file
|
|
141
|
+
tailwindcss: {},
|
|
142
|
+
autoprefixer: {},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**tailwind.config.js**
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
// tailwind.config.js
|
|
151
|
+
import sangamTailwind from "@esds-sangam/tailwind-config";
|
|
152
|
+
|
|
153
|
+
/** @type {import('tailwindcss').Config} */
|
|
154
|
+
export default {
|
|
155
|
+
...sangamTailwind,
|
|
156
|
+
content: [
|
|
157
|
+
...sangamTailwind.content,
|
|
158
|
+
"./index.html",
|
|
159
|
+
"./src/**/*.{ts,tsx}",
|
|
160
|
+
"./node_modules/sangam-ui/dist/**/*.{js,mjs}",
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
130
165
|
### 5.3 Wire styles — single CSS entry file
|
|
131
166
|
|
|
132
167
|
> [!IMPORTANT]
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
|
8
8
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
9
9
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
10
10
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
11
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Sangam Button Component
|
|
@@ -571,7 +572,7 @@ declare const SearchField: React.ForwardRefExoticComponent<SearchFieldProps & Re
|
|
|
571
572
|
* Sangam Textarea Component
|
|
572
573
|
*
|
|
573
574
|
* Production-grade textarea following Figma design specs:
|
|
574
|
-
* - Width:
|
|
575
|
+
* - Width: 100% with min-width 450px (responsive, matches Input pattern)
|
|
575
576
|
* - Height: Fixed 96px
|
|
576
577
|
* - Colors: Neutral, Red, Green from colors.json
|
|
577
578
|
* - Multiple states: Default, Hover, Active, Typing, Filled, Error, Success, Disabled
|
|
@@ -1095,87 +1096,50 @@ interface ChipProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children
|
|
|
1095
1096
|
*/
|
|
1096
1097
|
declare const Chip: React.ForwardRefExoticComponent<ChipProps & React.RefAttributes<HTMLDivElement>>;
|
|
1097
1098
|
|
|
1099
|
+
/** Toast entry for the provider (id, content, optional duration in ms; default 4000) */
|
|
1100
|
+
interface ToastEntryOptions {
|
|
1101
|
+
title: string;
|
|
1102
|
+
variant?: "info" | "success" | "warning" | "error";
|
|
1103
|
+
description?: string;
|
|
1104
|
+
ctaText?: string;
|
|
1105
|
+
onCtaClick?: () => void;
|
|
1106
|
+
showClose?: boolean;
|
|
1107
|
+
/** Duration in ms before auto-dismiss. @default 4000 */
|
|
1108
|
+
duration?: number;
|
|
1109
|
+
}
|
|
1110
|
+
interface ToastEntry extends ToastEntryOptions {
|
|
1111
|
+
id: string;
|
|
1112
|
+
}
|
|
1113
|
+
type ToastContextValue = {
|
|
1114
|
+
toasts: ToastEntry[];
|
|
1115
|
+
addToast: (options: ToastEntryOptions) => void;
|
|
1116
|
+
removeToast: (id: string) => void;
|
|
1117
|
+
};
|
|
1098
1118
|
/**
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
*
|
|
1105
|
-
* -
|
|
1106
|
-
* - Padding: 16px
|
|
1107
|
-
* - Background: white (neutral-50)
|
|
1108
|
-
* - Title: 16px, 500 weight, 24px line-height, neutral-1100 color
|
|
1109
|
-
* - Description: 14px, 400 weight, 24px line-height, neutral-600 color
|
|
1110
|
-
* - Shadow: 0px 4px 10px 0px
|
|
1111
|
-
* - Close icon: 16x16
|
|
1112
|
-
* - Status icon: 20x20
|
|
1113
|
-
* - CTA Button: radius 8px, padding 4px 16px
|
|
1114
|
-
*
|
|
1115
|
-
* Four color variants (token-based):
|
|
1116
|
-
* 1. Info - blue-600 border/icon
|
|
1117
|
-
* 2. Success - green-800 border/icon
|
|
1118
|
-
* 3. Warning - orange-700 border/icon
|
|
1119
|
-
* 4. Error - red-800 border/icon
|
|
1120
|
-
*
|
|
1121
|
-
* Three layout variants:
|
|
1122
|
-
* 1. Default - Title only (56px height)
|
|
1123
|
-
* 2. With Description - Title + description + close (84px height)
|
|
1124
|
-
* 3. With CTA - Title + button (84px height, no description)
|
|
1125
|
-
*
|
|
1126
|
-
* @example Basic usage
|
|
1127
|
-
* <Toast variant="info" title="Info message" />
|
|
1128
|
-
*
|
|
1129
|
-
* @example With description
|
|
1130
|
-
* <Toast variant="success" title="Success!" description="Your changes have been saved" />
|
|
1131
|
-
*
|
|
1132
|
-
* @example With CTA button
|
|
1133
|
-
* <Toast variant="info" title="New update" ctaText="Update" onCtaClick={() => {}} />
|
|
1119
|
+
* Hook to show toasts. Must be used inside ToastProvider.
|
|
1120
|
+
* Each call to addToast() shows a new toast; toasts stack from the top (60px from top) and auto-dismiss after 4 seconds.
|
|
1121
|
+
*/
|
|
1122
|
+
declare function useToast(): ToastContextValue;
|
|
1123
|
+
/**
|
|
1124
|
+
* Provider for toast notifications. Renders toasts in a fixed container 60px from the top, stacked vertically.
|
|
1125
|
+
* Toasts auto-dismiss after 4 seconds (configurable per toast via duration).
|
|
1134
1126
|
*/
|
|
1127
|
+
declare function ToastProvider({ children }: {
|
|
1128
|
+
children: React.ReactNode;
|
|
1129
|
+
}): react_jsx_runtime.JSX.Element;
|
|
1135
1130
|
declare const toastVariants: (props?: ({
|
|
1136
1131
|
variant?: "error" | "success" | "info" | "warning" | null | undefined;
|
|
1137
1132
|
layout?: "default" | "withDescription" | null | undefined;
|
|
1138
1133
|
} & class_variance_authority_dist_types.ClassProp) | undefined) => string;
|
|
1139
1134
|
interface ToastProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title">, VariantProps<typeof toastVariants> {
|
|
1140
|
-
/**
|
|
1141
|
-
* Toast variant type
|
|
1142
|
-
* @default "info"
|
|
1143
|
-
*/
|
|
1144
1135
|
variant?: "info" | "success" | "warning" | "error";
|
|
1145
|
-
/**
|
|
1146
|
-
* Title text (required)
|
|
1147
|
-
*/
|
|
1148
1136
|
title: string;
|
|
1149
|
-
/**
|
|
1150
|
-
* Description text (optional)
|
|
1151
|
-
* When provided, toast height becomes 84px
|
|
1152
|
-
*/
|
|
1153
1137
|
description?: string;
|
|
1154
|
-
/**
|
|
1155
|
-
* Whether to show close button
|
|
1156
|
-
* @default true (false when CTA is provided)
|
|
1157
|
-
*/
|
|
1158
1138
|
showClose?: boolean;
|
|
1159
|
-
/**
|
|
1160
|
-
* Callback when close button is clicked
|
|
1161
|
-
*/
|
|
1162
1139
|
onClose?: () => void;
|
|
1163
|
-
/**
|
|
1164
|
-
* CTA button text (optional)
|
|
1165
|
-
* When provided, replaces close button and height becomes 84px
|
|
1166
|
-
*/
|
|
1167
1140
|
ctaText?: string;
|
|
1168
|
-
/**
|
|
1169
|
-
* Callback when CTA button is clicked
|
|
1170
|
-
*/
|
|
1171
1141
|
onCtaClick?: () => void;
|
|
1172
1142
|
}
|
|
1173
|
-
/**
|
|
1174
|
-
* Toast component with icon, title, optional description, and close/CTA
|
|
1175
|
-
*
|
|
1176
|
-
* Displays status-based notification with appropriate icon and border color
|
|
1177
|
-
* Supports three layouts: default (56px), with description (84px), with CTA (84px)
|
|
1178
|
-
*/
|
|
1179
1143
|
declare const Toast: React.ForwardRefExoticComponent<ToastProps & React.RefAttributes<HTMLDivElement>>;
|
|
1180
1144
|
|
|
1181
1145
|
/**
|
|
@@ -1431,4 +1395,4 @@ type Size = "sm" | "md" | "lg";
|
|
|
1431
1395
|
type Variant = "primary" | "secondary" | "outline" | "ghost";
|
|
1432
1396
|
type ColorScheme = "primary" | "secondary" | "success" | "error" | "warning" | "info";
|
|
1433
1397
|
|
|
1434
|
-
export { Avatar, Badge, type BaseComponentProps, Button, Checkbox, Chip, type ColorScheme, Dropdown, DropdownMulti, type DropdownMultiProps, type DropdownOption, type DropdownProps, Input, Label, Loader, PageFooter, type PageFooterProps, PageHeader, type PageHeaderAction, type PageHeaderProps, type PageHeaderTab, ProgressBar, Radio, RadioGroup, SearchField, SideMenu, type SideMenuItem, type SideMenuProps, type Size, Skeleton, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, type TabsTriggerOption, type TabsTriggerProps, Textarea, Toast, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Upload, UploadFileItem, type UploadFileItemProps, type UploadFileItemStatus, type UploadProps, type Variant, avatarVariants, badgeVariants, buttonVariants, checkboxRootVariants as checkboxVariants, chipVariants, dropdownContentVariants, dropdownItemVariants, dropdownTriggerVariants, inputVariants, loaderVariants, pageHeaderVariants, radioVariants, searchFieldVariants, sideMenuVariants, skeletonVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipContentVariants, uploadBoxVariants, uploadFileItemBoxVariants };
|
|
1398
|
+
export { Avatar, Badge, type BaseComponentProps, Button, Checkbox, Chip, type ColorScheme, Dropdown, DropdownMulti, type DropdownMultiProps, type DropdownOption, type DropdownProps, Input, Label, Loader, PageFooter, type PageFooterProps, PageHeader, type PageHeaderAction, type PageHeaderProps, type PageHeaderTab, ProgressBar, Radio, RadioGroup, SearchField, SideMenu, type SideMenuItem, type SideMenuProps, type Size, Skeleton, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, type TabsTriggerOption, type TabsTriggerProps, Textarea, Toast, type ToastEntryOptions, ToastProvider, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Upload, UploadFileItem, type UploadFileItemProps, type UploadFileItemStatus, type UploadProps, type Variant, avatarVariants, badgeVariants, buttonVariants, checkboxRootVariants as checkboxVariants, chipVariants, dropdownContentVariants, dropdownItemVariants, dropdownTriggerVariants, inputVariants, loaderVariants, pageHeaderVariants, radioVariants, searchFieldVariants, sideMenuVariants, skeletonVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipContentVariants, uploadBoxVariants, uploadFileItemBoxVariants, useToast };
|
package/dist/index.js
CHANGED
|
@@ -1248,8 +1248,8 @@ import { Info as Info3, TickmarkFilled as TickmarkFilled2, InfoFilled as InfoFil
|
|
|
1248
1248
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1249
1249
|
var textareaVariants = cva9(
|
|
1250
1250
|
[
|
|
1251
|
-
// Base styles
|
|
1252
|
-
"w-[450px] h-24 rounded-sm border transition-all duration-200 ease-out",
|
|
1251
|
+
// Base styles: w-full like Input, min-w-[450px] per design spec
|
|
1252
|
+
"w-full min-w-[450px] h-24 rounded-sm border transition-all duration-200 ease-out",
|
|
1253
1253
|
"font-sans text-sm leading-6 font-normal",
|
|
1254
1254
|
// 14px font, 24px line-height, 400 weight
|
|
1255
1255
|
"px-3 py-1",
|
|
@@ -1308,11 +1308,25 @@ var Textarea = React9.forwardRef(
|
|
|
1308
1308
|
showCounter = true,
|
|
1309
1309
|
showLabelIcon = false,
|
|
1310
1310
|
showHelperIcon = false,
|
|
1311
|
+
onChange,
|
|
1311
1312
|
...props
|
|
1312
1313
|
}, ref) => {
|
|
1313
1314
|
const variant = error ? "error" : success ? "success" : "default";
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1315
|
+
const [internalLength, setInternalLength] = React9.useState(
|
|
1316
|
+
() => value != null ? value.toString().length : props.defaultValue != null ? String(props.defaultValue).length : 0
|
|
1317
|
+
);
|
|
1318
|
+
const currentLength = internalLength;
|
|
1319
|
+
const isFilled = currentLength > 0;
|
|
1320
|
+
React9.useEffect(() => {
|
|
1321
|
+
if (value !== void 0) setInternalLength(value != null ? value.toString().length : 0);
|
|
1322
|
+
}, [value]);
|
|
1323
|
+
const handleChange = React9.useCallback(
|
|
1324
|
+
(e) => {
|
|
1325
|
+
setInternalLength(e.target.value.length);
|
|
1326
|
+
onChange?.(e);
|
|
1327
|
+
},
|
|
1328
|
+
[onChange]
|
|
1329
|
+
);
|
|
1316
1330
|
const textareaId = props.id || `textarea-${React9.useId()}`;
|
|
1317
1331
|
const helperTextId = helperText ? `${textareaId}-helper` : void 0;
|
|
1318
1332
|
const helperTextColor = error ? "text-red-800" : success ? "text-green-800" : "text-neutral-600";
|
|
@@ -1339,6 +1353,7 @@ var Textarea = React9.forwardRef(
|
|
|
1339
1353
|
"aria-describedby": helperTextId,
|
|
1340
1354
|
"aria-invalid": error ? "true" : void 0,
|
|
1341
1355
|
className: cn9(textareaVariants({ variant, filled: isFilled }), className),
|
|
1356
|
+
onChange: handleChange,
|
|
1342
1357
|
...props
|
|
1343
1358
|
}
|
|
1344
1359
|
),
|
|
@@ -1515,7 +1530,7 @@ var TooltipContent = React10.forwardRef(
|
|
|
1515
1530
|
)
|
|
1516
1531
|
] }),
|
|
1517
1532
|
!title && children,
|
|
1518
|
-
showArrow && /* @__PURE__ */ jsx10("div", { className: cn10(getCaretPosition(), "pointer-events-none"), children: /* @__PURE__ */ jsx10(CaretComponent, { className: "text-neutral-1100" }) })
|
|
1533
|
+
showArrow && /* @__PURE__ */ jsx10("div", { className: cn10(getCaretPosition(), "pointer-events-none text-neutral-1100"), children: /* @__PURE__ */ jsx10(CaretComponent, { className: "text-neutral-1100", "aria-hidden": true }) })
|
|
1519
1534
|
]
|
|
1520
1535
|
}
|
|
1521
1536
|
) });
|
|
@@ -1956,6 +1971,15 @@ var leadingIconMap = {
|
|
|
1956
1971
|
process: InfoFilled4,
|
|
1957
1972
|
action: InfoFilled4
|
|
1958
1973
|
};
|
|
1974
|
+
var leadingIconOutlinedColorClass = {
|
|
1975
|
+
info: "!text-blue-600",
|
|
1976
|
+
success: "!text-green-800",
|
|
1977
|
+
warning: "!text-orange-700",
|
|
1978
|
+
error: "!text-red-800",
|
|
1979
|
+
neutral: "!text-neutral-1100",
|
|
1980
|
+
process: "!text-purple-700",
|
|
1981
|
+
action: "!text-neutral-400"
|
|
1982
|
+
};
|
|
1959
1983
|
var Badge = React15.forwardRef(
|
|
1960
1984
|
({
|
|
1961
1985
|
className,
|
|
@@ -1998,7 +2022,7 @@ var Badge = React15.forwardRef(
|
|
|
1998
2022
|
size: leadingIconSize,
|
|
1999
2023
|
className: cn15(
|
|
2000
2024
|
"shrink-0",
|
|
2001
|
-
resolvedAppearance === "solid" ? "text-neutral-50" :
|
|
2025
|
+
resolvedAppearance === "solid" ? "text-neutral-50" : leadingIconOutlinedColorClass[resolvedState]
|
|
2002
2026
|
),
|
|
2003
2027
|
"aria-hidden": "true"
|
|
2004
2028
|
}
|
|
@@ -2010,7 +2034,7 @@ var Badge = React15.forwardRef(
|
|
|
2010
2034
|
size: dropdownChevronSize,
|
|
2011
2035
|
className: cn15(
|
|
2012
2036
|
"shrink-0",
|
|
2013
|
-
resolvedAppearance === "solid" ? "text-neutral-50" :
|
|
2037
|
+
resolvedAppearance === "solid" ? "text-neutral-50" : leadingIconOutlinedColorClass[resolvedState]
|
|
2014
2038
|
),
|
|
2015
2039
|
"aria-hidden": "true"
|
|
2016
2040
|
}
|
|
@@ -2022,7 +2046,7 @@ var Badge = React15.forwardRef(
|
|
|
2022
2046
|
onClick: onCloseClick,
|
|
2023
2047
|
className: cn15(
|
|
2024
2048
|
"shrink-0 p-0 border-0 bg-transparent cursor-pointer rounded inline-flex items-center justify-center",
|
|
2025
|
-
resolvedAppearance === "solid" ? "text-neutral-50" :
|
|
2049
|
+
resolvedAppearance === "solid" ? "text-neutral-50" : leadingIconOutlinedColorClass[resolvedState]
|
|
2026
2050
|
),
|
|
2027
2051
|
"aria-label": "Dismiss",
|
|
2028
2052
|
children: /* @__PURE__ */ jsx15(Close4, { size: closeIconSize, "aria-hidden": true })
|
|
@@ -2138,25 +2162,101 @@ Chip.displayName = "Chip";
|
|
|
2138
2162
|
|
|
2139
2163
|
// src/components/toast.tsx
|
|
2140
2164
|
import * as React17 from "react";
|
|
2165
|
+
import { createPortal } from "react-dom";
|
|
2141
2166
|
import { cva as cva16 } from "class-variance-authority";
|
|
2142
2167
|
import { cn as cn17 } from "@esds-sangam/utils";
|
|
2143
2168
|
import { Close as Close6, InfoFilled as InfoFilled5, TickmarkFilled as TickmarkFilled5, WarningFilled as WarningFilled4 } from "@esds-sangam/icons";
|
|
2144
2169
|
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2170
|
+
var ToastContext = React17.createContext(null);
|
|
2171
|
+
var TOAST_DEFAULT_DURATION = 4e3;
|
|
2172
|
+
var TOAST_TOP_OFFSET_PX = 0;
|
|
2173
|
+
var TOAST_ANIMATION_DURATION_MS = 300;
|
|
2174
|
+
function ToastItemWrapper({
|
|
2175
|
+
children,
|
|
2176
|
+
onAnimationEnd
|
|
2177
|
+
}) {
|
|
2178
|
+
const [mounted, setMounted] = React17.useState(false);
|
|
2179
|
+
React17.useEffect(() => {
|
|
2180
|
+
const t = requestAnimationFrame(() => {
|
|
2181
|
+
setMounted(true);
|
|
2182
|
+
});
|
|
2183
|
+
return () => cancelAnimationFrame(t);
|
|
2184
|
+
}, []);
|
|
2185
|
+
return /* @__PURE__ */ jsx17(
|
|
2186
|
+
"div",
|
|
2187
|
+
{
|
|
2188
|
+
className: cn17(
|
|
2189
|
+
"transition-all duration-300 ease-out",
|
|
2190
|
+
mounted ? "translate-y-0 opacity-100" : "-translate-y-full opacity-0"
|
|
2191
|
+
),
|
|
2192
|
+
style: { transitionDuration: `${TOAST_ANIMATION_DURATION_MS}ms` },
|
|
2193
|
+
onTransitionEnd: onAnimationEnd,
|
|
2194
|
+
children
|
|
2195
|
+
}
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
function useToast() {
|
|
2199
|
+
const ctx = React17.useContext(ToastContext);
|
|
2200
|
+
if (!ctx) throw new Error("useToast must be used within ToastProvider");
|
|
2201
|
+
return ctx;
|
|
2202
|
+
}
|
|
2203
|
+
function ToastProvider({ children }) {
|
|
2204
|
+
const [toasts, setToasts] = React17.useState([]);
|
|
2205
|
+
const removeToast = React17.useCallback((id) => {
|
|
2206
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
2207
|
+
}, []);
|
|
2208
|
+
const addToast = React17.useCallback((options) => {
|
|
2209
|
+
const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2210
|
+
const duration = options.duration ?? TOAST_DEFAULT_DURATION;
|
|
2211
|
+
const entry = { ...options, id };
|
|
2212
|
+
setToasts((prev) => [...prev, entry]);
|
|
2213
|
+
const t = setTimeout(() => {
|
|
2214
|
+
removeToast(id);
|
|
2215
|
+
}, duration);
|
|
2216
|
+
return () => clearTimeout(t);
|
|
2217
|
+
}, [removeToast]);
|
|
2218
|
+
const value = React17.useMemo(
|
|
2219
|
+
() => ({ toasts, addToast, removeToast }),
|
|
2220
|
+
[toasts, addToast, removeToast]
|
|
2221
|
+
);
|
|
2222
|
+
return /* @__PURE__ */ jsxs13(ToastContext.Provider, { value, children: [
|
|
2223
|
+
children,
|
|
2224
|
+
typeof document !== "undefined" && createPortal(
|
|
2225
|
+
/* @__PURE__ */ jsx17(
|
|
2226
|
+
"div",
|
|
2227
|
+
{
|
|
2228
|
+
className: "fixed left-0 right-0 z-[100] flex flex-col items-center gap-2 pt-0",
|
|
2229
|
+
style: { top: TOAST_TOP_OFFSET_PX },
|
|
2230
|
+
"aria-live": "polite",
|
|
2231
|
+
role: "region",
|
|
2232
|
+
"aria-label": "Notifications",
|
|
2233
|
+
children: toasts.map((t) => /* @__PURE__ */ jsx17(ToastItemWrapper, { children: /* @__PURE__ */ jsx17(
|
|
2234
|
+
Toast,
|
|
2235
|
+
{
|
|
2236
|
+
variant: t.variant ?? "info",
|
|
2237
|
+
title: t.title,
|
|
2238
|
+
description: t.description,
|
|
2239
|
+
showClose: t.showClose ?? true,
|
|
2240
|
+
onClose: () => removeToast(t.id),
|
|
2241
|
+
ctaText: t.ctaText,
|
|
2242
|
+
onCtaClick: t.onCtaClick
|
|
2243
|
+
}
|
|
2244
|
+
) }, t.id))
|
|
2245
|
+
}
|
|
2246
|
+
),
|
|
2247
|
+
document.body
|
|
2248
|
+
)
|
|
2249
|
+
] });
|
|
2250
|
+
}
|
|
2145
2251
|
var toastVariants = cva16(
|
|
2146
2252
|
[
|
|
2147
|
-
// Base styles
|
|
2148
2253
|
"flex gap-2",
|
|
2149
2254
|
"w-[420px]",
|
|
2150
|
-
// 384px fixed width
|
|
2151
2255
|
"rounded-sm",
|
|
2152
|
-
// 8px = tokens radius.lg
|
|
2153
2256
|
"bg-white",
|
|
2154
2257
|
"p-4",
|
|
2155
|
-
// 16px padding
|
|
2156
2258
|
"border-l-[3px]",
|
|
2157
|
-
// 3px left border
|
|
2158
2259
|
"shadow-md"
|
|
2159
|
-
// tokens.shadows.md = 0 4px 10px 0 rgba(0,0,0,0.1)
|
|
2160
2260
|
],
|
|
2161
2261
|
{
|
|
2162
2262
|
variants: {
|
|
@@ -2168,9 +2268,7 @@ var toastVariants = cva16(
|
|
|
2168
2268
|
},
|
|
2169
2269
|
layout: {
|
|
2170
2270
|
default: "h-14 items-center",
|
|
2171
|
-
// 56px, centered vertically
|
|
2172
2271
|
withDescription: "h-21 items-start"
|
|
2173
|
-
// 84px, top aligned
|
|
2174
2272
|
}
|
|
2175
2273
|
},
|
|
2176
2274
|
defaultVariants: {
|
|
@@ -2228,7 +2326,7 @@ var Toast = React17.forwardRef(
|
|
|
2228
2326
|
onClick: onCtaClick,
|
|
2229
2327
|
size: "small",
|
|
2230
2328
|
variant: "primary",
|
|
2231
|
-
className: "w-auto self-start
|
|
2329
|
+
className: "w-auto self-start",
|
|
2232
2330
|
children: ctaText
|
|
2233
2331
|
}
|
|
2234
2332
|
)
|
|
@@ -2613,8 +2711,15 @@ var uploadBoxVariants = cva19([
|
|
|
2613
2711
|
"rounded-sm border border-dashed border-neutral-400 hover:border-neutral-700",
|
|
2614
2712
|
"bg-white",
|
|
2615
2713
|
"transition-colors duration-200 ease-out",
|
|
2616
|
-
"cursor-pointer"
|
|
2714
|
+
"cursor-pointer",
|
|
2715
|
+
"min-h-[120px]"
|
|
2716
|
+
// ensure dropzone has clickable height
|
|
2617
2717
|
]);
|
|
2718
|
+
function formatFileSize(bytes) {
|
|
2719
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2720
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2721
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2722
|
+
}
|
|
2618
2723
|
var Upload = React21.forwardRef(
|
|
2619
2724
|
({
|
|
2620
2725
|
className,
|
|
@@ -2627,14 +2732,63 @@ var Upload = React21.forwardRef(
|
|
|
2627
2732
|
...props
|
|
2628
2733
|
}, ref) => {
|
|
2629
2734
|
const inputRef = React21.useRef(null);
|
|
2735
|
+
const [fileEntries, setFileEntries] = React21.useState([]);
|
|
2736
|
+
const progressIntervalRef = React21.useRef(null);
|
|
2630
2737
|
const handleClick = () => {
|
|
2631
2738
|
inputRef.current?.click();
|
|
2632
2739
|
};
|
|
2633
2740
|
const handleFiles = (fileList) => {
|
|
2634
|
-
if (!fileList
|
|
2741
|
+
if (!fileList) return;
|
|
2635
2742
|
const files = Array.from(fileList);
|
|
2636
|
-
if (files.length)
|
|
2743
|
+
if (files.length) {
|
|
2744
|
+
const newEntries = files.map((file, i) => ({
|
|
2745
|
+
id: `${file.name}-${file.size}-${Date.now()}-${i}`,
|
|
2746
|
+
file,
|
|
2747
|
+
status: "uploading",
|
|
2748
|
+
progress: 0
|
|
2749
|
+
}));
|
|
2750
|
+
setFileEntries((prev) => multiple ? [...prev, ...newEntries] : newEntries);
|
|
2751
|
+
onFilesSelected?.(files);
|
|
2752
|
+
}
|
|
2637
2753
|
};
|
|
2754
|
+
const removeEntry = (id) => {
|
|
2755
|
+
setFileEntries((prev) => prev.filter((e) => e.id !== id));
|
|
2756
|
+
};
|
|
2757
|
+
const setEntryStatus = (id, status, progress) => {
|
|
2758
|
+
setFileEntries(
|
|
2759
|
+
(prev) => prev.map(
|
|
2760
|
+
(e) => e.id === id ? { ...e, status, ...progress !== void 0 ? { progress } : {} } : e
|
|
2761
|
+
)
|
|
2762
|
+
);
|
|
2763
|
+
};
|
|
2764
|
+
React21.useEffect(() => {
|
|
2765
|
+
const uploading = fileEntries.filter((e) => e.status === "uploading");
|
|
2766
|
+
if (uploading.length === 0) return;
|
|
2767
|
+
const steps = [10, 50, 100];
|
|
2768
|
+
let stepIndex = 0;
|
|
2769
|
+
progressIntervalRef.current = setInterval(() => {
|
|
2770
|
+
stepIndex++;
|
|
2771
|
+
if (stepIndex > steps.length) {
|
|
2772
|
+
if (progressIntervalRef.current) {
|
|
2773
|
+
clearInterval(progressIntervalRef.current);
|
|
2774
|
+
progressIntervalRef.current = null;
|
|
2775
|
+
}
|
|
2776
|
+
setFileEntries(
|
|
2777
|
+
(prev) => prev.map(
|
|
2778
|
+
(e) => e.status === "uploading" ? { ...e, progress: 100, status: "complete" } : e
|
|
2779
|
+
)
|
|
2780
|
+
);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
const value = steps[stepIndex - 1];
|
|
2784
|
+
setFileEntries(
|
|
2785
|
+
(prev) => prev.map((e) => e.status === "uploading" ? { ...e, progress: value } : e)
|
|
2786
|
+
);
|
|
2787
|
+
}, 400);
|
|
2788
|
+
return () => {
|
|
2789
|
+
if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
|
|
2790
|
+
};
|
|
2791
|
+
}, [fileEntries.filter((e) => e.status === "uploading").length]);
|
|
2638
2792
|
const handleInputChange = (event) => {
|
|
2639
2793
|
handleFiles(event.target.files);
|
|
2640
2794
|
event.target.value = "";
|
|
@@ -2657,24 +2811,12 @@ var Upload = React21.forwardRef(
|
|
|
2657
2811
|
label,
|
|
2658
2812
|
required && /* @__PURE__ */ jsx21("span", { className: "text-red-800 ml-0.5", children: "*" })
|
|
2659
2813
|
] }),
|
|
2660
|
-
/* @__PURE__ */
|
|
2661
|
-
"input",
|
|
2662
|
-
{
|
|
2663
|
-
ref: inputRef,
|
|
2664
|
-
type: "file",
|
|
2665
|
-
accept,
|
|
2666
|
-
multiple,
|
|
2667
|
-
className: "hidden",
|
|
2668
|
-
onChange: handleInputChange
|
|
2669
|
-
}
|
|
2670
|
-
),
|
|
2671
|
-
/* @__PURE__ */ jsx21(
|
|
2814
|
+
/* @__PURE__ */ jsxs16(
|
|
2672
2815
|
"div",
|
|
2673
2816
|
{
|
|
2674
2817
|
role: "button",
|
|
2675
2818
|
tabIndex: 0,
|
|
2676
2819
|
"aria-label": label,
|
|
2677
|
-
onClick: handleClick,
|
|
2678
2820
|
onKeyDown: (e) => {
|
|
2679
2821
|
if (e.key === "Enter" || e.key === " ") {
|
|
2680
2822
|
e.preventDefault();
|
|
@@ -2688,29 +2830,59 @@ var Upload = React21.forwardRef(
|
|
|
2688
2830
|
// Padding: top/bottom 20px (py-5), left/right 12px (px-3)
|
|
2689
2831
|
"py-5 px-3"
|
|
2690
2832
|
),
|
|
2691
|
-
children:
|
|
2692
|
-
/* @__PURE__ */ jsx21(
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2833
|
+
children: [
|
|
2834
|
+
/* @__PURE__ */ jsx21(
|
|
2835
|
+
"input",
|
|
2836
|
+
{
|
|
2837
|
+
ref: inputRef,
|
|
2838
|
+
type: "file",
|
|
2839
|
+
accept,
|
|
2840
|
+
multiple,
|
|
2841
|
+
className: "absolute inset-0 z-10 w-full cursor-pointer opacity-0",
|
|
2842
|
+
onChange: handleInputChange,
|
|
2843
|
+
tabIndex: -1,
|
|
2844
|
+
"aria-hidden": "true"
|
|
2845
|
+
}
|
|
2846
|
+
),
|
|
2847
|
+
/* @__PURE__ */ jsxs16("div", { className: "relative z-0 flex flex-col items-center gap-2 text-center pointer-events-none", children: [
|
|
2848
|
+
/* @__PURE__ */ jsx21("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-200 text-blue-600", children: /* @__PURE__ */ jsx21(CloudUpload, { size: 20, color: colors.blue["600"], "aria-hidden": "true" }) }),
|
|
2849
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1", children: [
|
|
2850
|
+
/* @__PURE__ */ jsxs16("p", { className: "text-sm leading-6 text-neutral-1100", children: [
|
|
2851
|
+
/* @__PURE__ */ jsx21("span", { className: "font-normal", children: "Drop your files here or " }),
|
|
2852
|
+
/* @__PURE__ */ jsx21(
|
|
2853
|
+
Button,
|
|
2854
|
+
{
|
|
2855
|
+
type: "button",
|
|
2856
|
+
variant: "link",
|
|
2857
|
+
size: "small",
|
|
2858
|
+
className: "pointer-events-auto font-medium p-0 h-auto inline",
|
|
2859
|
+
onClick: (e) => {
|
|
2860
|
+
e.stopPropagation();
|
|
2861
|
+
handleClick();
|
|
2862
|
+
},
|
|
2863
|
+
children: "Click to upload"
|
|
2864
|
+
}
|
|
2865
|
+
)
|
|
2866
|
+
] }),
|
|
2867
|
+
/* @__PURE__ */ jsx21("p", { className: "text-xs font-normal leading-4 text-neutral-600", children: helperText })
|
|
2868
|
+
] })
|
|
2710
2869
|
] })
|
|
2711
|
-
]
|
|
2870
|
+
]
|
|
2712
2871
|
}
|
|
2713
|
-
)
|
|
2872
|
+
),
|
|
2873
|
+
fileEntries.length > 0 && /* @__PURE__ */ jsx21("div", { className: "flex flex-col gap-3", children: fileEntries.map((entry) => /* @__PURE__ */ jsx21(
|
|
2874
|
+
UploadFileItem,
|
|
2875
|
+
{
|
|
2876
|
+
status: entry.status,
|
|
2877
|
+
fileName: entry.file.name,
|
|
2878
|
+
fileSize: formatFileSize(entry.file.size),
|
|
2879
|
+
progress: entry.progress,
|
|
2880
|
+
onCancel: () => removeEntry(entry.id),
|
|
2881
|
+
onRetry: () => setEntryStatus(entry.id, "uploading", 0),
|
|
2882
|
+
onDelete: () => removeEntry(entry.id)
|
|
2883
|
+
},
|
|
2884
|
+
entry.id
|
|
2885
|
+
)) })
|
|
2714
2886
|
]
|
|
2715
2887
|
}
|
|
2716
2888
|
);
|
|
@@ -2857,6 +3029,7 @@ export {
|
|
|
2857
3029
|
TabsTrigger,
|
|
2858
3030
|
Textarea,
|
|
2859
3031
|
Toast,
|
|
3032
|
+
ToastProvider,
|
|
2860
3033
|
Toggle,
|
|
2861
3034
|
Tooltip,
|
|
2862
3035
|
TooltipContent,
|
|
@@ -2886,5 +3059,6 @@ export {
|
|
|
2886
3059
|
toggleVariants,
|
|
2887
3060
|
tooltipContentVariants,
|
|
2888
3061
|
uploadBoxVariants,
|
|
2889
|
-
uploadFileItemBoxVariants
|
|
3062
|
+
uploadFileItemBoxVariants,
|
|
3063
|
+
useToast
|
|
2890
3064
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sangam-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"esds"
|
|
15
15
|
],
|
|
16
16
|
"author": {
|
|
17
|
-
"name": "
|
|
18
|
-
"email": "
|
|
17
|
+
"name": "ESDS",
|
|
18
|
+
"email": "ESDS-Design-All@esds.co.in",
|
|
19
19
|
"url": "https://esds.com"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|