@umituz/web-dashboard 4.0.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,11 +14,8 @@ Your bundler compiles it alongside your app; no prebuilt bundle ships.
14
14
 
15
15
  - **Billing moved to [@umituz/web-polar-payment](https://github.com/umituz/web-polar-payment)**
16
16
  under the `./portal` entry point. The old `./billing*` entry points are gone.
17
- - **`@umituz/web-design-system` is no longer a dependency.** The primitives
18
- (`Button`, `Input`, `Checkbox`, `Skeleton`, `cn`) live in `./primitives`.
19
17
  - **Styling is shipped, not assumed.** Import `@umituz/web-dashboard/styles.css`
20
- (and optionally `tokens.css`) once; all components use `udb-*` BEM classes.
21
- Tailwind, design-system tokens and utility classes are gone from the package.
18
+ once; all components use `udb-*` BEM classes.
22
19
  - **No i18n runtime.** `react-i18next` is out; hosts bridge their own `t` via
23
20
  `<DashboardI18nProvider t={i18n.t}>` (see below). Without a provider,
24
21
  keys humanize to readable English.
@@ -30,6 +27,31 @@ Your bundler compiles it alongside your app; no prebuilt bundle ships.
30
27
  texora/visora pattern (one module list drives routes + nav + guards) as
31
28
  reusable building blocks.
32
29
 
30
+ ## What changed in 4.1
31
+
32
+ - **The design system is back — for real this time.** 4.0 dropped
33
+ `@umituz/web-design-system` entirely; 4.1 makes it a runtime dependency
34
+ again and actually renders it:
35
+ - `./primitives` (`Button`, `Input`, `Checkbox`, `Skeleton`, `cn`) are thin
36
+ adapters that **delegate to the DS components** — the dashboard prop API
37
+ is unchanged, call sites need zero edits.
38
+ - `UserManagementTable` renders the DS `DataTable`; `MetricCard` composes
39
+ the DS `MetricCard`; `AnalyticsCard` is built on DS `Card` primitives.
40
+ - `styles.css` imports `@umituz/web-design-system/styles.css` transitively
41
+ (Tailwind v4 precompiled) — hosts still import only the dashboard
42
+ stylesheet.
43
+ - **`--u-*` tokens are now bridges.** `tokens.css` maps every color/radius/
44
+ shadow token to the DS `--color-*`/`--radius-*` variables (with the old
45
+ literal as fallback). Override either side; dark mode (`data-udb-theme="dark"`)
46
+ now sets DS variables and the bridge flows through.
47
+ - **Primitive base CSS is gone from `dashboard.css`.** Only dashboard-owned
48
+ modifiers remain (`udb-btn__spinner`, `udb-input--error/--with-toggle`,
49
+ `udb-skeleton--*`, `udb-card--sm/--lg`, `udb-table` for the permission
50
+ matrix); bases come from the DS stylesheet.
51
+ - **Peer footprint**: pulling the dashboard in transitively installs the DS's
52
+ Radix UI peers (npm 7+ handles this automatically; only imported components
53
+ end up in the bundle).
54
+
33
55
  ## Installation
34
56
 
35
57
  ```bash
@@ -38,7 +60,7 @@ import "@umituz/web-dashboard/styles.css";
38
60
  ```
39
61
 
40
62
  Peers: `react` + `react-dom` (^18 or ^19) and `react-router-dom` (^6 or ^7).
41
- Runtime deps: `clsx` + `lucide-react`. Nothing else.
63
+ Runtime deps: `@umituz/web-design-system` + `lucide-react`. Nothing else.
42
64
 
43
65
  ### Tailwind hosts
44
66
 
@@ -48,8 +70,12 @@ resets: import the package stylesheet **after** your base layer.
48
70
 
49
71
  ### Theming & dark mode
50
72
 
51
- Override the `--u-*` custom properties (see `tokens.css` for the full list).
52
- Dark mode is host-driven: put `data-udb-theme="dark"` on any ancestor.
73
+ `tokens.css` bridges every `--u-*` token to a design-system variable
74
+ (`--u-color-primary: var(--color-primary, #2563eb)`), so you can theme two
75
+ ways: override the `--u-*` bridge heads, or set the DS `--color-*`/`--radius-*`
76
+ variables (e.g. from your own Tailwind `@theme`). Dark mode is host-driven:
77
+ put `data-udb-theme="dark"` on any ancestor — the dashboard dark block sets
78
+ the DS variables and both layers switch together.
53
79
 
54
80
  ## Entry points
55
81
 
@@ -175,7 +201,7 @@ readably. The per-domain key catalogs are the string contract:
175
201
  import { AUTH_KEYS } from "@umituz/web-dashboard/auth";
176
202
  import { ANALYTICS_KEYS } from "@umituz/web-dashboard/analytics";
177
203
  import { ONBOARDING_KEYS } from "@umituz/web-dashboard/onboarding";
178
- import { SETTINGS_ADMIN_KEYS } from "@umituz/web-dashboard/settings/utils/i18nKeys";
204
+ import { SETTINGS_ADMIN_KEYS } from "@umituz/web-dashboard/settings/utils";
179
205
  import { PERMISSIONS_KEYS } from "@umituz/web-dashboard/permissions";
180
206
  import { SESSION_KEYS } from "@umituz/web-dashboard/session";
181
207
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "Installable app skeleton for the fleet — layouts, auth, onboarding, analytics (dependency-free SVG charts), calendar, i18n injection, permissions/session/module registry; ships its own udb-* stylesheet and --u-* tokens",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -71,10 +71,11 @@
71
71
  "react-router-dom": "^6.0.0 || ^7.0.0"
72
72
  },
73
73
  "dependencies": {
74
- "clsx": "^2.1.1",
74
+ "@umituz/web-design-system": "^4.0.2",
75
75
  "lucide-react": "^0.577.0"
76
76
  },
