@hexdspace/react 0.1.6 → 0.1.8

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
@@ -1,6 +1,6 @@
1
1
  # @hexdspace/react
2
2
 
3
- React basic features, UI components plus a Tailwind v4-compatible theme CSS.
3
+ React basic features, UI components, plus a Tailwind v4-compatible theme CSS.
4
4
 
5
5
  ## Install
6
6
 
@@ -17,9 +17,21 @@ pnpm add react react-dom react-router-dom radix-ui lucide-react react-toastify @
17
17
  Tailwind requirements (for the theme + utilities):
18
18
 
19
19
  ```sh
20
- pnpm add -D tailwindcss @tailwindcss/postcss tailwind-scrollbar
20
+ pnpm add -D tailwindcss tailwind-scrollbar
21
21
  ```
22
22
 
23
+ if you're on Vite:
24
+ ```sh
25
+ pnpm add -D @tailwindcss/vite
26
+ ```
27
+
28
+ if using postcss instead:
29
+ ```shell
30
+ pnpm add @tailwindcss/postcss
31
+ ```
32
+
33
+ and configure your postcss pipeline to include tailwind.
34
+
23
35
  ## Usage
24
36
 
25
37
  Import the theme into your Tailwind entry stylesheet and specify tailwind config:
@@ -27,7 +39,7 @@ Import the theme into your Tailwind entry stylesheet and specify tailwind config
27
39
  ```css
28
40
  /* src/index.css */
29
41
  @import "@hexdspace/react/css";
30
- @config "../tailwind.config.ts"; /* adjust path if needed */
42
+ @config "../tailwind.config.ts"; /* adjust path */
31
43
 
32
44
  /* Your app styles */
33
45
  ```
@@ -95,11 +95,6 @@
95
95
  --color-warning: var(--warning);
96
96
  --color-danger: var(--danger);
97
97
 
98
- --color-info-soft: var(--info-soft);
99
- --color-success-soft: var(--success-soft);
100
- --color-warning-soft: var(--warning-soft);
101
- --color-danger-soft: var(--danger-soft);
102
-
103
98
  --color-on-info: var(--on-info);
104
99
  --color-on-success: var(--on-success);
105
100
  --color-on-warning: var(--on-warning);
@@ -1,2 +1,3 @@
1
1
  @import "./base-theme.css";
2
2
  @import "tailwindcss";
3
+ @config "../../../../tailwind.config.mjs";
@@ -19,6 +19,11 @@
19
19
  --shape-radius-50: 1.5rem; /* 24px */
20
20
  --shape-radius-pill: 999px;
21
21
 
22
+ /* Motion */
23
+ --motion-fast: 0.1s;
24
+ --motion-med: 0.16s;
25
+ --motion-slow: 0.24s;
26
+
22
27
  /* Neutral ramp */
23
28
  --ink-10: oklch(0.92 0.043 258);
24
29
  --ink-20: oklch(0.84 0.043 258);
@@ -32,16 +37,17 @@
32
37
  --ink-100: oklch(0.16 0.025 258);
33
38
 
34
39
  /* Accent */
35
- --accent-10: oklch(0.900 0.020 120);
36
- --accent-20: oklch(0.870 0.025 120);
37
- --accent-30: oklch(0.855 0.028 120);
38
- --accent-40: oklch(0.845 0.030 120);
39
- --accent-50: oklch(0.8428 0.030 120);
40
- --accent-60: oklch(0.780 0.045 120);
41
- --accent-70: oklch(0.680 0.060 120);
42
- --accent-80: oklch(0.580 0.070 120);
43
- --accent-90: oklch(0.500 0.065 120);
44
- --accent-100: oklch(0.430 0.055 120);
40
+ /* Accent (cool mint green) */
41
+ --accent-10: oklch(0.93 0.028 160);
42
+ --accent-20: oklch(0.87 0.052 160);
43
+ --accent-30: oklch(0.84 0.068 160);
44
+ --accent-40: oklch(0.80 0.085 160);
45
+ --accent-50: oklch(0.74 0.105 160);
46
+ --accent-60: oklch(0.66 0.125 160);
47
+ --accent-70: oklch(0.58 0.135 160);
48
+ --accent-80: oklch(0.48 0.125 160);
49
+ --accent-90: oklch(0.42 0.105 160);
50
+ --accent-100: oklch(0.36 0.105 160);
45
51
 
46
52
  /* Brand ramp */
47
53
  --brand-10: oklch(0.97 0.020 250);
@@ -4,7 +4,7 @@
4
4
  :root {
5
5
  /* Surfaces */
6
6
  --bg: var(--ink-10);
7
- --surface-1: color-mix(in oklab, var(--ink-10), black 8%);
7
+ --surface-1: color-mix(in oklab, var(--ink-10), black 10%);
8
8
  --surface-2: color-mix(in oklab, var(--ink-10), black 6%);
9
9
  --surface-3: color-mix(in oklab, var(--ink-10), black 4%);
10
10
 
@@ -19,14 +19,16 @@
19
19
  --divider: color-mix(in oklab, var(--border), transparent 55%);
20
20
 
21
21
  /* Brand + accent roles */
22
- --brand: var(--brand-70);
23
- --brand-strong: var(--brand-90);
22
+ --brand: var(--brand-60);
23
+ --brand-strong: var(--brand-80);
24
+ --brand-contrast: var(--brand-90);
24
25
 
25
- --accent: var(--accent-70);
26
- --accent-strong: var(--accent-80);
26
+ --accent: var(--accent-50);
27
+ --accent-strong: var(--accent-70);
28
+ --accent-contrast: var(--accent-80);
27
29
 
28
30
  /* UI roles */
29
- --link: var(--accent);
31
+ --link: var(--accent-contrast);
30
32
  --link-hover: color-mix(in oklab, var(--link), white 14%);
31
33
  --focus: var(--brand);
32
34
  --focus-ring: 0 0 0 3px color-mix(in oklab, var(--focus), transparent 60%);
@@ -37,10 +39,10 @@
37
39
  --warning: var(--warning-60);
38
40
  --danger: var(--danger-60);
39
41
 
40
- --info-soft: color-mix(in oklab, var(--info), var(--bg) 85%);
41
- --success-soft: color-mix(in oklab, var(--success), var(--bg) 85%);
42
- --warning-soft: color-mix(in oklab, var(--warning), var(--bg) 85%);
43
- --danger-soft: color-mix(in oklab, var(--danger), var(--bg) 85%);
42
+ --info-contrast: var(--info-90);
43
+ --success-contrast: var(--success-80);
44
+ --warning-contrast: var(--warning-90);
45
+ --danger-contrast: var(--danger-80);
44
46
 
45
47
  /* On-colors */
46
48
  --on-info: oklch(0.16 0 0);
@@ -49,8 +51,8 @@
49
51
  --on-danger: oklch(0.16 0 0);
50
52
 
51
53
  /* Interaction tokens */
52
- --surface-hover: color-mix(in oklab, var(--surface-1), black 10%);
53
- --surface-active: color-mix(in oklab, var(--surface-1), black 12%);
54
+ --surface-hover: color-mix(in oklab, var(--surface-1), black 2%);
55
+ --surface-active: color-mix(in oklab, var(--surface-1), black 4%);
54
56
 
55
57
  /* Overlay */
56
58
  --overlay: oklch(0.16 0 0 / 0.40);
@@ -88,13 +90,15 @@
88
90
  --border: var(--ink-40);
89
91
  --divider: color-mix(in oklab, var(--border), transparent 40%);
90
92
 
91
- --brand: var(--brand-50);
92
- --brand-strong: var(--brand-40);
93
+ --brand: var(--brand-80);
94
+ --brand-strong: var(--brand-100);
95
+ --brand-contrast: var(--brand-50);
93
96
 
94
- --accent: var(--accent-70);
95
- --accent-strong: var(--accent-60);
97
+ --accent: var(--accent-80);
98
+ --accent-strong: var(--accent-100);
99
+ --accent-contrast: var(--accent-50);
96
100
 
97
- --link: var(--accent);
101
+ --link: var(--accent-contrast);
98
102
  --link-hover: color-mix(in oklab, var(--link), white 18%);
99
103
  --focus: var(--brand);
100
104
  --focus-ring: 0 0 0 3px color-mix(in oklab, var(--focus), transparent 60%);
@@ -104,6 +108,11 @@
104
108
  --warning: var(--warning-60);
105
109
  --danger: var(--danger-60);
106
110
 
111
+ --info-contrast: var(--info-50);
112
+ --success-contrast: var(--success-50);
113
+ --warning-contrast: var(--warning-50);
114
+ --danger-contrast: var(--danger-50);
115
+
107
116
  --surface-hover: color-mix(in oklab, var(--surface-1), white 6%);
108
117
  --surface-active: color-mix(in oklab, var(--surface-1), white 8%);
109
118
 
@@ -118,3 +127,7 @@
118
127
  --disabled-bg: color-mix(in oklab, var(--surface-2), transparent 35%);
119
128
  --disabled-border: color-mix(in oklab, var(--border), transparent 55%);
120
129
  }
