@saasflare/ui 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +68 -2
  2. package/dist/{button-0Bdl7Nqm.d.ts → button-BA7OcXqy.d.mts} +12 -17
  3. package/dist/{button-Brb4BhPO.d.mts → button-Bfg2Tnvx.d.ts} +12 -17
  4. package/dist/{chunk-D5LKWKG7.js → chunk-2GOPD64T.js} +117 -89
  5. package/dist/{chunk-WPOOC2FX.mjs → chunk-2ONA6OMO.mjs} +33 -44
  6. package/dist/{chunk-RW2S3KNB.mjs → chunk-5C65JNGY.mjs} +7 -6
  7. package/dist/{chunk-DNLCSV5M.js → chunk-7UD3SGPP.js} +32 -43
  8. package/dist/chunk-GI6VN7XU.mjs +2143 -0
  9. package/dist/{chunk-FT66KYRN.js → chunk-ITALEYDI.js} +2 -2
  10. package/dist/{chunk-4BOMMZEY.js → chunk-JC7EIEGI.js} +14 -13
  11. package/dist/chunk-N65HIOBD.js +234 -0
  12. package/dist/{chunk-EJHYM2HP.mjs → chunk-OZAWULTM.mjs} +1 -1
  13. package/dist/chunk-R3AVBLJ3.js +2207 -0
  14. package/dist/{chunk-WRONFPRI.mjs → chunk-RMQBB72G.mjs} +118 -91
  15. package/dist/chunk-XNDTCYSO.mjs +211 -0
  16. package/dist/{dialog-BmY55WSX.d.ts → dialog-CZRwrqDa.d.ts} +2 -2
  17. package/dist/{dialog-CcaHMAsS.d.mts → dialog-Cr0becOL.d.mts} +2 -2
  18. package/dist/entries/calendar.d.mts +3 -3
  19. package/dist/entries/calendar.d.ts +3 -3
  20. package/dist/entries/calendar.js +13 -214
  21. package/dist/entries/calendar.mjs +5 -196
  22. package/dist/entries/carousel.d.mts +3 -3
  23. package/dist/entries/carousel.d.ts +3 -3
  24. package/dist/entries/carousel.js +17 -14
  25. package/dist/entries/carousel.mjs +10 -7
  26. package/dist/entries/chart.d.mts +1 -1
  27. package/dist/entries/chart.d.ts +1 -1
  28. package/dist/entries/chart.js +11 -11
  29. package/dist/entries/chart.mjs +1 -1
  30. package/dist/entries/command.d.mts +3 -3
  31. package/dist/entries/command.d.ts +3 -3
  32. package/dist/entries/command.js +21 -19
  33. package/dist/entries/command.mjs +8 -6
  34. package/dist/entries/drawer.d.mts +1 -1
  35. package/dist/entries/drawer.d.ts +1 -1
  36. package/dist/entries/drawer.js +9 -9
  37. package/dist/entries/drawer.mjs +2 -2
  38. package/dist/entries/input-otp.d.mts +2 -2
  39. package/dist/entries/input-otp.d.ts +2 -2
  40. package/dist/entries/input-otp.js +10 -8
  41. package/dist/entries/input-otp.mjs +6 -4
  42. package/dist/entries/resizable.d.mts +3 -2
  43. package/dist/entries/resizable.d.ts +3 -2
  44. package/dist/entries/resizable.js +8 -6
  45. package/dist/entries/resizable.mjs +6 -4
  46. package/dist/index.d.mts +974 -31
  47. package/dist/index.d.ts +974 -31
  48. package/dist/index.js +2994 -556
  49. package/dist/index.mjs +2488 -201
  50. package/dist/{use-saasflare-props-NrM2Glmp.d.ts → use-saasflare-props-BrjMhU0U.d.mts} +53 -4
  51. package/dist/{use-saasflare-props-NrM2Glmp.d.mts → use-saasflare-props-BrjMhU0U.d.ts} +53 -4
  52. package/package.json +4 -3
  53. package/styles/aurora.css +47 -0
  54. package/styles/palettes.css +487 -3
  55. package/styles/surfaces.css +89 -10
  56. package/styles/theme.css +41 -19
package/README.md CHANGED
@@ -29,9 +29,69 @@ yarn add @saasflare/ui
29
29
  - **Tailwind v4 native** — tokens declared via `@theme`, no `tailwind.config.ts`
30
30
  extension required.
31
31
  - **Theming via CSS variables** — switch palettes by setting `data-palette="…"`
32
- on `<html>`. 17 presets shipped, custom palettes are one CSS block.
32
+ on `<html>`. 20 presets shipped, custom palettes are one CSS block.
33
33
  - **Light / dark out of the box** — driven by `next-themes`.
34
34
  - **TypeScript strict** — full types, no `any`.