77
77
  "devDependencies": {
78
+ "@types/node": "^22.20.5",
78
79
  "@types/react": "^19.2.14",
79
80
  "@types/react-dom": "^19.2.3",
80
81
  "@typescript-eslint/eslint-plugin": "^8.57.2",
@@ -1,21 +1,25 @@
1
1
  /**
2
- * Analytics Card Component
3
- *
4
- * Configurable analytics card with chart or metrics. Surface comes
5
- * from the shared `udb-card` helper; size modifiers shrink/grow the
6
- * padding ("full" shares the md default).
2
+ * Analytics Card — chart/metric container on the design-system Card.
3
+ * Size modifiers ("sm"/"lg") are dashboard-owned padding overrides.
7
4
  */
8
5
 
9
6
  import { Loader2, AlertCircle } from "lucide-react";
7
+ import {
8
+ Card,
9
+ CardHeader,
10
+ CardTitle,
11
+ CardDescription,
12
+ CardContent,
13
+ } from "@umituz/web-design-system/organisms";
10
14
  import { cn } from "../../../primitives";
11
15
  import type { AnalyticsCardProps } from "../types/analytics";
12
16
  import { MetricCard } from "./MetricCard";
13
17
  import { AnalyticsChart } from "./AnalyticsChart";
14
18
 
15
19
  const SURFACE_CLASS = {
16
- sm: 'udb-card--sm',
20
+ sm: "udb-card--sm",
17
21
  md: undefined,
18
- lg: 'udb-card--lg',
22
+ lg: "udb-card--lg",
19
23
  full: undefined,
20
24
  } as const;
21
25
 
@@ -32,50 +36,45 @@ export const AnalyticsCard = ({
32
36
  }: AnalyticsCardProps) => {
33
37
  if (loading) {
34
38
  return (
35
- <div className={cn("udb-card", SURFACE_CLASS[size], className)}>
39
+ <Card className={cn(SURFACE_CLASS[size], className)}>
36
40
  <div className="udb-analytics-card__status" role="status">
37
41
  <Loader2 className="udb-spin udb-analytics-card__spinner" aria-hidden="true" />
38
42
  </div>
39
- </div>
43
+ </Card>
40
44
  );
41
45
  }
42
46
 
43
47
  if (error) {
44
48
  return (
45
- <div className={cn("udb-card", SURFACE_CLASS[size], className)}>
49
+ <Card className={cn(SURFACE_CLASS[size], className)}>
46
50
  <div className="udb-analytics-card__status udb-analytics-card__status--error" role="alert">
47
51
  <AlertCircle className="udb-analytics-card__error-icon" aria-hidden="true" />
48
52
  <p className="udb-analytics-card__error-text">{error}</p>
49
53
  </div>
50
- </div>
54
+ </Card>
51
55
  );
52
56
  }
53
57
 
54
58
  return (
55
- <div className={cn("udb-card", SURFACE_CLASS[size], className)}>
56
- {/* Header */}
59
+ <Card className={cn(SURFACE_CLASS[size], className)}>
57
60
  {(title || description) && (
58
- <div className="udb-analytics-card__header">
59
- {title && <h3 className="udb-analytics-card__title">{title}</h3>}
60
- {description && <p className="udb-analytics-card__description">{description}</p>}
61
- </div>
62
- )}
63
-
64
- {/* Chart */}
65
- {chart && <AnalyticsChart config={chart} />}
66
-
67
- {/* Metrics */}
68
- {metrics && metrics.length > 0 && (
69
- <div className="udb-analytics-card__metrics">
70
- {metrics.map((metric) => (
71
- <MetricCard key={metric.id} metric={metric} size="sm" />
72
- ))}
73
- </div>
61
+ <CardHeader>
62
+ {title && <CardTitle>{title}</CardTitle>}
63
+ {description && <CardDescription>{description}</CardDescription>}
64
+ </CardHeader>
74
65
  )}
75
-
76
- {/* Custom Content */}
77
- {children}
78
- </div>
66
+ <CardContent>
67
+ {chart && <AnalyticsChart config={chart} />}
68
+ {metrics && metrics.length > 0 && (
69
+ <div className="udb-analytics-card__metrics">
70
+ {metrics.map((metric) => (
71
+ <MetricCard key={metric.id} metric={metric} size="sm" />
72
+ ))}
73
+ </div>
74
+ )}
75
+ {children}
76
+ </CardContent>
77
+ </Card>
79
78
  );
80
79
  };
81
80
 
@@ -1,14 +1,12 @@
1
1
  /**
2
- * MetricCard
3
- *
4
- * Single KPI tile with a trend indicator. Colors and sizes come
5
- * from `--u-*` tokens via the `udb-metric-*` classes (no hardcoded
6
- * green/orange) and a11y role=button when interactive.
2
+ * MetricCard — dashboard logic (growth, i18n, a11y) on top of the
3
+ * design-system MetricCard presentation.
7
4
  */
8
5
 
9
- import { ArrowUp, ArrowDown, Minus } from "lucide-react";
6
+ import {
7
+ MetricCard as DSMetricCard,
8
+ } from "@umituz/web-design-system/organisms";
10
9
  import { useT } from "../../../i18n";
11
- import { cn } from "../../../primitives";
12
10
  import type { MetricCardProps } from "../types/analytics";
13
11
  import { formatMetricValue, calculateGrowth, getTrend } from "../utils/analytics";
14
12
  import { ANALYTICS_KEYS } from "../utils/i18nKeys";
@@ -25,61 +23,27 @@ export const MetricCard = ({
25
23
 
26
24
  const hasPrevious = metric.previousValue !== undefined;
27
25
  const growth = hasPrevious ? calculateGrowth(metric.value, metric.previousValue as number) : null;
28
- const trend = growth !== null ? getTrend(growth) : 'stable';
29
-
30
- const TrendIcon = trend === 'up' ? ArrowUp : trend === 'down' ? ArrowDown : Minus;
31
-
32
- const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
33
- if (!onClick) return;
34
- if (e.key === 'Enter' || e.key === ' ') {
35
- e.preventDefault();
36
- onClick();
37
- }
38
- };
26
+ const trend = growth !== null ? getTrend(growth) : "stable";
39
27
 
40
28
  return (
41
- <div
42
- role={onClick ? 'button' : undefined}
43
- tabIndex={onClick ? 0 : undefined}
44
- aria-label={onClick ? `${metric.name} — ${formatMetricValue(metric)}` : undefined}
29
+ <DSMetricCard
30
+ className={className}
31
+ title={metric.name}
32
+ value={formatMetricValue(metric)}
33
+ icon={showIcon ? metric.icon : undefined}
34
+ iconColor="text-primary"
35
+ size={size}
36
+ trend={
37
+ showTrend && growth !== null && trend !== "stable"
38
+ ? {
39
+ value: growth,
40
+ label: t(ANALYTICS_KEYS.common.lastPeriod),
41
+ }
42
+ : undefined
43
+ }
44
+ trendValueFormatter={(v) => `${Math.abs(v).toFixed(1)}%`}
45
45
  onClick={onClick}
46
- onKeyDown={onClick ? handleKeyDown : undefined}
47
- className={cn(
48
- 'udb-metric',
49
- `udb-metric--${size}`,
50
- onClick && 'udb-metric--interactive',
51
- className,
52
- )}
53
- >
54
- <div className="udb-metric__head">
55
- <div className="udb-metric__body">
56
- <p className="udb-metric__name">{metric.name}</p>
57
-
58
- <div className="udb-metric__value">{formatMetricValue(metric)}</div>
59
-
60
- {showTrend && hasPrevious && growth !== null && (
61
- <div
62
- className={cn('udb-metric__trend', `udb-metric__trend--${trend}`)}
63
- aria-label={t(ANALYTICS_KEYS.metric.change)}
64
- >
65
- <TrendIcon className="udb-metric__trend-icon" aria-hidden="true" />
66
- <span className="udb-metric__trend-value">
67
- {Math.abs(growth).toFixed(1)}%
68
- </span>
69
- <span className="udb-metric__trend-period">
70
- {t(ANALYTICS_KEYS.common.lastPeriod)}
71
- </span>
72
- </div>
73
- )}
74
- </div>
75
-
76
- {showIcon && metric.icon && (
77
- <div className="udb-metric__icon-box">
78
- <metric.icon className="udb-metric__icon" aria-hidden="true" />
79
- </div>
80
- )}
81
- </div>
82
- </div>
46
+ />
83
47
  );
84
48
  };
85
49
 
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * UserManagementTable — admin skeleton for the users screen.
3
3
  * Config-driven: rows and role catalog come in as props, role
4
- * changes delegate out. No fetching, no package-side copy beyond
5
- * the empty state.
4
+ * changes delegate out.
6
5
  */
7
6
 
8
- import { Users } from "lucide-react";
7
+ import { DataTable, type Column } from "@umituz/web-design-system/organisms";
9
8
  import { useT } from "../../../i18n";
10
9
  import { SETTINGS_ADMIN_KEYS } from "../utils/i18nKeys";
11
10
  import type { RoleKey, RoleInfo } from "../../permissions/types";
@@ -32,55 +31,38 @@ export const UserManagementTable = ({
32
31
  }: UserManagementTableProps) => {
33
32
  const t = useT();
34
33
 
35
- if (users.length === 0) {
36
- return (
37
- <div className="udb-empty">
38
- <div className="udb-empty__icon">
39
- <Users className="udb-empty__glyph" aria-hidden="true" />
40
- </div>
41
- <p className="udb-empty__text">{t(SETTINGS_ADMIN_KEYS.users.empty)}</p>
42
- </div>
43
- );
44
- }
34
+ const columns: Column<ManagedUser>[] = [
35
+ { id: "name", header: t("settings.users.columns.name"), accessor: "name" },
36
+ { id: "email", header: t("settings.users.columns.email"), accessor: "email" },
37
+ {
38
+ id: "role",
39
+ header: t("settings.users.columns.role"),
40
+ accessor: (user) =>
41
+ onRoleChange ? (
42
+ <select
43
+ className="udb-period-select__select"
44
+ value={user.role}
45
+ aria-label={t("settings.users.columns.role")}
46
+ onChange={(e) => onRoleChange(user.id, e.target.value as RoleKey)}
47
+ >
48
+ {roles.map((role) => (
49
+ <option key={role.key} value={role.key}>
50
+ {role.label}
51
+ </option>
52
+ ))}
53
+ </select>
54
+ ) : (
55
+ roles.find((role) => role.key === user.role)?.label ?? user.role
56
+ ),
57
+ },
58
+ ];
45
59
 
46
60
  return (
47
- <div className="udb-table__wrap">
48
- <table className="udb-table">
49
- <thead>
50
- <tr>
51
- <th className="udb-table__th">{t("settings.users.columns.name")}</th>
52
- <th className="udb-table__th">{t("settings.users.columns.email")}</th>
53
- <th className="udb-table__th">{t("settings.users.columns.role")}</th>
54
- </tr>
55
- </thead>
56
- <tbody>
57
- {users.map((user) => (
58
- <tr key={user.id} className="udb-table__row">
59
- <td className="udb-table__td">{user.name}</td>
60
- <td className="udb-table__td">{user.email}</td>
61
- <td className="udb-table__td">
62
- {onRoleChange ? (
63
- <select
64
- className="udb-period-select__select"
65
- value={user.role}
66
- aria-label={t("settings.users.columns.role")}
67
- onChange={(e) => onRoleChange(user.id, e.target.value)}
68
- >
69
- {roles.map((role) => (
70
- <option key={role.key} value={role.key}>
71
- {role.label}
72
- </option>
73
- ))}
74
- </select>
75
- ) : (
76
- roles.find((role) => role.key === user.role)?.label ?? user.role
77
- )}
78
- </td>
79
- </tr>
80
- ))}
81
- </tbody>
82
- </table>
83
- </div>
61
+ <DataTable
62
+ data={users}
63
+ columns={columns}
64
+ emptyState={{ title: t(SETTINGS_ADMIN_KEYS.users.empty) }}
65
+ />
84
66
  );
85
67
  };
86
68
 
@@ -9,3 +9,4 @@ export {
9
9
  getSettingsDefaultRoute,
10
10
  generateSettingsKey,
11
11
  } from './settings';
12
+ export { SETTINGS_ADMIN_KEYS } from './i18nKeys';
@@ -29,6 +29,6 @@ const interpolate = (text: string, params?: Record<string, unknown>): string =>
29
29
 
30
30
  export const defaultT: TranslateFn = (key, params) => {
31
31
  const segments = key.split(".").filter(Boolean);
32
- const source = segments.length > 0 ? segments[segments.length - 1] : key;
32
+ const source = segments[segments.length - 1] ?? key;
33
33
  return interpolate(humanizeSegment(source), params);
34
34
  };
@@ -1,29 +1,23 @@
1
1
  /**
2
- * Button — camp-agnostic action button (`udb-btn`).
3
- *
4
- * Clones only the surface this package actually uses: variants
5
- * default/destructive/outline/ghost, sizes default/sm/icon. No `asChild`
6
- * (radix-free), no variant library — classes come from `styles.css`.
2
+ * Button — delegates to the design system.
3
+ * Dashboard surface: variants default/destructive/outline/ghost,
4
+ * sizes default/sm/icon. Call-site API unchanged.
7
5
  */
8
6
 
9
- import { forwardRef, type ButtonHTMLAttributes } from 'react';
10
- import { cn } from './cn';
7
+ import { forwardRef } from 'react';
8
+ import { Button as DSButton } from '@umituz/web-design-system/atoms';
11
9
 
12
10
  export type ButtonVariant = 'default' | 'destructive' | 'outline' | 'ghost';
13
11
  export type ButtonSize = 'default' | 'sm' | 'icon';
14
12
 
15
- export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
13
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
16
14
  variant?: ButtonVariant;
17
15
  size?: ButtonSize;
18
16
  }