130
+
131
+ @media (prefers-reduced-motion: reduce) {
132
+ transition-duration: 1ms; animation-duration: 1ms;
133
+ }
package/dist/index.d.ts CHANGED
@@ -352,17 +352,6 @@ type AuthControllerProviderProps = {
352
352
  };
353
353
  declare function AuthControllerProvider({ children, controller }: AuthControllerProviderProps): react_jsx_runtime.JSX.Element;
354
354
 
355
- interface InputProps {
356
- id: string;
357
- label: string;
358
- type: string;
359
- value: string;
360
- onChange: (e: React__default.ChangeEvent<HTMLInputElement>) => void;
361
- placeholder: string;
362
- className?: string;
363
- }
364
- declare const AuthFormInputField: React__default.FC<InputProps>;
365
-
366
355
  type RequireAuthProps = {
367
356
  guarded?: ReactNode;
368
357
  loadingPlaceholder?: ReactNode;
@@ -400,7 +389,7 @@ declare class MockAuthHttpClient extends MockHttpClient {
400
389
  }
401
390
 
402
391
  declare const buttonVariants: (props?: ({
403
- variant?: "success" | "warning" | "info" | "link" | "primary" | "secondary" | "outline" | "ghost" | "danger" | null | undefined;
392
+ variant?: "success" | "warning" | "info" | "link" | "primary" | "accent" | "secondary" | "outline" | "ghost" | "danger" | null | undefined;
404
393
  size?: "icon" | "sm" | "md" | "lg" | null | undefined;
405
394
  chrome?: "push" | "flat" | "inset" | "soft" | "glass" | "glow" | "hairline" | null | undefined;
406
395
  fullWidth?: boolean | null | undefined;
@@ -411,7 +400,54 @@ interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>
411
400
  loading?: boolean;
412
401
  leftIcon?: React.ReactNode;
413
402
  rightIcon?: React.ReactNode;
403
+ reserveLeftIcon?: boolean;
404
+ reserveRightIcon?: boolean;
414
405
  }
415
406
  declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
416
407
 
417
- export { AuthController, AuthControllerCtx, type AuthControllerDeps, AuthControllerProvider, AuthDispatchCtx, AuthFormInputField, AuthProvider, type AuthState, AuthStateCtx, Button, type ButtonProps, type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type ErrorResponse, type GenericResponse, type HttpClient, HttpError, type HttpMethod, type HttpResponse, type Instruction, type InstructionContext, MockAuthHttpClient, MockHttpClient, type MutationFn, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OnSuccessFn, type OptimisticSnapshot, type OptimisticUpdateFn, RedirectIfAuthed, type RedirectIfAuthedProps, type RequestConfig, RequireAuth, type RequireAuthProps, type ResolvedToastTheme, type ResponsiveMutation, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, type User, authController, controllerFactory, createAuthController, httpClient as fetchHttpClient, notifierController, resolveToastTheme, ui, useAuth, useAuthActions, useAuthController, useAuthDispatch, useAuthedUser, useResponsiveMutation };
408
+ type ControlLikeProps = {
409
+ id?: string;
410
+ disabled?: boolean;
411
+ required?: boolean;
412
+ invalid?: boolean;
413
+ "aria-describedby"?: string;
414
+ };
415
+ interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
416
+ label?: React.ReactNode;
417
+ hint?: React.ReactNode;
418
+ error?: React.ReactNode;
419
+ required?: boolean;
420
+ disabled?: boolean;
421
+ id?: string;
422
+ children: React.ReactElement<ControlLikeProps>;
423
+ }
424
+ declare function Field({ label, hint, error, required, disabled, id, children, className, ...props }: FieldProps): react_jsx_runtime.JSX.Element;
425
+
426
+ declare const controlShellVariants: (props?: ({
427
+ variant?: "outline" | "ghost" | "surface" | null | undefined;
428
+ size?: "sm" | "md" | "lg" | null | undefined;
429
+ fullWidth?: boolean | null | undefined;
430
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
431
+ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">, VariantProps<typeof controlShellVariants> {
432
+ invalid?: boolean;
433
+ leftSlot?: React.ReactNode;
434
+ rightSlot?: React.ReactNode;
435
+ reserveLeftSlot?: boolean;
436
+ reserveRightSlot?: boolean;
437
+ inputClassName?: string;
438
+ }
439
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
440
+
441
+ declare const textareaShellVariants: (props?: ({
442
+ variant?: "outline" | "ghost" | "surface" | null | undefined;
443
+ size?: "sm" | "md" | "lg" | null | undefined;
444
+ fullWidth?: boolean | null | undefined;
445
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
446
+ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement>, VariantProps<typeof textareaShellVariants> {
447
+ invalid?: boolean;
448
+ textareaClassName?: string;
449
+ autoResize?: boolean;
450
+ }
451
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
452
+
453
+ export { AuthController, AuthControllerCtx, type AuthControllerDeps, AuthControllerProvider, AuthDispatchCtx, AuthProvider, type AuthState, AuthStateCtx, Button, type ButtonProps, type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type ErrorResponse, Field, type FieldProps, type GenericResponse, type HttpClient, HttpError, type HttpMethod, type HttpResponse, Input, type InputProps, type Instruction, type InstructionContext, MockAuthHttpClient, MockHttpClient, type MutationFn, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OnSuccessFn, type OptimisticSnapshot, type OptimisticUpdateFn, RedirectIfAuthed, type RedirectIfAuthedProps, type RequestConfig, RequireAuth, type RequireAuthProps, type ResolvedToastTheme, type ResponsiveMutation, Textarea, type TextareaProps, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, type User, authController, controllerFactory, createAuthController, httpClient as fetchHttpClient, notifierController, resolveToastTheme, ui, useAuth, useAuthActions, useAuthController, useAuthDispatch, useAuthedUser, useResponsiveMutation };
package/dist/index.js CHANGED
@@ -897,42 +897,9 @@ function useAuthedUser() {
897
897
  return auth.user;
898
898
  }
899
899
 
900
- // src/feature/auth/interface/web/react/AuthFormInputField.tsx
901
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
902
- var inputLabelStyles = {
903
- display: "block",
904
- fontSize: "small",
905
- fontWeight: "bolder",
906
- marginBottom: "4px"
907
- };
908
- var inputFieldStyles = {
909
- display: "block",
910
- width: "100%",
911
- padding: "0.5rem 0.75rem"
912
- };
913
- var AuthFormInputField = ({ id, label, type, value, onChange, placeholder, className }) => {
914
- return /* @__PURE__ */ jsxs2("div", { children: [
915
- /* @__PURE__ */ jsx5("label", { htmlFor: id, style: inputLabelStyles, children: label }),
916
- /* @__PURE__ */ jsx5(
917
- "input",
918
- {
919
- id,
920
- name: id,
921
- type,
922
- required: true,
923
- value,
924
- onChange,
925
- className,
926
- placeholder,
927
- style: inputFieldStyles
928
- }
929
- )
930
- ] });
931
- };
932
-
933
900
  // src/feature/auth/interface/web/react/auth-route-guards.tsx
934
901
  import { Navigate, Outlet, useLocation } from "react-router-dom";
935
- import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
902
+ import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
936
903
  function RequireAuth({
937
904
  guarded,
938
905
  loadingPlaceholder,
@@ -943,18 +910,18 @@ function RequireAuth({
943
910
  const auth = useAuth();
944
911
  const location = useLocation();
945
912
  if (auth.status === "loading") {
946
- return /* @__PURE__ */ jsx6(Fragment, { children: loadingPlaceholder ?? null });
913
+ return /* @__PURE__ */ jsx5(Fragment, { children: loadingPlaceholder ?? null });
947
914
  }
948
915
  if (auth.status === "error") {
949
- return /* @__PURE__ */ jsx6(Fragment, { children: errorPlaceholder ?? null });
916
+ return /* @__PURE__ */ jsx5(Fragment, { children: errorPlaceholder ?? null });
950
917
  }
951
918
  if (auth.status === "unauthenticated") {
952
919
  if (unauthenticatedRedirectTo) {
953
- return /* @__PURE__ */ jsx6(Navigate, { to: unauthenticatedRedirectTo, replace: true, state: { from: location } });
920
+ return /* @__PURE__ */ jsx5(Navigate, { to: unauthenticatedRedirectTo, replace: true, state: { from: location } });
954
921
  }
955
- return /* @__PURE__ */ jsx6(Fragment, { children: unauthenticatedPlaceholder ?? null });
922
+ return /* @__PURE__ */ jsx5(Fragment, { children: unauthenticatedPlaceholder ?? null });
956
923
  }
957
- return guarded ? /* @__PURE__ */ jsx6(Fragment, { children: guarded }) : /* @__PURE__ */ jsx6(Outlet, {});
924
+ return guarded ? /* @__PURE__ */ jsx5(Fragment, { children: guarded }) : /* @__PURE__ */ jsx5(Outlet, {});
958
925
  }
959
926
  function RedirectIfAuthed({
960
927
  redirectTo,
@@ -965,18 +932,18 @@ function RedirectIfAuthed({
965
932
  }) {
966
933
  const auth = useAuth();
967
934
  if (auth.status === "loading") {
968
- return /* @__PURE__ */ jsx6(Fragment, { children: loadingPlaceholder ?? null });
935
+ return /* @__PURE__ */ jsx5(Fragment, { children: loadingPlaceholder ?? null });
969
936
  }
970
937
  if (auth.status === "error") {
971
- return /* @__PURE__ */ jsx6(Fragment, { children: errorPlaceholder ?? loadingPlaceholder ?? null });
938
+ return /* @__PURE__ */ jsx5(Fragment, { children: errorPlaceholder ?? loadingPlaceholder ?? null });
972
939
  }
973
940
  if (auth.status === "authenticated") {
974
941
  if (redirectTo) {
975
- return /* @__PURE__ */ jsx6(Navigate, { to: redirectTo, replace: true });
942
+ return /* @__PURE__ */ jsx5(Navigate, { to: redirectTo, replace: true });
976
943
  }
977
- return /* @__PURE__ */ jsx6(Fragment, { children: authenticatedPlaceholder ?? null });
944
+ return /* @__PURE__ */ jsx5(Fragment, { children: authenticatedPlaceholder ?? null });
978
945
  }
979
- return nonAuthedPlaceholder ? /* @__PURE__ */ jsx6(Fragment, { children: nonAuthedPlaceholder }) : /* @__PURE__ */ jsx6(Outlet, {});
946
+ return nonAuthedPlaceholder ? /* @__PURE__ */ jsx5(Fragment, { children: nonAuthedPlaceholder }) : /* @__PURE__ */ jsx5(Outlet, {});
980
947
  }
981
948
 
982
949
  // src/feature/auth/infra/mock-auth-http-client.ts
@@ -1064,22 +1031,22 @@ function cn(...inputs) {
1064
1031
  }
1065
1032
 
1066
1033
  // src/ui/components/Button.tsx
1067
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1034
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1068
1035
  var buttonBase = cn(
1069
1036
  "relative isolate inline-flex items-center justify-center gap-2 leading-none text-sm font-medium select-none cursor-pointer",
1070
1037
  "rounded-[var(--radius-btn)]",
1071
- "shadow-[var(--shadow-10)]",
1072
- "transition-[background-color,color,box-shadow,transform] duration-150 ease",
1038
+ "transition-[background-color,color,box-shadow,transform] duration-[var(--motion-med)] ease",
1039
+ "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]",
1073
1040
  "[--state-hover:color-mix(in_oklab,currentColor,transparent_92%)]",
1074
1041
  "[--state-active:color-mix(in_oklab,currentColor,transparent_86%)]",
1075
- "after:content-[''] after:absolute after:inset-0 after:rounded-[inherit] after:bg-[color:var(--state-hover)] after:opacity-0 after:pointer-events-none after:transition-opacity after:duration-100 after:ease",
1076
- "hover:after:opacity-100 active:after:bg-[color:var(--state-active)] active:after:opacity-100",
1077
- "disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed"
1042
+ "after:content-[''] after:absolute after:inset-0 after:rounded-[inherit] after:bg-[color:var(--state-hover)] after:opacity-0 after:pointer-events-none after:transition-opacity after:duration-[var(--motion-fast)] after:ease",
1043
+ "hover:after:opacity-100 active:after:bg-[color:var(--state-active)] active:after:opacity-100"
1078
1044
  );
1045
+ var flatChrome = cn();
1079
1046
  var pushChrome = cn(
1080
- "border-b-[4px]",
1047
+ "border-b-[3px]",
1081
1048
  "transition-all",
1082
- "duration-100",
1049
+ "duration-[var(--motion-fast)]",
1083
1050
  "ease-out",
1084
1051
  "shadow-[0_2px_4px_rgba(0,0,0,0.28),0_8px_16px_rgba(0,0,0,0.14)]",
1085
1052
  "hover:shadow-[0_3px_6px_rgba(0,0,0,0.30),0_12px_22px_rgba(0,0,0,0.18)]",
@@ -1087,9 +1054,10 @@ var pushChrome = cn(
1087
1054
  "active:translate-y-[1px]"
1088
1055
  );
1089
1056
  var softChrome = cn(
1057
+ "font-thin",
1090
1058
  "border",
1091
- "border-[color:color-mix(in_oklab,var(--border),transparent_20%)]",
1092
- "shadow-[0_1px_0_rgba(255,255,255,0.65)_inset,0_3px_8px_rgba(0,0,0,0.14)]"
1059
+ "border-[color:color-mix(in_oklab,var(--border),transparent_80%)]",
1060
+ "shadow-[0_0_2px_1px_rgba(255,255,255,0.35)_inset,0_2px_4px_rgba(0,0,0,0.14)]"
1093
1061
  );
1094
1062
  var insetChrome = cn(
1095
1063
  "border",
@@ -1097,7 +1065,7 @@ var insetChrome = cn(
1097
1065
  "shadow-[inset_0_2px_6px_rgba(0,0,0,0.18)]"
1098
1066
  );
1099
1067
  var glassChrome = cn(
1100
- "backdrop-blur-[6px]",
1068
+ "backdrop-blur-[2px]",
1101
1069
  "border",
1102
1070
  "shadow-[0_8px_18px_rgba(0,0,0,0.12)]"
1103
1071
  );
@@ -1116,7 +1084,8 @@ var hairlineChrome = cn(
1116
1084
  var buttonVariants = cva(buttonBase, {
1117
1085
  variants: {
1118
1086
  variant: {
1119
- primary: "bg-[color:var(--brand)] text-[color:var(--text-inverted)]",
1087
+ primary: "bg-[color:var(--brand)]",
1088
+ accent: "bg-[color:var(--accent)]",
1120
1089
  secondary: "border border-[color:var(--border)] bg-[color:var(--surface-2)]",
1121
1090
  outline: "bg-transparent border border-[color:var(--border)] shadow-none",
1122
1091
  ghost: "bg-transparent shadow-none",
@@ -1133,7 +1102,7 @@ var buttonVariants = cva(buttonBase, {
1133
1102
  icon: "h-10 w-10 p-0"
1134
1103
  },
1135
1104
  chrome: {
1136
- flat: "",
1105
+ flat: flatChrome,
1137
1106
  push: pushChrome,
1138
1107
  soft: softChrome,
1139
1108
  inset: insetChrome,
@@ -1148,25 +1117,28 @@ var buttonVariants = cva(buttonBase, {
1148
1117
  { variant: "link", className: "border-0" },
1149
1118
  { chrome: "soft", className: "text-[color:var(--text-1)]" },
1150
1119
  { chrome: "glass", className: "text-[color:var(--text-1)]" },
1151
- { chrome: "push", variant: "primary", className: "border-b-[color:var(--brand-80)]" },
1152
- { chrome: "push", variant: "secondary", className: "border-b-[color:color-mix(in oklab, var(--border), transparent 50%)]" },
1153
- { chrome: "push", variant: "outline", className: "border-b-[color:color-mix(in oklab, var(--border), transparent 50%)]" },
1120
+ { chrome: "push", variant: "primary", className: "border-b-[color:var(--brand-strong)]" },
1121
+ { chrome: "push", variant: "accent", className: "border-b-[color:var(--accent-strong)]" },
1122
+ { chrome: "push", variant: "secondary", className: "border-b-[color:color-mix(in_oklab,var(--border),transparent 50%)]" },
1123
+ { chrome: "push", variant: "outline", className: "border-b-[color:color-mix(in_oklab,var(--border),transparent 50%)]" },
1154
1124
  { chrome: "push", variant: "ghost", className: "border-b-transparent shadow-none" },
1155
1125
  { chrome: "push", variant: "success", className: "border-b-[color:var(--success-80)]" },
1156
1126
  { chrome: "push", variant: "info", className: "border-b-[color:var(--info-80)]" },
1157
1127
  { chrome: "push", variant: "warning", className: "border-b-[color:var(--warning-80)]" },
1158
1128
  { chrome: "push", variant: "danger", className: "border-b-[color:var(--danger-80)]" },
1159
1129
  { chrome: "push", variant: "link", className: "border-b-0 shadow-none" },
1160
- { chrome: "soft", variant: "primary", className: cn("bg-[color:color-mix(in_oklab,var(--brand),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--brand),transparent_55%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--brand),transparent_70%)]") },
1161
- { chrome: "soft", variant: "secondary", className: cn("bg-[color:color-mix(in_oklab,var(--surface-2),transparent_25%)]", "border-[color:color-mix(in_oklab,var(--border),transparent_15%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--border),transparent_70%)]") },
1162
- { chrome: "soft", variant: "outline", className: cn("border-[color:color-mix(in_oklab,var(--border),transparent_15%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--border),transparent_70%)]") },
1163
- { chrome: "soft", variant: "ghost", className: "shadow-[0_3px_8px_color-mix(in_oklab,var(--border),transparent_75%)]" },
1164
- { chrome: "soft", variant: "success", className: cn("bg-[color:color-mix(in_oklab,var(--success),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--success),transparent_55%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--success),transparent_70%)]") },
1165
- { chrome: "soft", variant: "info", className: cn("bg-[color:color-mix(in_oklab,var(--info),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--info),transparent_55%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--info),transparent_70%)]") },
1166
- { chrome: "soft", variant: "warning", className: cn("bg-[color:color-mix(in_oklab,var(--warning),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--warning),transparent_55%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--warning),transparent_70%)]") },
1167
- { chrome: "soft", variant: "danger", className: cn("bg-[color:color-mix(in_oklab,var(--danger),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--danger),transparent_55%)]", "shadow-[0_1px_0_rgba(255,255,255,0.5)_inset,0_3px_8px_color-mix(in_oklab,var(--danger),transparent_70%)]") },
1168
- { chrome: "soft", variant: "link", className: cn("text-[color:var(--link)]", "shadow-[0_3px_8px_color-mix(in_oklab,var(--link),transparent_75%)]") },
1130
+ { chrome: "soft", variant: "primary", className: cn("bg-[color:color-mix(in_oklab,var(--brand),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--brand),transparent_85%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--brand),transparent_70%)]") },
1131
+ { chrome: "soft", variant: "accent", className: cn("bg-[color:color-mix(in_oklab,var(--accent),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--accent),transparent_85%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--accent),transparent_70%)]") },
1132
+ { chrome: "soft", variant: "secondary", className: cn("bg-[color:color-mix(in_oklab,var(--surface-2),transparent_25%)]", "border-[color:color-mix(in_oklab,var(--border),transparent_45%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.1)_inset,0_2px_4px_color-mix(in_oklab,var(--border),transparent_70%)]") },
1133
+ { chrome: "soft", variant: "outline", className: cn("border-[color:color-mix(in_oklab,var(--border),transparent_45%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.1)_inset,0_2px_4px_color-mix(in_oklab,var(--border),transparent_70%)]") },
1134
+ { chrome: "soft", variant: "ghost", className: "shadow-[0_2px_4px_color-mix(in_oklab,var(--border),transparent_75%)]" },
1135
+ { chrome: "soft", variant: "success", className: cn("bg-[color:color-mix(in_oklab,var(--success),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--success),transparent_75%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--success),transparent_70%)]") },
1136
+ { chrome: "soft", variant: "info", className: cn("bg-[color:color-mix(in_oklab,var(--info),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--info),transparent_75%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--info),transparent_70%)]") },
1137
+ { chrome: "soft", variant: "warning", className: cn("bg-[color:color-mix(in_oklab,var(--warning),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--warning),transparent_75%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--warning),transparent_70%)]") },
1138
+ { chrome: "soft", variant: "danger", className: cn("bg-[color:color-mix(in_oklab,var(--danger),transparent_70%)]", "border-[color:color-mix(in_oklab,var(--danger),transparent_75%)]", "shadow-[0_0_2px_1px_rgba(255,255,255,0.2)_inset,0_2px_4px_color-mix(in_oklab,var(--danger),transparent_70%)]") },
1139
+ { chrome: "soft", variant: "link", className: cn("text-[color:var(--link)]", "shadow-[0_2px_4px_color-mix(in_oklab,var(--link),transparent_75%)]") },
1169
1140
  { chrome: "glass", variant: "primary", className: cn("bg-[color:color-mix(in_oklab,var(--brand),transparent_65%)]", "border-[color:var(--brand)]") },
1141
+ { chrome: "glass", variant: "accent", className: cn("bg-[color:color-mix(in_oklab,var(--accent),transparent_65%)]", "border-[color:var(--accent)]") },
1170
1142
  { chrome: "glass", variant: "secondary", className: cn("bg-[color:color-mix(in_oklab,var(--surface-2),transparent_35%)]", "border-[color:color-mix(in_oklab,var(--border),transparent_70%)]") },
1171
1143
  { chrome: "glass", variant: "outline", className: "" },
1172
1144
  { chrome: "glass", variant: "ghost", className: "" },
@@ -1176,6 +1148,7 @@ var buttonVariants = cva(buttonBase, {
1176
1148
  { chrome: "glass", variant: "danger", className: cn("bg-[color:color-mix(in_oklab,var(--danger),transparent_65%)]", "border-[color:var(--danger)]") },
1177
1149
  { chrome: "glass", variant: "link", className: "text-[color:var(--link)]" },
1178
1150
  { chrome: "glow", variant: "primary", className: cn("border-[color:var(--brand)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--brand),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--brand),transparent_55%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--brand),transparent_10%),0_14px_34px_color-mix(in_oklab,var(--brand),transparent_40%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--brand),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--brand),transparent_70%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--brand),transparent_15%),0_12px_30px_color-mix(in_oklab,var(--brand),transparent_55%)]") },
1151
+ { chrome: "glow", variant: "accent", className: cn("border-[color:var(--accent)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--accent),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--accent),transparent_55%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--accent),transparent_10%),0_14px_34px_color-mix(in_oklab,var(--accent),transparent_40%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--accent),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--accent),transparent_70%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--accent),transparent_15%),0_12px_30px_color-mix(in_oklab,var(--accent),transparent_55%)]") },
1179
1152
  { chrome: "glow", variant: "secondary", className: cn("border-[color:color-mix(in_oklab,var(--border),transparent_20%)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--border),transparent_60%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_15%),0_14px_34px_color-mix(in_oklab,var(--border),transparent_45%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--border),transparent_75%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_20%),0_12px_30px_color-mix(in_oklab,var(--border),transparent_60%)]") },
1180
1153
  { chrome: "glow", variant: "outline", className: cn("border-[color:color-mix(in_oklab,var(--border),transparent_20%)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--border),transparent_60%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_15%),0_14px_34px_color-mix(in_oklab,var(--border),transparent_45%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--border),transparent_75%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--border),transparent_20%),0_12px_30px_color-mix(in_oklab,var(--border),transparent_60%)]") },
1181
1154
  { chrome: "glow", variant: "ghost", className: "shadow-none border-transparent" },
@@ -1184,18 +1157,21 @@ var buttonVariants = cva(buttonBase, {
1184
1157
  { chrome: "glow", variant: "warning", className: cn("border-[color:var(--warning)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--warning),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--warning),transparent_55%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--warning),transparent_10%),0_14px_34px_color-mix(in_oklab,var(--warning),transparent_40%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--warning),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--warning),transparent_70%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--warning),transparent_15%),0_12px_30px_color-mix(in_oklab,var(--warning),transparent_55%)]") },
1185
1158
  { chrome: "glow", variant: "danger", className: cn("border-[color:var(--danger)]", "shadow-[0_0_0_1px_color-mix(in_oklab,var(--danger),transparent_20%),0_10px_26px_color-mix(in_oklab,var(--danger),transparent_55%)]", "hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--danger),transparent_10%),0_14px_34px_color-mix(in_oklab,var(--danger),transparent_40%)]", "dark:shadow-[0_0_0_1px_color-mix(in_oklab,var(--danger),transparent_30%),0_8px_22px_color-mix(in_oklab,var(--danger),transparent_70%)]", "dark:hover:shadow-[0_0_0_1px_color-mix(in_oklab,var(--danger),transparent_15%),0_12px_30px_color-mix(in_oklab,var(--danger),transparent_55%)]") },
1186
1159
  { chrome: "glow", variant: "link", className: "text-[color:var(--link)] shadow-none border-transparent" },
1187
- { chrome: "hairline", variant: "primary", className: "border-[color:color-mix(in_oklab,var(--brand),transparent_20%)] text-[color:var(--brand)]" },
1160
+ { chrome: "hairline", variant: "primary", className: "border-[color:color-mix(in_oklab,var(--brand-contrast),transparent_20%)] text-[color:var(--brand-contrast)]" },
1161
+ { chrome: "hairline", variant: "accent", className: "border-[color:color-mix(in_oklab,var(--accent-contrast),transparent_20%)] text-[color:var(--accent-contrast)]" },
1188
1162
  { chrome: "hairline", variant: "secondary", className: "border-[color:color-mix(in_oklab,var(--border),transparent_20%)] text-[color:var(--text-1)]" },
1189
1163
  { chrome: "hairline", variant: "outline", className: "border-[color:color-mix(in_oklab,var(--border),transparent_20%)] text-[color:var(--text-1)]" },
1190
1164
  { chrome: "hairline", variant: "ghost", className: "border-transparent text-[color:var(--text-1)]" },
1191
- { chrome: "hairline", variant: "success", className: "border-[color:color-mix(in_oklab,var(--success),transparent_20%)] text-[color:var(--success)]" },
1192
- { chrome: "hairline", variant: "info", className: "border-[color:color-mix(in_oklab,var(--info),transparent_20%)] text-[color:var(--info)]" },
1193
- { chrome: "hairline", variant: "warning", className: "border-[color:color-mix(in_oklab,var(--warning),transparent_20%)] text-[color:var(--warning)]" },
1194
- { chrome: "hairline", variant: "danger", className: "border-[color:color-mix(in_oklab,var(--danger),transparent_20%)] text-[color:var(--danger)]" },
1165
+ { chrome: "hairline", variant: "success", className: "border-[color:color-mix(in_oklab,var(--success),transparent_20%)] text-[color:var(--success-contrast)]" },
1166
+ { chrome: "hairline", variant: "info", className: "border-[color:color-mix(in_oklab,var(--info),transparent_20%)] text-[color:var(--info-contrast)]" },
1167
+ { chrome: "hairline", variant: "warning", className: "border-[color:color-mix(in_oklab,var(--warning),transparent_20%)] text-[color:var(--warning-contrast)]" },
1168
+ { chrome: "hairline", variant: "danger", className: "border-[color:color-mix(in_oklab,var(--danger),transparent_20%)] text-[color:var(--danger-contrast)]" },
1195
1169
  { chrome: "hairline", variant: "link", className: "text-[color:var(--link)] border-transparent" }
1196
1170
  ],
1197
1171
  defaultVariants: { variant: "primary", size: "md", chrome: "flat", fullWidth: false }
1198
1172
  });
1173
+ var iconSlotBase = cn("inline-flex items-center justify-center", "shrink-0");
1174
+ var iconSlotReserve = "min-w-4 min-h-4";
1199
1175
  var Button = React3.forwardRef(
1200
1176
  ({
1201
1177
  asChild = false,
@@ -1208,6 +1184,8 @@ var Button = React3.forwardRef(
1208
1184
  disabled,
1209
1185
  leftIcon,
1210
1186
  rightIcon,
1187
+ reserveLeftIcon,
1188
+ reserveRightIcon,
1211
1189
  children,
1212
1190
  onClick,
1213
1191
  type,
@@ -1219,19 +1197,19 @@ var Button = React3.forwardRef(
1219
1197
  isDisabled && "opacity-50 pointer-events-none cursor-not-allowed",
1220
1198
  className
1221
1199
  );
1222
- const content = /* @__PURE__ */ jsxs3(Fragment2, { children: [
1223
- loading ? /* @__PURE__ */ jsx7(
1224
- "span",
1225
- {
1226
- "aria-hidden": "true",
1227
- className: "inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
1228
- }
1229
- ) : leftIcon,
1230
- children,
1231
- rightIcon
1232
- ] });
1200
+ const showLeftSlot = Boolean(loading || leftIcon || reserveLeftIcon);
1201
+ const showRightSlot = Boolean(rightIcon || reserveRightIcon);
1202
+ const spinner = /* @__PURE__ */ jsx6(
1203
+ "span",
1204
+ {
1205
+ "aria-hidden": "true",
1206
+ className: "inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
1207
+ }
1208
+ );
1209
+ const leftSlot = showLeftSlot ? /* @__PURE__ */ jsx6("span", { className: cn(iconSlotBase, reserveLeftIcon && iconSlotReserve), children: loading ? spinner : leftIcon }) : null;
1210
+ const rightSlot = showRightSlot ? /* @__PURE__ */ jsx6("span", { className: cn(iconSlotBase, reserveRightIcon && iconSlotReserve), children: rightIcon }) : null;
1233
1211
  if (asChild) {
1234
- return /* @__PURE__ */ jsx7(
1212
+ return /* @__PURE__ */ jsxs2(
1235
1213
  Slot.Root,
1236
1214
  {
1237
1215
  ref,
@@ -1249,11 +1227,15 @@ var Button = React3.forwardRef(
1249
1227
  onClick?.(e);
1250
1228
  },
1251
1229
  ...props,
1252
- children: content
1230
+ children: [
1231
+ leftSlot,
1232
+ /* @__PURE__ */ jsx6(Slot.Slottable, { children }),
1233
+ rightSlot
1234
+ ]
1253
1235
  }
1254
1236
  );
1255
1237
  }
1256
- return /* @__PURE__ */ jsx7(
1238
+ return /* @__PURE__ */ jsxs2(
1257
1239
  "button",
1258
1240
  {
1259
1241
  ref,
@@ -1264,29 +1246,318 @@ var Button = React3.forwardRef(
1264
1246
  "data-loading": loading ? "" : void 0,
1265
1247
  onClick,
1266
1248
  ...props,
1267
- children: content
1249
+ children: [
1250
+ leftSlot,
1251
+ children,
1252
+ rightSlot
1253
+ ]
1268
1254
  }
1269
1255
  );
1270
1256
  }
1271
1257
  );
1272
1258
  Button.displayName = "Button";
1259
+
1260
+ // src/ui/components/Field.tsx
1261
+ import * as React5 from "react";
1262
+
1263
+ // src/ui/components/Label.tsx
1264
+ import * as React4 from "react";
1265
+ import { jsx as jsx7 } from "react/jsx-runtime";
1266
+ var Label = React4.forwardRef(
1267
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx7(
1268
+ "label",
1269
+ {
1270
+ ref,
1271
+ className: cn(
1272
+ "inline-flex items-center gap-2 text-sm font-medium text-[color:var(--text-1)]",
1273
+ className
1274
+ ),
1275
+ ...props
1276
+ }
1277
+ )
1278
+ );
1279
+ Label.displayName = "Label";
1280
+
1281
+ // src/ui/components/Field.tsx
1282
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1283
+ function Field({ label, hint, error, required, disabled, id, children, className, ...props }) {
1284
+ const reactId = React5.useId();
1285
+ const controlId = id ?? `field-${reactId}`;
1286
+ const hintId = hint ? `${controlId}-hint` : void 0;
1287
+ const errorId = error ? `${controlId}-error` : void 0;
1288
+ const childInvalid = Boolean(children.props.invalid);
1289
+ const invalid = Boolean(error) || childInvalid;
1290
+ const describedByParts = [
1291
+ children.props["aria-describedby"],
1292
+ hintId,
1293
+ errorId
1294
+ ].filter(Boolean);
1295
+ const ariaDescribedBy = describedByParts.length ? describedByParts.join(" ") : void 0;
1296
+ const control = React5.cloneElement(children, {
1297
+ id: controlId,
1298
+ disabled: disabled ?? children.props.disabled,
1299
+ required: required ?? children.props.required,
1300
+ invalid,
1301
+ "aria-describedby": ariaDescribedBy
1302
+ });
1303
+ return /* @__PURE__ */ jsxs3("div", { className: cn("grid gap-1.5", className), ...props, children: [
1304
+ label ? /* @__PURE__ */ jsx8("div", { className: "flex items-center justify-between gap-3", children: /* @__PURE__ */ jsxs3(Label, { htmlFor: controlId, children: [
1305
+ label,
1306
+ required ? /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", className: "text-(--muted)", children: " * " }) : null
1307
+ ] }) }) : null,
1308
+ control,
1309
+ error ? /* @__PURE__ */ jsx8(
1310
+ "p",
1311
+ {
1312
+ id: errorId,
1313
+ className: "text-xs leading-5 text-(--danger-contrast)",
1314
+ role: "alert",
1315
+ children: error
1316
+ }
1317
+ ) : hint ? /* @__PURE__ */ jsx8("p", { id: hintId, className: "text-xs leading-5 text-(--muted)", children: hint }) : null
1318
+ ] });
1319
+ }
1320
+
1321
+ // src/ui/components/Input.tsx
1322
+ import * as React6 from "react";
1323
+ import { cva as cva2 } from "class-variance-authority";
1324
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1325
+ var controlShellBase = cn(
1326
+ "relative inline-flex items-center gap-2",
1327
+ "rounded-[var(--radius-input)]",
1328
+ "border border-[color:color-mix(in_oklab,var(--border),transparent_55%)]",
1329
+ "text-[color:var(--text-1)]",
1330
+ "transition-[background-color,border-color,box-shadow] duration-[var(--motion-med)] ease",
1331
+ "[--control-focus:var(--focus)]",
1332
+ "focus-within:shadow-[0_0_0_3px_color-mix(in_oklab,var(--control-focus),transparent_60%)]",
1333
+ "focus-within:border-[color:var(--control-focus)]",
1334
+ "data-[invalid]:[--control-focus:var(--danger)]",
1335
+ "data-[invalid]:border-[color:color-mix(in_oklab,var(--danger),transparent_20%)]",
1336
+ "data-[disabled]:bg-[color:var(--disabled-bg)]",
1337
+ "data-[disabled]:text-[color:var(--disabled-fg)]",
1338
+ "data-[disabled]:border-[color:var(--disabled-border)]",
1339
+ "data-[disabled]:cursor-not-allowed"
1340
+ );
1341
+ var controlShellVariants = cva2(controlShellBase, {
1342
+ variants: {
1343
+ variant: {
1344
+ surface: cn(
1345
+ "bg-[color:var(--surface-2)]",
1346
+ "hover:bg-[color:var(--surface-hover)]"
1347
+ ),
1348
+ outline: cn(
1349
+ "bg-transparent",
1350
+ "hover:bg-[color:color-mix(in_oklab,var(--surface-2),transparent_40%)]",
1351
+ "border-[color:color-mix(in_oklab,var(--border),transparent_35%)]"
1352
+ ),
1353
+ ghost: cn(
1354
+ "bg-transparent",
1355
+ "border-[color:transparent]",
1356
+ "hover:bg-[color:color-mix(in_oklab,var(--surface-2),transparent_55%)]",
1357
+ "focus-within:border-[color:color-mix(in_oklab,var(--border),transparent_35%)]"
1358
+ )
1359
+ },
1360
+ size: {
1361
+ sm: "h-8 px-3 text-sm [--input-slot-size:2rem]",
1362
+ md: "h-10 px-3.5 text-sm [--input-slot-size:2.5rem]",
1363
+ lg: "h-11 px-4 text-base [--input-slot-size:2.75rem]"
1364
+ },
1365
+ fullWidth: { true: "w-full", false: "w-auto" }
1366
+ },
1367
+ defaultVariants: { variant: "surface", size: "md", fullWidth: false }
1368
+ });
1369
+ var inputBase = cn(
1370
+ "min-w-0 flex-1 bg-transparent",
1371
+ "outline-none border-0 ring-0",
1372
+ "placeholder:text-[color:color-mix(in_oklab,var(--muted),transparent_20%)]",
1373
+ "disabled:cursor-not-allowed",
1374
+ "read-only:cursor-default"
1375
+ );
1376
+ var slotBase = cn(
1377
+ "inline-flex items-center justify-center",
1378
+ "text-[color:var(--muted)]",
1379
+ "data-[disabled]:text-[color:var(--disabled-fg)]"
1380
+ );
1381
+ var slotReserve = "min-w-[var(--input-slot-size)]";
1382
+ var Input = React6.forwardRef(
1383
+ ({
1384
+ variant,
1385
+ size,
1386
+ fullWidth,
1387
+ invalid,
1388
+ leftSlot,
1389
+ rightSlot,
1390
+ reserveLeftSlot,
1391
+ reserveRightSlot,
1392
+ className,
1393
+ inputClassName,
1394
+ disabled,
1395
+ ...props
1396
+ }, ref) => {
1397
+ const isDisabled = Boolean(disabled);
1398
+ const showLeftSlot = Boolean(leftSlot || reserveLeftSlot);
1399
+ const showRightSlot = Boolean(rightSlot || reserveRightSlot);
1400
+ return /* @__PURE__ */ jsxs4(
1401
+ "div",
1402
+ {
1403
+ className: cn(controlShellVariants({ variant, size, fullWidth }), className),
1404
+ "data-invalid": invalid ? "" : void 0,
1405
+ "data-disabled": isDisabled ? "" : void 0,
1406
+ children: [
1407
+ showLeftSlot ? /* @__PURE__ */ jsx9(
1408
+ "span",
1409
+ {
1410
+ className: cn(slotBase, reserveLeftSlot && slotReserve),
1411
+ "data-disabled": isDisabled ? "" : void 0,
1412
+ children: leftSlot
1413
+ }
1414
+ ) : null,
1415
+ /* @__PURE__ */ jsx9(
1416
+ "input",
1417
+ {
1418
+ ref,
1419
+ disabled: isDisabled,
1420
+ "aria-invalid": invalid || void 0,
1421
+ className: cn(inputBase, inputClassName),
1422
+ ...props
1423
+ }
1424
+ ),
1425
+ showRightSlot ? /* @__PURE__ */ jsx9(
1426
+ "span",
1427
+ {
1428
+ className: cn(slotBase, reserveRightSlot && slotReserve),
1429
+ "data-disabled": isDisabled ? "" : void 0,
1430
+ children: rightSlot
1431
+ }
1432
+ ) : null
1433
+ ]
1434
+ }
1435
+ );
1436
+ }
1437
+ );
1438
+ Input.displayName = "Input";
1439
+
1440
+ // src/ui/components/Textarea.tsx
1441
+ import * as React7 from "react";
1442
+ import { cva as cva3 } from "class-variance-authority";
1443
+ import { jsx as jsx10 } from "react/jsx-runtime";
1444
+ var controlShellBase2 = cn(
1445
+ "relative inline-flex w-full",
1446
+ "rounded-[var(--radius-input)]",
1447
+ "border border-[color:color-mix(in_oklab,var(--border),transparent_55%)]",
1448
+ "text-[color:var(--text-1)]",
1449
+ "transition-[background-color,border-color,box-shadow] duration-[var(--motion-med)] ease",
1450
+ "[--control-focus:var(--focus)]",
1451
+ "focus-within:shadow-[0_0_0_3px_color-mix(in_oklab,var(--control-focus),transparent_60%)]",
1452
+ "focus-within:border-[color:var(--control-focus)]",
1453
+ "data-[invalid]:[--control-focus:var(--danger)]",
1454
+ "data-[invalid]:border-[color:color-mix(in_oklab,var(--danger),transparent_20%)]",
1455
+ "data-[disabled]:bg-[color:var(--disabled-bg)]",
1456
+ "data-[disabled]:text-[color:var(--disabled-fg)]",
1457
+ "data-[disabled]:border-[color:var(--disabled-border)]",
1458
+ "data-[disabled]:cursor-not-allowed"
1459
+ );
1460
+ var textareaShellVariants = cva3(controlShellBase2, {
1461
+ variants: {
1462
+ variant: {
1463
+ surface: cn(
1464
+ "bg-[color:var(--surface-2)]",
1465
+ "hover:bg-[color:var(--surface-hover)]"
1466
+ ),
1467
+ outline: cn(
1468
+ "bg-transparent",
1469
+ "hover:bg-[color:color-mix(in_oklab,var(--surface-2),transparent_40%)]",
1470
+ "border-[color:color-mix(in_oklab,var(--border),transparent_35%)]"
1471
+ ),
1472
+ ghost: cn(
1473
+ "bg-transparent",
1474
+ "border-[color:transparent]",
1475
+ "hover:bg-[color:color-mix(in_oklab,var(--surface-2),transparent_55%)]",
1476
+ "focus-within:border-[color:color-mix(in_oklab,var(--border),transparent_35%)]"
1477
+ )
1478
+ },
1479
+ size: {
1480
+ sm: "p-3 text-sm",
1481
+ md: "p-3.5 text-sm",
1482
+ lg: "p-4 text-base"
1483
+ },
1484
+ fullWidth: { true: "w-full", false: "w-auto" }
1485
+ },
1486
+ defaultVariants: { variant: "surface", size: "md", fullWidth: true }
1487
+ });
1488
+ var textareaBase = cn(
1489
+ "min-w-0 w-full bg-transparent",
1490
+ "outline-none border-0 ring-0",
1491
+ "resize-y",
1492
+ "placeholder:text-[color:color-mix(in_oklab,var(--muted),transparent_20%)]",
1493
+ "disabled:cursor-not-allowed",
1494
+ "read-only:cursor-default"
1495
+ );
1496
+ var Textarea = React7.forwardRef(
1497
+ ({
1498
+ variant,
1499
+ size,
1500
+ fullWidth,
1501
+ invalid,
1502
+ className,
1503
+ textareaClassName,
1504
+ disabled,
1505
+ autoResize,
1506
+ onInput,
1507
+ rows = 4,
1508
+ ...props
1509
+ }, ref) => {
1510
+ const isDisabled = Boolean(disabled);
1511
+ const handleInput = (e) => {
1512
+ if (autoResize) {
1513
+ const el = e.currentTarget;
1514
+ el.style.height = "auto";
1515
+ el.style.height = `${el.scrollHeight}px`;
1516
+ }
1517
+ onInput?.(e);
1518
+ };
1519
+ return /* @__PURE__ */ jsx10(
1520
+ "div",
1521
+ {
1522
+ className: cn(textareaShellVariants({ variant, size, fullWidth }), className),
1523
+ "data-invalid": invalid ? "" : void 0,
1524
+ "data-disabled": isDisabled ? "" : void 0,
1525
+ children: /* @__PURE__ */ jsx10(
1526
+ "textarea",
1527
+ {
1528
+ ref,
1529
+ disabled: isDisabled,
1530
+ "aria-invalid": invalid || void 0,
1531
+ className: cn(textareaBase, textareaClassName),
1532
+ rows,
1533
+ onInput: handleInput,
1534
+ ...props
1535
+ }
1536
+ )
1537
+ }
1538
+ );
1539
+ }
1540
+ );
1541
+ Textarea.displayName = "Textarea";
1273
1542
  export {
1274
1543
  AuthController,
1275
1544
  AuthControllerCtx,
1276
1545
  AuthControllerProvider,
1277
1546
  AuthDispatchCtx,
1278
- AuthFormInputField,
1279
1547
  AuthProvider,
1280
1548
  AuthStateCtx,
1281
1549
  Button,
1282
1550
  DEFAULT_NOTIFICATION_CHANNEL,
1551
+ Field,
1283
1552
  HttpError,
1553
+ Input,
1284
1554
  MockAuthHttpClient,
1285
1555
  MockHttpClient,
1286
1556
  NotificationHost,
1287
1557
  NotifierController,
1288
1558
  RedirectIfAuthed,
1289
1559
  RequireAuth,
1560
+ Textarea,
1290
1561
  authController,
1291
1562
  controllerFactory,
1292
1563
  createAuthController,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexdspace/react",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,8 +52,7 @@
52
52
  "@tailwindcss/vite": "^4.1.18",
53
53
  "@tanstack/react-query": "^5.90.11",
54
54
  "@types/react": "^19.2.7",
55
- "@vitest/browser": "^3.2.4",
56
- "@vitest/coverage-v8": "^3.2.4",
55
+ "@vitest/browser-playwright": "^4.0.16",
57
56
  "autoprefixer": "^10.4.22",
58
57
  "lucide-react": "^0.555.0",
59
58
  "playwright": "^1.57.0",