35
+ - **AI-codegen friendly** — shadcn-compatible registry (`npx shadcn add https://ui.saasflare.io/r/<name>.json`) lets AI tools (v0, Lovable, Bolt, Cursor, Claude) pull component source straight into your project. `llms.txt` and `llms-full.txt` published for LLM discovery.
36
+
37
+ ---
38
+
39
+ ## For AI codegen tools
40
+
41
+ This library publishes three artifacts AI generators can consume:
42
+
43
+ | URL | Purpose |
44
+ |---|---|
45
+ | [`https://ui.saasflare.io/llms.txt`](https://ui.saasflare.io/llms.txt) | Concise index (per [llmstxt.org](https://llmstxt.org)) |
46
+ | [`https://ui.saasflare.io/llms-full.txt`](https://ui.saasflare.io/llms-full.txt) | Full component reference — props, types, examples |
47
+ | [`https://ui.saasflare.io/registry.json`](https://ui.saasflare.io/registry.json) | shadcn-compatible registry index — list of every installable component |
48
+ | `https://ui.saasflare.io/api/mcp` | Remote MCP server — let Claude / Cursor / Claude Code query the catalog live (see "MCP server" below) |
49
+
50
+ ### MCP server
51
+
52
+ Add the endpoint URL to any MCP-aware client and your agent can call `list_components`, `get_component`, `search_components`, `recommend_for_use_case`, `get_install_command`, `list_palettes`, `get_palette`, and `list_hooks` directly.
53
+
54
+ **Claude Code**
55
+ ```bash
56
+ claude mcp add --transport http saasflare-ui https://ui.saasflare.io/api/mcp
57
+ ```
58
+
59
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`)
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "saasflare-ui": { "url": "https://ui.saasflare.io/api/mcp" }
64
+ }
65
+ }
66
+ ```
67
+
68
+ **Cursor** (`.cursor/mcp.json`)
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "saasflare-ui": { "url": "https://ui.saasflare.io/api/mcp" }
73
+ }
74
+ }
75
+ ```
76
+
77
+ To pull a component's source into a project (the AI-friendly path):
78
+
79
+ ```bash
80
+ pnpm add @saasflare/ui # runtime, providers, motion bundle
81
+ npx shadcn add https://ui.saasflare.io/r/feature-card.json # source of one component
82
+ npx shadcn add https://ui.saasflare.io/r/pricing-card.json # …and another
83
+ ```
84
+
85
+ The imported files land in your project's `components/ui/` (or wherever your `components.json` aliases point) with all internal imports already rewritten to `@saasflare/ui`. No further wiring needed.
86
+
87
+ To regenerate locally:
88
+
89
+ ```bash
90
+ pnpm --filter @saasflare/ui build:registry # writes apps/ui/public/r/*.json
91
+ pnpm --filter @saasflare/ui build:llms # writes apps/ui/public/llms{,-full}.txt
92
+ ```
93
+
94
+ To add a new component to the registry, append an entry to `packages/ui/registry.json` and rebuild.
35
95
 
36
96
  ---
37
97
 
@@ -206,7 +266,13 @@ import { ResizablePanel, ResizablePanelGroup } from "@saasflare/ui/resizable";
206
266
 
207
267
  Built-in presets: `saasflare` (default), `ocean`, `ink`, `aurora`, `indigo`,
208
268
  `emerald`, `violet`, `coral`, `stone`, `jade`, `cobalt`, `amber`, `fuchsia`,
209
- `honey`, `teal`, `iris`, `ruby`.
269
+ `honey`, `teal`, `iris`, `ruby`, `colorful`, `craivo`.
270
+
271
+ `colorful` is special: switching to it auto-applies a soft pastel gradient
272
+ on hover (peach → blush → lavender → cyan) wrapped in a matching
273
+ color-aura glow, to all outline buttons, feature/testimonial/team/spotlight
274
+ cards, and feature-card icon chips on the page. Cards get a gradient
275
+ border ring; buttons fill with the pastel sweep. No component-side opt-in.
210
276
 
211
277
  ### Define a custom palette
212
278
 
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as class_variance_authority_types from 'class-variance-authority/types';
3
3
  import * as React from 'react';
4
4
  import { VariantProps } from 'class-variance-authority';
5
- import { c as SaasflareComponentProps } from './use-saasflare-props-NrM2Glmp.js';
5
+ import { d as SaasflareComponentProps } from './use-saasflare-props-BrjMhU0U.mjs';
6
6
 
7
7
  declare const INTENTS: readonly ["primary", "neutral", "success", "warning", "danger", "info"];
8
8
  type Intent = (typeof INTENTS)[number];
@@ -10,12 +10,12 @@ type Intent = (typeof INTENTS)[number];
10
10
  * Button variant definitions using the 3-axis system.
11
11
  *
12
12
  * Axes:
13
- * variant — visual treatment: solid, soft, outline, ghost, link, glass, shadow
13
+ * variant — visual treatment: solid, soft, outline, ghost, link, glass, clay, shadow
14
14
  * intent — color intent via data-intent attribute + CSS tokens
15
15
  * size — dimensional: xs, sm, md, lg, xl, icon, icon-xs, icon-sm, icon-lg
16
16
  */
17
17
  declare const buttonVariants: (props?: ({
18
- variant?: "outline" | "link" | "glass" | "soft" | "solid" | "ghost" | "shadow" | null | undefined;
18
+ variant?: "link" | "outline" | "glass" | "clay" | "soft" | "solid" | "ghost" | "shadow" | null | undefined;
19
19
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
20
20
  } & class_variance_authority_types.ClassProp) | undefined) => string;
21
21
  /** Framer-motion event overrides that conflict with React HTML events */
@@ -31,30 +31,29 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
31
31
  asChild?: boolean;
32
32
  /** Semantic color intent */
33
33
  intent?: Intent;
34
- /** Show loading spinner (replaces left icon, keeps text visible) */
35
- loading?: boolean;
36
34
  /** Stretch to full width of container */
37
35
  fullWidth?: boolean;
38
36
  }
39
37
  /**
40
- * Primary interactive button with motion, loading, and intent support.
38
+ * Primary interactive button with motion and intent support.
41
39
  *
42
40
  * Resolves `surface` and `animated` via {@link useSaasflareProps} with the
43
41
  * precedence: component prop > <SaasflareProvider> context > hardcoded default.
44
42
  *
45
- * When no explicit `variant` is set and the resolved surface is `"glass"`, the
46
- * button promotes itself to `variant="glass"`. An explicit `variant` prop always
47
- * wins over the surface-based promotion.
43
+ * When no explicit `variant` is set and the resolved surface is `"glass"` or
44
+ * `"clay"`, the button promotes itself to that matching variant. An explicit
45
+ * `variant` prop always wins over the surface-based promotion.
46
+ *
47
+ * For loading / pending states use {@link StatefulButton}.
48
48
  *
49
49
  * @component
50
50
  * @layer ui
51
51
  *
52
- * @param {string} variant - Visual treatment: "solid" | "soft" | "outline" | "ghost" | "link" | "glass" | "shadow"
52
+ * @param {string} variant - Visual treatment: "solid" | "soft" | "outline" | "ghost" | "link" | "glass" | "clay" | "shadow"
53
53
  * @param {string} intent - Color intent: "primary" | "neutral" | "success" | "warning" | "danger" | "info"
54
54
  * @param {string} size - Button size: "xs" | "sm" | "md" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg"
55
- * @param {string} surface - Surface style override: "flat" | "glass" (inherits from provider when omitted)
55
+ * @param {string} surface - Surface style override: "flat" | "glass" | "clay" (inherits from provider when omitted)
56
56
  * @param {boolean} animated - Gate motion effects (inherits from provider when omitted)
57
- * @param {boolean} loading - Shows spinner, sets aria-busy, preserves width
58
57
  * @param {boolean} fullWidth - Stretches to container width
59
58
  * @param {boolean} asChild - Render as child element (Slot pattern)
60
59
  *
@@ -71,10 +70,6 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
71
70
  * <SaasflareProvider surface="glass"><Button>Frosted</Button></SaasflareProvider>
72
71
  *
73
72
  * @example
74
- * // Loading state
75
- * <Button loading>Processing...</Button>
76
- *
77
- * @example
78
73
  * // Icon button
79
74
  * <Button variant="ghost" size="icon"><SettingsIcon /></Button>
80
75
  *
@@ -82,6 +77,6 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
82
77
  * // Legacy API (deprecated but supported)
83
78
  * <Button variant="destructive">Delete</Button>
84
79
  */
85
- declare function Button({ className, variant: variantProp, size, intent: intentProp, asChild, loading, fullWidth, surface, radius, animated, disabled, children, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
80
+ declare function Button({ className, variant: variantProp, size, intent: intentProp, asChild, fullWidth, surface, iconWeight, radius, animated, disabled, children, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
86
81
 
87
82
  export { Button as B, type Intent as I, type ButtonProps as a, buttonVariants as b };
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as class_variance_authority_types from 'class-variance-authority/types';
3
3
  import * as React from 'react';
4
4
  import { VariantProps } from 'class-variance-authority';
5
- import { c as SaasflareComponentProps } from './use-saasflare-props-NrM2Glmp.mjs';
5
+ import { d as SaasflareComponentProps } from './use-saasflare-props-BrjMhU0U.js';
6
6
 
7
7
  declare const INTENTS: readonly ["primary", "neutral", "success", "warning", "danger", "info"];
8
8
  type Intent = (typeof INTENTS)[number];
@@ -10,12 +10,12 @@ type Intent = (typeof INTENTS)[number];
10
10
  * Button variant definitions using the 3-axis system.
11
11
  *
12
12
  * Axes:
13
- * variant — visual treatment: solid, soft, outline, ghost, link, glass, shadow
13
+ * variant — visual treatment: solid, soft, outline, ghost, link, glass, clay, shadow
14
14
  * intent — color intent via data-intent attribute + CSS tokens
15
15
  * size — dimensional: xs, sm, md, lg, xl, icon, icon-xs, icon-sm, icon-lg
16
16
  */
17
17
  declare const buttonVariants: (props?: ({
18
- variant?: "outline" | "link" | "glass" | "soft" | "solid" | "ghost" | "shadow" | null | undefined;
18
+ variant?: "link" | "outline" | "glass" | "clay" | "soft" | "solid" | "ghost" | "shadow" | null | undefined;
19
19
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
20
20
  } & class_variance_authority_types.ClassProp) | undefined) => string;
21
21
  /** Framer-motion event overrides that conflict with React HTML events */
@@ -31,30 +31,29 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
31
31
  asChild?: boolean;
32
32
  /** Semantic color intent */
33
33
  intent?: Intent;
34
- /** Show loading spinner (replaces left icon, keeps text visible) */
35
- loading?: boolean;
36
34
  /** Stretch to full width of container */
37
35
  fullWidth?: boolean;
38
36
  }
39
37
  /**
40
- * Primary interactive button with motion, loading, and intent support.
38
+ * Primary interactive button with motion and intent support.
41
39
  *
42
40
  * Resolves `surface` and `animated` via {@link useSaasflareProps} with the
43
41
  * precedence: component prop > <SaasflareProvider> context > hardcoded default.
44
42
  *
45
- * When no explicit `variant` is set and the resolved surface is `"glass"`, the
46
- * button promotes itself to `variant="glass"`. An explicit `variant` prop always
47
- * wins over the surface-based promotion.
43
+ * When no explicit `variant` is set and the resolved surface is `"glass"` or
44
+ * `"clay"`, the button promotes itself to that matching variant. An explicit
45
+ * `variant` prop always wins over the surface-based promotion.
46
+ *
47
+ * For loading / pending states use {@link StatefulButton}.
48
48
  *
49
49
  * @component
50
50
  * @layer ui
51
51
  *
52
- * @param {string} variant - Visual treatment: "solid" | "soft" | "outline" | "ghost" | "link" | "glass" | "shadow"
52
+ * @param {string} variant - Visual treatment: "solid" | "soft" | "outline" | "ghost" | "link" | "glass" | "clay" | "shadow"
53
53
  * @param {string} intent - Color intent: "primary" | "neutral" | "success" | "warning" | "danger" | "info"
54
54
  * @param {string} size - Button size: "xs" | "sm" | "md" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg"
55
- * @param {string} surface - Surface style override: "flat" | "glass" (inherits from provider when omitted)
55
+ * @param {string} surface - Surface style override: "flat" | "glass" | "clay" (inherits from provider when omitted)
56
56
  * @param {boolean} animated - Gate motion effects (inherits from provider when omitted)
57
- * @param {boolean} loading - Shows spinner, sets aria-busy, preserves width
58
57
  * @param {boolean} fullWidth - Stretches to container width
59
58
  * @param {boolean} asChild - Render as child element (Slot pattern)
60
59
  *
@@ -71,10 +70,6 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
71
70
  * <SaasflareProvider surface="glass"><Button>Frosted</Button></SaasflareProvider>
72
71
  *
73
72
  * @example
74
- * // Loading state
75
- * <Button loading>Processing...</Button>
76
- *
77
- * @example
78
73
  * // Icon button
79
74
  * <Button variant="ghost" size="icon"><SettingsIcon /></Button>
80
75
  *
@@ -82,6 +77,6 @@ interface ButtonProps extends Omit<React.ComponentProps<"button">, MotionConflic
82
77
  * // Legacy API (deprecated but supported)
83
78
  * <Button variant="destructive">Delete</Button>
84
79
  */
85
- declare function Button({ className, variant: variantProp, size, intent: intentProp, asChild, loading, fullWidth, surface, radius, animated, disabled, children, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
80
+ declare function Button({ className, variant: variantProp, size, intent: intentProp, asChild, fullWidth, surface, iconWeight, radius, animated, disabled, children, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
86
81
 
87
82
  export { Button as B, type Intent as I, type ButtonProps as a, buttonVariants as b };
@@ -89,6 +89,99 @@ var getServerSnapshot = () => false;
89
89
  function useReducedMotion() {
90
90
  return React2__namespace.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
91
91
  }
92
+ var SYNC_PREFIX = "sf-ls:";
93
+ function useLocalStorage(key, initialValue, options) {
94
+ const initialRef = React2.useRef(initialValue);
95
+ const optionsRef = React2.useRef(options);
96
+ optionsRef.current = options;
97
+ const serialize = React2.useCallback(
98
+ (value) => (optionsRef.current?.serializer ?? JSON.stringify)(value),
99
+ []
100
+ );
101
+ const deserialize = React2.useCallback(
102
+ (raw) => (optionsRef.current?.deserializer ?? JSON.parse)(raw),
103
+ []
104
+ );
105
+ const handleError = React2.useCallback(
106
+ (error, operation) => {
107
+ if (optionsRef.current?.onError) {
108
+ optionsRef.current.onError(error, operation);
109
+ } else {
110
+ console.warn(`useLocalStorage: ${operation} failed for "${key}"`, error);
111
+ }
112
+ },
113
+ [key]
114
+ );
115
+ const readValue = React2.useCallback(() => {
116
+ if (typeof window === "undefined") return initialRef.current;
117
+ try {
118
+ const item = window.localStorage.getItem(key);
119
+ return item !== null ? deserialize(item) : initialRef.current;
120
+ } catch (error) {
121
+ handleError(error, "read");
122
+ return initialRef.current;
123
+ }
124
+ }, [key, deserialize, handleError]);
125
+ const [storedValue, setStoredValue] = React2.useState(initialValue);
126
+ const setValue = React2.useCallback(
127
+ (value) => {
128
+ let prev;
129
+ if (typeof window === "undefined") {
130
+ prev = initialRef.current;
131
+ } else {
132
+ try {
133
+ const raw = window.localStorage.getItem(key);
134
+ prev = raw !== null ? deserialize(raw) : initialRef.current;
135
+ } catch {
136
+ prev = initialRef.current;
137
+ }
138
+ }
139
+ const next = typeof value === "function" ? value(prev) : value;
140
+ setStoredValue(next);
141
+ if (typeof window === "undefined") return;
142
+ try {
143
+ window.localStorage.setItem(key, serialize(next));
144
+ } catch (err) {
145
+ handleError(err, "write");
146
+ return;
147
+ }
148
+ setTimeout(() => {
149
+ window.dispatchEvent(new CustomEvent(`${SYNC_PREFIX}${key}`));
150
+ }, 0);
151
+ },
152
+ [key, serialize, deserialize, handleError]
153
+ );
154
+ const removeValue = React2.useCallback(() => {
155
+ setStoredValue(initialRef.current);
156
+ if (typeof window === "undefined") return;
157
+ try {
158
+ window.localStorage.removeItem(key);
159
+ } catch (err) {
160
+ handleError(err, "remove");
161
+ return;
162
+ }
163
+ setTimeout(() => {
164
+ window.dispatchEvent(new CustomEvent(`${SYNC_PREFIX}${key}`));
165
+ }, 0);
166
+ }, [key, handleError]);
167
+ React2.useEffect(() => {
168
+ const onStorage = (e) => {
169
+ if (e.key === key) setStoredValue(readValue());
170
+ };
171
+ const onSync = () => setStoredValue(readValue());
172
+ const syncEvent = `${SYNC_PREFIX}${key}`;
173
+ window.addEventListener("storage", onStorage);
174
+ window.addEventListener(syncEvent, onSync);
175
+ return () => {
176
+ window.removeEventListener("storage", onStorage);
177
+ window.removeEventListener(syncEvent, onSync);
178
+ };
179
+ }, [key, readValue]);
180
+ React2.useEffect(() => {
181
+ setStoredValue(readValue());
182
+ }, [readValue]);
183
+ return [storedValue, setValue, removeValue];
184
+ }
92
185
  var AnimationContext = React2.createContext(
93
186
  void 0
94
187
  );
@@ -151,95 +244,18 @@ function SaasflareScript({ nonce, palette, surface, radius, animated, storageKey
151
244
  }
152
245
  );
153
246
  }
154
- var SYNC_PREFIX = "sf-ls:";
155
- function useLocalStorage(key, initialValue, options) {
156
- const initialRef = React2.useRef(initialValue);
157
- const optionsRef = React2.useRef(options);
158
- optionsRef.current = options;
159
- const serialize = React2.useCallback(
160
- (value) => (optionsRef.current?.serializer ?? JSON.stringify)(value),
161
- []
162
- );
163
- const deserialize = React2.useCallback(
164
- (raw) => (optionsRef.current?.deserializer ?? JSON.parse)(raw),
165
- []
166
- );
167
- const handleError = React2.useCallback(
168
- (error, operation) => {
169
- if (optionsRef.current?.onError) {
170
- optionsRef.current.onError(error, operation);
171
- } else {
172
- console.warn(`useLocalStorage: ${operation} failed for "${key}"`, error);
173
- }
174
- },
175
- [key]
176
- );
177
- const readValue = React2.useCallback(() => {
178
- if (typeof window === "undefined") return initialRef.current;
179
- try {
180
- const item = window.localStorage.getItem(key);
181
- return item !== null ? deserialize(item) : initialRef.current;
182
- } catch (error) {
183
- handleError(error, "read");
184
- return initialRef.current;
185
- }
186
- }, [key, deserialize, handleError]);
187
- const [storedValue, setStoredValue] = React2.useState(() => readValue());
188
- const setValue = React2.useCallback(
189
- (value) => {
190
- try {
191
- setStoredValue((prev) => {
192
- const next = typeof value === "function" ? value(prev) : value;
193
- if (typeof window !== "undefined") {
194
- window.localStorage.setItem(key, serialize(next));
195
- window.dispatchEvent(new CustomEvent(`${SYNC_PREFIX}${key}`));
196
- }
197
- return next;
198
- });
199
- } catch (error) {
200
- handleError(error, "write");
201
- }
202
- },
203
- [key, serialize, handleError]
204
- );
205
- const removeValue = React2.useCallback(() => {
206
- try {
207
- if (typeof window !== "undefined") {
208
- window.localStorage.removeItem(key);
209
- window.dispatchEvent(new CustomEvent(`${SYNC_PREFIX}${key}`));
210
- }
211
- setStoredValue(initialRef.current);
212
- } catch (error) {
213
- handleError(error, "remove");
214
- }
215
- }, [key, handleError]);
216
- React2.useEffect(() => {
217
- const onStorage = (e) => {
218
- if (e.key === key) setStoredValue(readValue());
219
- };
220
- const onSync = () => setStoredValue(readValue());
221
- const syncEvent = `${SYNC_PREFIX}${key}`;
222
- window.addEventListener("storage", onStorage);
223
- window.addEventListener(syncEvent, onSync);
224
- return () => {
225
- window.removeEventListener("storage", onStorage);
226
- window.removeEventListener(syncEvent, onSync);
227
- };
228
- }, [key, readValue]);
229
- React2.useEffect(() => {
230
- setStoredValue(readValue());
231
- }, [readValue]);
232
- return [storedValue, setValue, removeValue];
233
- }
234
247
  var DEFAULT_CONTEXT = {
235
248
  palette: null,
236
249
  surface: "flat",
237
- radius: "rounded",
250
+ radius: "soft",
251
+ iconWeight: "regular",
238
252
  setPalette: () => {
239
253
  },
240
254
  setSurface: () => {
241
255
  },
242
256
  setRadius: () => {
257
+ },
258
+ setIconWeight: () => {
243
259
  }
244
260
  };
245
261
  var SaasflareThemeContext = React2.createContext(DEFAULT_CONTEXT);
@@ -299,7 +315,8 @@ var PERSISTED_DEFAULTS = {
299
315
  palette: null,
300
316
  surface: null,
301
317
  radius: null,
302
- animated: null
318
+ animated: null,
319
+ iconWeight: null
303
320
  };
304
321
  function SaasflareProvider({
305
322
  children,
@@ -307,8 +324,9 @@ function SaasflareProvider({
307
324
  palette,
308
325
  surface,
309
326
  radius,
310
- animated = true,
311
- smoothScrolling = false,
327
+ iconWeight,
328
+ animated,
329
+ smoothScrolling = true,
312
330
  disableScript = false,
313
331
  scriptNonce,
314
332
  storageKey = UI_PREFS_STORAGE_KEY,
@@ -325,7 +343,9 @@ function SaasflareProvider({
325
343
  );
326
344
  const currentPalette = isCustomPalette ? palette.name : palette ?? persisted.palette;
327
345
  const currentStyle = surface ?? persisted.surface ?? "flat";
328
- const currentRadius = radius ?? persisted.radius ?? "rounded";
346
+ const currentRadius = radius ?? persisted.radius ?? "soft";
347
+ const currentIconWeight = iconWeight ?? persisted.iconWeight ?? "regular";
348
+ const currentAnimated = animated ?? persisted.animated ?? true;
329
349
  const setPalette = React2.useCallback(
330
350
  (id) => setPersisted((prev) => ({ ...prev, palette: id })),
331
351
  [setPersisted]
@@ -338,8 +358,12 @@ function SaasflareProvider({
338
358
  (r) => setPersisted((prev) => ({ ...prev, radius: r })),
339
359
  [setPersisted]
340
360
  );
361
+ const setIconWeight = React2.useCallback(
362
+ (w) => setPersisted((prev) => ({ ...prev, iconWeight: w })),
363
+ [setPersisted]
364
+ );
341
365
  const prefersReduced = useReducedMotion();
342
- const effectiveAnimated = animated && !prefersReduced;
366
+ const effectiveAnimated = currentAnimated && !prefersReduced;
343
367
  React2.useEffect(() => {
344
368
  const root = document.documentElement;
345
369
  if (currentPalette) {
@@ -356,7 +380,7 @@ function SaasflareProvider({
356
380
  {
357
381
  attribute: "class",
358
382
  defaultTheme: theme,
359
- enableSystem: theme === "system",
383
+ enableSystem: true,
360
384
  storageKey: themeStorageKey,
361
385
  disableTransitionOnChange: true,
362
386
  children: [
@@ -374,9 +398,11 @@ function SaasflareProvider({
374
398
  palette: currentPalette,
375
399
  surface: currentStyle,
376
400
  radius: currentRadius,
401
+ iconWeight: currentIconWeight,
377
402
  setPalette,
378
403
  setSurface,
379
- setRadius
404
+ setRadius,
405
+ setIconWeight
380
406
  },
381
407
  children: /* @__PURE__ */ jsxRuntime.jsxs(AnimationContext.Provider, { value: { animated: effectiveAnimated }, children: [
382
408
  isCustomPalette && /* @__PURE__ */ jsxRuntime.jsx(CustomPaletteInjector, { palette }),
@@ -443,7 +469,8 @@ function useSaasflareProps(props = {}) {
443
469
  surface: props.surface ?? ctx.surface,
444
470
  radius: props.radius ?? ctx.radius,
445
471
  animated: props.animated ?? anim?.animated ?? true,
446
- palette: ctx.palette
472
+ palette: ctx.palette,
473
+ iconWeight: props.iconWeight ?? ctx.iconWeight
447
474
  };
448
475
  }
449
476
 
@@ -453,6 +480,7 @@ exports.SaasflareShell = SaasflareShell;
453
480
  exports.SmoothScrollProvider = SmoothScrollProvider;
454
481
  exports.cn = cn;
455
482
  exports.useAnimation = useAnimation;
483
+ exports.useLocalStorage = useLocalStorage;
456
484
  exports.useReducedMotion = useReducedMotion;
457
485
  exports.useSaasflareProps = useSaasflareProps;
458
486
  exports.useSaasflareTheme = useSaasflareTheme;
@@ -1,11 +1,10 @@
1
1
  "use client";
2
- import { useSaasflareMotion, spring } from './chunk-EJHYM2HP.mjs';
3
- import { useSaasflareProps, cn } from './chunk-WRONFPRI.mjs';
4
- import { Loader2Icon } from 'lucide-react';
2
+ import { useSaasflareMotion, spring } from './chunk-OZAWULTM.mjs';
3
+ import { useSaasflareProps, cn } from './chunk-RMQBB72G.mjs';
5
4
  import { m } from 'motion/react';
6
5
  import { cva } from 'class-variance-authority';
7
6
  import * as Slot from '@radix-ui/react-slot';
8
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { jsx } from 'react/jsx-runtime';
9
8
 
10
9
  var MotionSlot = m.create(Slot.Root);
11
10
  var LEGACY_VARIANT_MAP = {
@@ -19,11 +18,12 @@ var buttonVariants = cva(
19
18
  variants: {
20
19
  variant: {
21
20
  solid: "bg-[var(--intent)] text-[var(--intent-fg)] shadow-xs hover:brightness-110 dark:hover:brightness-125",
22
- soft: "bg-[var(--intent)]/15 text-[var(--intent)] hover:bg-[var(--intent)]/25 dark:bg-[var(--intent)]/20 dark:hover:bg-[var(--intent)]/30",
23
- outline: "border border-[var(--intent)]/30 text-[var(--intent)] shadow-xs hover:bg-[var(--intent)]/10 dark:border-[var(--intent)]/40 dark:hover:bg-[var(--intent)]/15",
24
- ghost: "text-[var(--intent)] hover:bg-[var(--intent)]/10 dark:hover:bg-[var(--intent)]/15",
25
- link: "text-[var(--intent)] underline-offset-4 hover:underline",
26
- glass: "bg-[var(--glass-bg)] text-[var(--intent)] border border-[var(--glass-border)] backdrop-blur-lg shadow-[var(--glass-shadow)] hover:bg-[var(--glass-bg-hover)] hover:border-[var(--glass-border-hover)]",
21
+ soft: "bg-[var(--intent)]/15 text-[var(--intent-text)] hover:bg-[var(--intent)]/25 dark:bg-[var(--intent)]/20 dark:hover:bg-[var(--intent)]/30",
22
+ outline: "border border-[var(--intent-text)]/30 text-[var(--intent-text)] shadow-xs hover:bg-[var(--intent-text)]/10 dark:border-[var(--intent-text)]/40 dark:hover:bg-[var(--intent-text)]/15",
23
+ ghost: "text-[var(--intent-text)] hover:bg-[var(--intent-text)]/10 dark:hover:bg-[var(--intent-text)]/15",
24
+ link: "text-[var(--intent-text)] underline-offset-4 hover:underline",
25
+ glass: "bg-[var(--surface-bg)] text-[var(--intent-text)] border border-[var(--surface-border)] [backdrop-filter:var(--surface-backdrop)] [-webkit-backdrop-filter:var(--surface-backdrop)] shadow-[var(--surface-shadow)] hover:brightness-110 dark:hover:brightness-125",
26
+ clay: "bg-[var(--intent)] text-[var(--intent-fg)] shadow-[var(--surface-shadow)] hover:brightness-110 active:translate-y-px dark:hover:brightness-125",
27
27
  shadow: "bg-[var(--intent)] text-[var(--intent-fg)] shadow-[var(--btn-shadow)] hover:shadow-[var(--btn-shadow-hover)] hover:brightness-110 dark:hover:brightness-125"
28
28
  },
29
29
  size: {
@@ -50,18 +50,18 @@ function Button({
50
50
  size = "md",
51
51
  intent: intentProp = "primary",
52
52
  asChild = false,
53
- loading = false,
54
53
  fullWidth = false,
55
54
  surface,
55
+ iconWeight,
56
56
  radius,
57
57
  animated,
58
58
  disabled,
59
59
  children,
60
60
  ...props
61
61
  }) {
62
- const sf = useSaasflareProps({ surface, radius, animated });
63
- const motion = useSaasflareMotion(sf.animated, spring, disabled ?? false, loading);
64
- const effectiveVariant = variantProp ?? (sf.surface === "glass" ? "glass" : "solid");
62
+ const sf = useSaasflareProps({ surface, radius, animated, iconWeight });
63
+ const motion = useSaasflareMotion(sf.animated, spring, disabled ?? false);
64
+ const effectiveVariant = variantProp ?? (sf.surface === "glass" ? "glass" : sf.surface === "clay" ? "clay" : "solid");
65
65
  let resolvedVariant = effectiveVariant;
66
66
  let resolvedIntent = intentProp;
67
67
  const legacy = LEGACY_VARIANT_MAP[effectiveVariant];
@@ -71,56 +71,45 @@ function Button({
71
71
  resolvedIntent = legacy.intent;
72
72
  }
73
73
  }
74
+ const dataAttrs = {
75
+ "data-slot": "button",
76
+ "data-variant": resolvedVariant,
77
+ "data-intent": resolvedIntent,
78
+ "data-size": size,
79
+ "data-surface": sf.surface,
80
+ "data-radius": sf.radius,
81
+ "data-animated": String(sf.animated)
82
+ };
83
+ const classes = cn(
84
+ buttonVariants({ variant: resolvedVariant, size }),
85
+ fullWidth && "w-full",
86
+ className
87
+ );
74
88
  if (asChild) {
75
89
  return /* @__PURE__ */ jsx(
76
90
  MotionSlot,
77
91
  {
78
92
  ...props,
79
- "data-slot": "button",
80
- "data-variant": resolvedVariant,
81
- "data-intent": resolvedIntent,
82
- "data-size": size,
83
- "data-surface": sf.surface,
84
- "data-radius": sf.radius,
85
- "data-animated": String(sf.animated),
93
+ ...dataAttrs,
86
94
  whileHover: motion.disabled ? void 0 : { scale: 1.02 },
87
95
  whileTap: motion.disabled ? void 0 : { scale: 0.97 },
88
96
  transition: motion.transition,
89
- className: cn(
90
- buttonVariants({ variant: resolvedVariant, size }),
91
- fullWidth && "w-full",
92
- className
93
- ),
97
+ className: classes,
94
98
  children
95
99
  }
96
100
  );
97
101
  }
98
- return /* @__PURE__ */ jsxs(
102
+ return /* @__PURE__ */ jsx(
99
103
  m.button,
100
104
  {
101
- "data-slot": "button",
102
- "data-variant": resolvedVariant,
103
- "data-intent": resolvedIntent,
104
- "data-size": size,
105
- "data-surface": sf.surface,
106
- "data-radius": sf.radius,
107
- "data-animated": String(sf.animated),
105
+ ...dataAttrs,
108
106
  whileHover: motion.disabled ? void 0 : { scale: 1.02 },
109
107
  whileTap: motion.disabled ? void 0 : { scale: 0.97 },
110
108
  transition: motion.transition,
111
- className: cn(
112
- buttonVariants({ variant: resolvedVariant, size }),
113
- fullWidth && "w-full",
114
- className
115
- ),
109
+ className: classes,
116
110
  disabled,
117
- "aria-busy": loading || void 0,
118
- "aria-disabled": disabled || void 0,
119
111
  ...props,
120
- children: [
121
- loading && /* @__PURE__ */ jsx(Loader2Icon, { className: "animate-spin", "aria-hidden": "true" }),
122
- children
123
- ]
112
+ children
124
113
  }
125
114
  );
126
115
  }