19
17
 
20
18
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
21
- ({ className, variant = 'default', size = 'default', ...props }, ref) => (
22
- <button
23
- ref={ref}
24
- className={cn('udb-btn', `udb-btn--${variant}`, `udb-btn--${size}`, className)}
25
- {...props}
26
- />
19
+ ({ variant = 'default', size = 'default', ...props }, ref) => (
20
+ <DSButton ref={ref} variant={variant} size={size} {...props} />
27
21
  ),
28
22
  );
29
23
 
@@ -1,40 +1,17 @@
1
1
  /**
2
- * Checkbox — native checkbox with a boolean-change callback (`udb-checkbox`).
3
- *
4
- * Keeps the `onCheckedChange` convenience this package relies on; every
5
- * other native attribute passes straight through.
2
+ * Checkbox — delegates to the design system (native + onCheckedChange).
6
3
  */
7
4
 
8
- import { forwardRef, useCallback, type InputHTMLAttributes } from 'react';
9
- import { cn } from './cn';
5
+ import { forwardRef } from 'react';
6
+ import { Checkbox as DSCheckbox } from '@umituz/web-design-system/atoms';
10
7
 
11
- export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
8
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
12
9
  checked?: boolean;
13
10
  onCheckedChange?: (checked: boolean) => void;
