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 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: Fixed 450px
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
- * Sangam Toast Component
1100
- *
1101
- * Production-grade toast notification following Figma design specs:
1102
- * - Fixed width: 384px
1103
- * - Height: 56px (default), 84px (with description or CTA)
1104
- * - Border radius: 8px
1105
- * - Left border width: 3px
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 isFilled = !!value && value.toString().length > 0;
1315
- const currentLength = value ? value.toString().length : 0;
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" : "text-neutral-1100"
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" : "text-neutral-1100"
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" : "text-neutral-1100"
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 mt-",
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 || !onFilesSelected) return;
2741
+ if (!fileList) return;
2635
2742
  const files = Array.from(fileList);
2636
- if (files.length) onFilesSelected(files);
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__ */ jsx21(
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: /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center gap-2 text-center", children: [
2692
- /* @__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" }) }),
2693
- /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1", children: [
2694
- /* @__PURE__ */ jsxs16("p", { className: "text-sm leading-6 text-neutral-1100", children: [
2695
- /* @__PURE__ */ jsx21("span", { className: "font-normal", children: "Drop your files here or " }),
2696
- /* @__PURE__ */ jsx21(
2697
- "button",
2698
- {
2699
- type: "button",
2700
- className: "font-medium text-blue-600 underline-offset-4 hover:underline",
2701
- onClick: (e) => {
2702
- e.stopPropagation();
2703
- handleClick();
2704
- },
2705
- children: "Click to upload"
2706
- }
2707
- )
2708
- ] }),
2709
- /* @__PURE__ */ jsx21("p", { className: "text-xs font-normal leading-4 text-neutral-600", children: helperText })
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.8",
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": "Rahul Kuvlekar",
18
- "email": "rahul.kuvlekar@esds.com",
17
+ "name": "ESDS",
18
+ "email": "ESDS-Design-All@esds.co.in",
19
19
  "url": "https://esds.com"
20
20
  },
21
21
  "exports": {