14
11
  }
15
12
 
16
- export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
17
- ({ className, checked, onCheckedChange, onChange, ...props }, ref) => {
18
- // Memoized so parent re-renders don't recreate the handler identity.
19
- const handleChange = useCallback(
20
- (e: React.ChangeEvent<HTMLInputElement>) => {
21
- onCheckedChange?.(e.target.checked);
22
- onChange?.(e);
23
- },
24
- [onCheckedChange, onChange],
25
- );
26
-
27
- return (
28
- <input
29
- ref={ref}
30
- type="checkbox"
31
- checked={checked}
32
- onChange={handleChange}
33
- className={cn('udb-checkbox', className)}
34
- {...props}
35
- />
36
- );
37
- },
38
- );
13
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => (
14
+ <DSCheckbox ref={ref} {...props} />
15
+ ));
39
16
 
40
17
  Checkbox.displayName = 'Checkbox';
@@ -1,18 +1,22 @@
1
1
  /**
2
- * Input — text input field (`udb-input`).
3
- *
4
- * Minimal clone of the surface this package uses: a native input with
5
- * token-driven styling. Everything else passes through untouched.
2
+ * Input — delegates to the design system.
3
+ * Native `size` (character width) has no DS counterpart; it maps to a
4
+ * `ch`-based width so the original prop API keeps working.
6
5
  */
7
6
 
8
- import { forwardRef, type InputHTMLAttributes } from 'react';
9
- import { cn } from './cn';
7
+ import { forwardRef } from 'react';
8
+ import { Input as DSInput } from '@umituz/web-design-system/atoms';
10
9
 
11
- export type InputProps = InputHTMLAttributes<HTMLInputElement>;
10
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
12
11
 
13
12
  export const Input = forwardRef<HTMLInputElement, InputProps>(
14
- ({ className, type = 'text', ...props }, ref) => (
15
- <input ref={ref} type={type} className={cn('udb-input', className)} {...props} />
13
+ ({ type = 'text', size: nativeSize, style, ...props }, ref) => (
14
+ <DSInput
15
+ ref={ref}
16
+ type={type}
17
+ {...props}
18
+ style={nativeSize != null ? { width: `${nativeSize}ch`, ...style } : style}
19
+ />
16
20
  ),
17
21
  );
18
22
 
@@ -1,26 +1,6 @@
1
1
  /**
2
- * Skeleton — loading placeholder (`udb-skeleton`).
3
- *
4
- * The pulse animation is defined in `styles.css` (a CSS file can ship
5
- * keyframes; inline styles cannot).
2
+ * Skeleton — re-exported from the design system (variant API identical).
6
3
  */
7
4
 
8
- import { forwardRef, type HTMLAttributes } from 'react';
9
- import { cn } from './cn';
10
-
11
- export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
12
- variant?: 'text' | 'circular' | 'rectangular';
13
- }
14
-
15
- export const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
16
- ({ className, variant = 'rectangular', ...props }, ref) => (
17
- <div
18
- ref={ref}
19
- className={cn('udb-skeleton', `udb-skeleton--${variant}`, className)}
20
- role="status"
21
- {...props}
22
- />
23
- ),
24
- );
25
-
26
- Skeleton.displayName = 'Skeleton';
5
+ export { Skeleton } from '@umituz/web-design-system/atoms';
6
+ export type { SkeletonProps } from '@umituz/web-design-system/atoms';
@@ -1,15 +1,10 @@
1
1
  /**
2
- * cn — conditional className join (clsx only).
2
+ * cn — conditional className join, delegated to the design system.
3
3
  *
4
- * Deliberately drops the old tailwind-merge layer: v4 ships its own
5
- * `udb-*` class vocabulary where overrides append instead of conflicting,
6
- * so plain conditional joining is the whole job.
4
+ * The design system's `cn` (clsx + tailwind-merge) understands both the
5
+ * `udb-*` vocabulary (unknown classes pass through untouched) and real
6
+ * Tailwind utilities a host may pass, resolving conflicts by last-wins.
7
7
  */
8
8
 
9
- import { clsx, type ClassValue } from 'clsx';
10
-
11
- export type { ClassValue };
12
-
13
- export function cn(...inputs: ClassValue[]): string {
14
- return clsx(inputs);
15
- }
9
+ export { cn } from '@umituz/web-design-system/utils/cn';
10
+ export type { ClassValue } from '@umituz/web-design-system/utils/cn';
@@ -10,12 +10,15 @@
10
10
  * tokens (see `tokens.css`); override them for theming and dark mode.
11
11
  *
12
12
  * Sections:
13
- * 1. Scope & reset 5. Sidebar 9. Onboarding
14
- * 2. Primitives 6. Header / menus 10. Settings
15
- * 3. Layout helpers 7. Auth 11. Analytics
16
- * 4. App shell 8. Forms (auth) 12. Shared blocks
13
+ * 1. Scope & reset 5. Header/menus 9. Settings
14
+ * 2. Layout helpers 6. Auth 10. Analytics
15
+ * 3. App shell 7. Forms (auth) 11. Shared blocks
16
+ * 4. Sidebar 8. Onboarding
17
17
  */
18
18
 
19
+ @import "@umituz/web-design-system/styles.css";
20
+ @import "./tokens.css";
21
+
19
22
  /* ============================================================
20
23
  * 1. Scope & reset
21
24
  * ============================================================ */
@@ -50,191 +53,7 @@
50
53
  }
51
54
 
52
55
  /* ============================================================
53
- * 2. Primitives — Button
54
- * ============================================================ */
55
-
56
- .udb-btn {
57
- display: inline-flex;
58
- align-items: center;
59
- justify-content: center;
60
- gap: var(--u-space-2);
61
- white-space: nowrap;
62
- border: 1px solid transparent;
63
- border-radius: var(--u-radius-md);
64
- font-family: inherit;
65
- font-size: var(--u-font-size-md);
66
- font-weight: var(--u-font-weight-medium);
67
- line-height: 1;
68
- cursor: pointer;
69
- text-decoration: none;
70
- transition: background-color 0.15s ease, color 0.15s ease,
71
- border-color 0.15s ease, transform 0.1s ease;
72
- }
73
-
74
- .udb-btn:focus-visible {
75
- outline: none;
76
- box-shadow: 0 0 0 2px var(--u-color-surface), 0 0 0 4px var(--u-color-ring);
77
- }
78
-
79
- .udb-btn:disabled {
80
- pointer-events: none;
81
- opacity: 0.55;
82
- }
83
-
84
- .udb-btn:active:not(:disabled) {
85
- transform: scale(0.97);
86
- }
87
-
88
- .udb-btn svg {
89
- pointer-events: none;
90
- width: 1em;
91
- height: 1em;
92
- flex-shrink: 0;
93
- }
94
-
95
- /* Variants */
96
- .udb-btn--default {
97
- background: var(--u-color-primary);
98
- color: var(--u-color-primary-contrast);
99
- }
100
-
101
- .udb-btn--default:hover:not(:disabled) {
102
- background: var(--u-color-primary-hover);
103
- }
104
-
105
- .udb-btn--destructive {
106
- background: var(--u-color-danger);
107
- color: var(--u-color-danger-contrast);
108
- }
109
-
110
- .udb-btn--destructive:hover:not(:disabled) {
111
- background: var(--u-color-danger-hover);
112
- }
113
-
114
- .udb-btn--outline {
115
- background: var(--u-color-surface);
116
- color: var(--u-color-text);
117
- border-color: var(--u-color-border-strong);
118
- }
119
-
120
- .udb-btn--outline:hover:not(:disabled) {
121
- background: var(--u-color-surface-muted);
122
- }
123
-
124
- .udb-btn--ghost {
125
- background: transparent;
126
- color: var(--u-color-text);
127
- }
128
-
129
- .udb-btn--ghost:hover:not(:disabled) {
130
- background: var(--u-color-surface-muted);
131
- }
132
-
133
- /* Sizes */
134
- .udb-btn--default {
135
- height: 40px;
136
- padding: 0 var(--u-space-4);
137
- }
138
-
139
- .udb-btn--sm {
140
- height: 32px;
141
- padding: 0 var(--u-space-3);
142
- font-size: var(--u-font-size-sm);
143
- }
144
-
145
- .udb-btn--icon {
146
- height: 36px;
147
- width: 36px;
148
- padding: 0;
149
- }
150
-
151
- /* ============================================================
152
- * 2. Primitives — Input / Checkbox / Skeleton
153
- * ============================================================ */
154
-
155
- .udb-input {
156
- display: block;
157
- width: 100%;
158
- height: 40px;
159
- padding: 0 var(--u-space-3);
160
- background: var(--u-color-surface);
161
- border: 1px solid var(--u-color-border-strong);
162
- border-radius: var(--u-radius-md);
163
- color: var(--u-color-text);
164
- font-family: inherit;
165
- font-size: var(--u-font-size-md);
166
- }
167
-
168
- .udb-input::placeholder {
169
- color: var(--u-color-muted);
170
- }
171
-
172
- .udb-input:focus-visible {
173
- outline: none;
174
- border-color: var(--u-color-primary);
175
- box-shadow: 0 0 0 3px var(--u-color-ring);
176
- }
177
-
178
- .udb-input:disabled {
179
- cursor: not-allowed;
180
- opacity: 0.55;
181
- background: var(--u-color-surface-muted);
182
- }
183
-
184
- .udb-checkbox {
185
- width: 18px;
186
- height: 18px;
187
- margin: 0;
188
- accent-color: var(--u-color-primary);
189
- cursor: pointer;
190
- flex-shrink: 0;
191
- }
192
-
193
- .udb-checkbox:disabled {
194
- cursor: not-allowed;
195
- opacity: 0.55;
196
- }
197
-
198
- .udb-checkbox:focus-visible {
199
- outline: none;
200
- box-shadow: 0 0 0 3px var(--u-color-ring);
201
- border-radius: var(--u-radius-sm);
202
- }
203
-
204
- .udb-skeleton {
205
- background: var(--u-color-surface-muted);
206
- animation: udb-pulse 1.6s ease-in-out infinite;
207
- border-radius: var(--u-radius-md);
208
- }
209
-
210
- .udb-skeleton--text {
211
- height: 16px;
212
- width: 100%;
213
- }
214
-
215
- .udb-skeleton--circular {
216
- height: 48px;
217
- width: 48px;
218
- border-radius: 50%;
219
- }
220
-
221
- .udb-skeleton--rectangular {
222
- height: 96px;
223
- width: 100%;
224
- }
225
-
226
- @keyframes udb-pulse {
227
- 0%,
228
- 100% {
229
- opacity: 1;
230
- }
231
- 50% {
232
- opacity: 0.55;
233
- }
234
- }
235
-
236
- /* ============================================================
237
- * 3. Layout helpers
56
+ * 2. Layout helpers
238
57
  * ============================================================ */
239
58
 
240
59
  .udb-stack {
@@ -254,14 +73,6 @@
254
73
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
255
74
  }
256
75
 
257
- .udb-card {
258
- background: var(--u-color-surface);
259
- border: 1px solid var(--u-color-border);
260
- border-radius: var(--u-radius-lg);
261
- padding: var(--u-space-6);
262
- box-shadow: var(--u-shadow-sm);
263
- }
264
-
265
76
  .udb-card--sm {
266
77
  padding: var(--u-space-4);
267
78
  }
@@ -271,7 +82,7 @@
271
82
  }
272
83
 
273
84
  /* ============================================================
274
- * 4. App shell
85
+ * 3. App shell
275
86
  * ============================================================ */
276
87
 
277
88
  .udb-layout {
@@ -376,7 +187,7 @@
376
187
  }
377
188
 
378
189
  /* ============================================================
379
- * 5. Sidebar
190
+ * 4. Sidebar
380
191
  * ============================================================ */
381
192
 
382
193
  .udb-sidebar {
@@ -589,7 +400,7 @@
589
400
  }
590
401
 
591
402
  /* ============================================================
592
- * 6. Header / menus / notifications / profile
403
+ * 5. Header / menus / notifications / profile
593
404
  * ============================================================ */
594
405
 
595
406
  .udb-header {
@@ -1015,7 +826,7 @@
1015
826
  }
1016
827
 
1017
828
  /* ============================================================
1018
- * 7. Auth pages
829
+ * 6. Auth pages
1019
830
  * ============================================================ */
1020
831
 
1021
832
  .udb-auth {
@@ -1208,7 +1019,7 @@
1208
1019
  }
1209
1020
 
1210
1021
  /* ============================================================
1211
- * 8. Forms (auth + generic fields)
1022
+ * 7. Forms (auth + generic fields)
1212
1023
  * ============================================================ */
1213
1024
 
1214
1025
  /* Shared spinner (also used by onboarding/analytics icons). */
@@ -1551,7 +1362,7 @@
1551
1362
  }
1552
1363
 
1553
1364
  /* ============================================================
1554
- * 9. Onboarding wizard
1365
+ * 8. Onboarding wizard
1555
1366
  * ============================================================ */
1556
1367
 
1557
1368
  .udb-onboarding {
@@ -2202,7 +2013,7 @@
2202
2013
  }
2203
2014
 
2204
2015
  /* ============================================================
2205
- * 10. Settings
2016
+ * 9. Settings
2206
2017
  * ============================================================ */
2207
2018
 
2208
2019
  .udb-settings {
@@ -2452,7 +2263,7 @@
2452
2263
  }
2453
2264
 
2454
2265
  /* ============================================================
2455
- * 11. Analytics
2266
+ * 10. Analytics
2456
2267
  * ============================================================ */
2457
2268
 
2458
2269
  .udb-analytics {
@@ -2572,131 +2383,6 @@
2572
2383
  cursor: pointer;
2573
2384
  }
2574
2385
 
2575
- /* KPI tile. */
2576
- .udb-metric {
2577
- background: var(--u-color-surface);
2578
- border: 1px solid var(--u-color-border);
2579
- border-radius: var(--u-radius-lg);
2580
- padding: var(--u-space-6);
2581
- box-shadow: var(--u-shadow-sm);
2582
- }
2583
-
2584
- .udb-metric--sm {
2585
- padding: var(--u-space-4);
2586
- }
2587
-
2588
- .udb-metric--lg {
2589
- padding: var(--u-space-8);
2590
- }
2591
-
2592
- .udb-metric--interactive {
2593
- cursor: pointer;
2594
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
2595
- }
2596
-
2597
- .udb-metric--interactive:hover {
2598
- border-color: var(--u-color-primary);
2599
- }
2600
-
2601
- .udb-metric--interactive:focus-visible {
2602
- outline: none;
2603
- box-shadow: 0 0 0 3px var(--u-color-ring);
2604
- }
2605
-
2606
- .udb-metric__head {
2607
- display: flex;
2608
- align-items: flex-start;
2609
- justify-content: space-between;
2610
- gap: var(--u-space-3);
2611
- }
2612
-
2613
- .udb-metric__body {
2614
- flex: 1;
2615
- min-width: 0;
2616
- }
2617
-
2618
- .udb-metric__name {
2619
- margin: 0 0 var(--u-space-1);
2620
- font-size: var(--u-font-size-md);
2621
- font-weight: var(--u-font-weight-medium);
2622
- color: var(--u-color-muted);
2623
- }
2624
-
2625
- .udb-metric__value {
2626
- font-size: var(--u-font-size-3xl);
2627
- font-weight: var(--u-font-weight-bold);
2628
- letter-spacing: -0.02em;
2629
- line-height: 1.2;
2630
- color: var(--u-color-text);
2631
- }
2632
-
2633
- .udb-metric--sm .udb-metric__name {
2634
- font-size: var(--u-font-size-sm);
2635
- }
2636
-
2637
- .udb-metric--sm .udb-metric__value {
2638
- font-size: var(--u-font-size-xl);
2639
- }
2640
-
2641
- .udb-metric--lg .udb-metric__name {
2642
- font-size: var(--u-font-size-lg);
2643
- }
2644
-
2645
- .udb-metric--lg .udb-metric__value {
2646
- font-size: var(--u-font-size-4xl);
2647
- }
2648
-
2649
- /* Trend row: one modifier tones the icon + value together;
2650
- * the "vs last period" label stays muted. */
2651
- .udb-metric__trend {
2652
- display: flex;
2653
- align-items: center;
2654
- gap: var(--u-space-1);
2655
- margin-top: var(--u-space-2);
2656
- color: var(--u-color-muted);
2657
- }
2658
-
2659
- .udb-metric__trend--up {
2660
- color: var(--u-color-success);
2661
- }
2662
-
2663
- .udb-metric__trend--down {
2664
- color: var(--u-color-danger);
2665
- }
2666
-
2667
- .udb-metric__trend-icon {
2668
- width: 16px;
2669
- height: 16px;
2670
- flex-shrink: 0;
2671
- }
2672
-
2673
- .udb-metric__trend-value {
2674
- font-size: var(--u-font-size-md);
2675
- font-weight: var(--u-font-weight-medium);
2676
- }
2677
-
2678
- .udb-metric__trend-period {
2679
- font-size: var(--u-font-size-md);
2680
- color: var(--u-color-muted);
2681
- }
2682
-
2683
- .udb-metric__icon-box {
2684
- display: flex;
2685
- align-items: center;
2686
- justify-content: center;
2687
- width: 48px;
2688
- height: 48px;
2689
- flex-shrink: 0;
2690
- border-radius: var(--u-radius-md);
2691
- background: var(--u-color-primary-soft);
2692
- }
2693
-
2694
- .udb-metric__icon {
2695
- width: 24px;
2696
- height: 24px;
2697
- color: var(--u-color-primary);
2698
- }
2699
-
2700
2386
  /* Chart card internals. */
2701
2387
  .udb-analytics-card__status {
2702
2388
  display: flex;
@@ -2810,7 +2496,7 @@
2810
2496
  }
2811
2497
 
2812
2498
  /* ============================================================
2813
- * 12. Shared blocks — empty state, tables, org switcher, matrix,
2499
+ * 11. Shared blocks — empty state, tables, org switcher, matrix,
2814
2500
  * audit list (used by guards, session and settings skeletons)
2815
2501
  * ============================================================ */
2816
2502
 
@@ -63,4 +63,29 @@ describe("dashboard stylesheet token contract", () => {
63
63
  it("provides dark-mode overrides", () => {
64
64
  expect(tokensCss).toContain('[data-udb-theme="dark"]');
65
65
  });
66
+
67
+ it("bridges core tokens to design-system variables with fallbacks", () => {
68
+ const BRIDGED = [
69
+ ["--u-color-primary", "var(--color-primary, #2563eb)"],
70
+ ["--u-color-bg", "var(--color-background, #f8fafc)"],
71
+ ["--u-color-danger", "var(--color-destructive, #dc2626)"],
72
+ ["--u-radius-md", "var(--radius-md, 8px)"],
73
+ ["--u-shadow-lg", "var(--shadow-lg,"],
74
+ ["--u-font-sans", "var(--font-sans,"],
75
+ ] as const;
76
+ for (const [token, bridge] of BRIDGED) {
77
+ expect(tokensCss).toContain(`${token}: ${bridge}`);
78
+ }
79
+ });
80
+
81
+ it("derives hover/soft shades from the bridged base color", () => {
82
+ expect(tokensCss).toMatch(/--u-color-primary-hover: var\(--color-primary-hover, color-mix\(/);
83
+ expect(tokensCss).toMatch(/--u-color-primary-soft: var\(--color-primary-soft, color-mix\(/);
84
+ });
85
+
86
+ it("pulls the design-system stylesheet transitively", () => {
87
+ const imports = [...dashboardCss.matchAll(/@import\s+"([^"]+)"/g)].map((m) => m[1]);
88
+ expect(imports[0]).toBe("@umituz/web-design-system/styles.css");
89
+ expect(imports).toContain("./tokens.css");
90
+ });
66
91
  });
@@ -1,53 +1,51 @@
1
1
  /**
2
2
  * @umituz/web-dashboard — design tokens (`--u-*`)
3
3
  *
4
- * The whole visual surface of the package is driven by these custom
5
- * properties. Hosts restyle everything (including dark mode) by
6
- * overriding them — no utility framework, no host theme variables.
7
- *
8
- * Dark mode: add `data-udb-theme="dark"` on any ancestor (typically
9
- * <html> or the layout root). The package never switches themes on
10
- * its own — the host decides.
4
+ * Colors, radii, fonts and shadows are bridged to the design system's
5
+ * `--color-*` / `--radius-*` / `--font-*` / `--shadow-*` variables; each
6
+ * bridge keeps its previous value as fallback. Dark mode: add
7
+ * `data-udb-theme="dark"` on any ancestor — the dark block re-sets the
8
+ * design-system variables and every bridge follows automatically.
11
9
  */
12
10
 
13
11
  :root {
14
- /* Palette — surfaces & text */
15
- --u-color-bg: #f8fafc;
16
- --u-color-surface: #ffffff;
17
- --u-color-surface-muted: #f1f5f9;
18
- --u-color-text: #0f172a;
19
- --u-color-muted: #64748b;
20
- --u-color-border: #e2e8f0;
12
+ /* Surfaces & text */
13
+ --u-color-bg: var(--color-background, #f8fafc);
14
+ --u-color-surface: var(--color-card, #ffffff);
15
+ --u-color-surface-muted: var(--color-muted, #f1f5f9);
16
+ --u-color-text: var(--color-foreground, #0f172a);
17
+ --u-color-muted: var(--color-muted-foreground, #64748b);
18
+ --u-color-border: var(--color-border, #e2e8f0);
21
19
  --u-color-border-strong: #cbd5e1;
22
20
 
23
- /* Palette — brand */
24
- --u-color-primary: #2563eb;
25
- --u-color-primary-hover: #1d4ed8;
26
- --u-color-primary-contrast: #ffffff;
27
- --u-color-primary-soft: rgba(37, 99, 235, 0.1);
28
- --u-color-secondary: #8b5cf6;
29
-
30
- /* Palette — semantic */
31
- --u-color-danger: #dc2626;
32
- --u-color-danger-hover: #b91c1c;
33
- --u-color-danger-contrast: #ffffff;
34
- --u-color-danger-soft: rgba(220, 38, 38, 0.1);
35
- --u-color-success: #16a34a;
36
- --u-color-success-soft: rgba(22, 163, 74, 0.12);
37
- --u-color-warning: #d97706;
38
- --u-color-warning-soft: rgba(217, 119, 6, 0.15);
21
+ /* Brand (hover/soft derive from the base color) */
22
+ --u-color-primary: var(--color-primary, #2563eb);
23
+ --u-color-primary-hover: var(--color-primary-hover, color-mix(in oklab, var(--color-primary, #2563eb) 86%, #000));
24
+ --u-color-primary-contrast: var(--color-primary-foreground, #ffffff);
25
+ --u-color-primary-soft: var(--color-primary-soft, color-mix(in srgb, var(--color-primary, #2563eb) 10%, transparent));
26
+ --u-color-secondary: var(--color-secondary, #8b5cf6);
27
+
28
+ /* Semantic */
29
+ --u-color-danger: var(--color-destructive, #dc2626);
30
+ --u-color-danger-hover: var(--color-destructive-hover, color-mix(in oklab, var(--color-destructive, #dc2626) 85%, #000));
31
+ --u-color-danger-contrast: var(--color-destructive-foreground, #ffffff);
32
+ --u-color-danger-soft: var(--color-destructive-soft, color-mix(in srgb, var(--color-destructive, #dc2626) 10%, transparent));
33
+ --u-color-success: var(--color-success, #16a34a);
34
+ --u-color-success-soft: var(--color-success-soft, color-mix(in srgb, var(--color-success, #16a34a) 12%, transparent));
35
+ --u-color-warning: var(--color-warning, #d97706);
36
+ --u-color-warning-soft: var(--color-warning-soft, color-mix(in srgb, var(--color-warning, #d97706) 15%, transparent));
39
37
 
40
38
  /* Focus */
41
- --u-color-ring: rgba(37, 99, 235, 0.45);
39
+ --u-color-ring: var(--color-ring, rgba(37, 99, 235, 0.45));
42
40
 
43
41
  /* Radii */
44
- --u-radius-sm: 6px;
45
- --u-radius-md: 8px;
46
- --u-radius-lg: 12px;
47
- --u-radius-xl: 16px;
42
+ --u-radius-sm: var(--radius-sm, 6px);
43
+ --u-radius-md: var(--radius-md, 8px);
44
+ --u-radius-lg: var(--radius-lg, 12px);
45
+ --u-radius-xl: var(--radius-xl, 16px);
48
46
  --u-radius-pill: 9999px;
49
47
 
50
- /* Spacing scale (4px base) */
48
+ /* Spacing (4px base) */
51
49
  --u-space-1: 4px;
52
50
  --u-space-2: 8px;
53
51
  --u-space-3: 12px;
@@ -58,8 +56,8 @@
58
56
  --u-space-8: 40px;
59
57
 
60
58
  /* Typography */
61
- --u-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
62
- "Helvetica Neue", Arial, "Noto Sans", sans-serif;
59
+ --u-font-sans: var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
60
+ "Helvetica Neue", Arial, "Noto Sans", sans-serif);
63
61
  --u-font-size-xs: 11px;
64
62
  --u-font-size-sm: 12px;
65
63
  --u-font-size-md: 14px;
@@ -72,17 +70,17 @@
72
70
  --u-font-weight-medium: 500;
73
71
  --u-font-weight-bold: 700;
74
72
 
75
- /* Layout metrics */
73
+ /* Layout */
76
74
  --u-sidebar-width: 240px;
77
75
  --u-sidebar-width-collapsed: 64px;
78
76
  --u-header-height: 56px;
79
77
 
80
78
  /* Elevation */
81
- --u-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
82
- --u-shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08);
83
- --u-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.14);
79
+ --u-shadow-sm: var(--shadow-sm, 0 1px 2px rgba(15, 23, 42, 0.06));
80
+ --u-shadow-md: var(--shadow-md, 0 4px 12px rgba(15, 23, 42, 0.08));
81
+ --u-shadow-lg: var(--shadow-lg, 0 12px 32px rgba(15, 23, 42, 0.14));
84
82
 
85
- /* Sidebar palette (distinct surface so hosts can tint it) */
83
+ /* Sidebar palette */
86
84
  --u-sidebar-bg: var(--u-color-surface);
87
85
  --u-sidebar-text: var(--u-color-text);
88
86
  --u-sidebar-muted: var(--u-color-muted);
@@ -92,33 +90,27 @@
92
90
  }
93
91
 
94
92
  [data-udb-theme="dark"] {
95
- --u-color-bg: #0b1120;
96
- --u-color-surface: #111827;
97
- --u-color-surface-muted: #1e293b;
98
- --u-color-text: #f1f5f9;
99
- --u-color-muted: #94a3b8;
100
- --u-color-border: #1f2a3c;
101
- --u-color-border-strong: #334155;
93
+ --color-background: #0b1120;
94
+ --color-card: #111827;
95
+ --color-muted: #1e293b;
96
+ --color-foreground: #f1f5f9;
97
+ --color-muted-foreground: #94a3b8;
98
+ --color-border: #1f2a3c;
102
99
 
103
- --u-color-primary: #3b82f6;
104
- --u-color-primary-hover: #60a5fa;
105
- --u-color-primary-contrast: #ffffff;
106
- --u-color-primary-soft: rgba(59, 130, 246, 0.16);
100
+ --color-primary: #3b82f6;
101
+ --color-primary-foreground: #ffffff;
102
+ --color-secondary: #a78bfa;
107
103
 
108
- --u-color-secondary: #a78bfa;
104
+ --color-destructive: #ef4444;
105
+ --color-destructive-foreground: #ffffff;
106
+ --color-success: #22c55e;
107
+ --color-warning: #f59e0b;
109
108
 
110
- --u-color-danger: #ef4444;
111
- --u-color-danger-hover: #f87171;
112
- --u-color-danger-contrast: #ffffff;
113
- --u-color-danger-soft: rgba(239, 68, 68, 0.16);
114
- --u-color-success: #22c55e;
115
- --u-color-success-soft: rgba(34, 197, 94, 0.16);
116
- --u-color-warning: #f59e0b;
117
- --u-color-warning-soft: rgba(245, 158, 11, 0.16);
109
+ --color-ring: rgba(96, 165, 250, 0.5);
118
110
 
119
- --u-color-ring: rgba(96, 165, 250, 0.5);
111
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
112
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
113
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
120
114
 
121
- --u-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
122
- --u-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
123
- --u-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
115
+ --u-color-border-strong: #334155;
124
116
  }