@saasflare/ui 3.1.2 → 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.
- package/README.md +68 -2
- package/dist/{button-0Bdl7Nqm.d.ts → button-BA7OcXqy.d.mts} +12 -17
- package/dist/{button-Brb4BhPO.d.mts → button-Bfg2Tnvx.d.ts} +12 -17
- package/dist/{chunk-D5LKWKG7.js → chunk-2GOPD64T.js} +117 -89
- package/dist/{chunk-56PMDC5F.mjs → chunk-2ONA6OMO.mjs} +29 -40
- package/dist/{chunk-RW2S3KNB.mjs → chunk-5C65JNGY.mjs} +7 -6
- package/dist/{chunk-NPNSPYTX.js → chunk-7UD3SGPP.js} +28 -39
- package/dist/chunk-GI6VN7XU.mjs +2143 -0
- package/dist/{chunk-FT66KYRN.js → chunk-ITALEYDI.js} +2 -2
- package/dist/{chunk-4BOMMZEY.js → chunk-JC7EIEGI.js} +14 -13
- package/dist/chunk-N65HIOBD.js +234 -0
- package/dist/{chunk-EJHYM2HP.mjs → chunk-OZAWULTM.mjs} +1 -1
- package/dist/chunk-R3AVBLJ3.js +2207 -0
- package/dist/{chunk-WRONFPRI.mjs → chunk-RMQBB72G.mjs} +118 -91
- package/dist/chunk-XNDTCYSO.mjs +211 -0
- package/dist/{dialog-BmY55WSX.d.ts → dialog-CZRwrqDa.d.ts} +2 -2
- package/dist/{dialog-CcaHMAsS.d.mts → dialog-Cr0becOL.d.mts} +2 -2
- package/dist/entries/calendar.d.mts +3 -3
- package/dist/entries/calendar.d.ts +3 -3
- package/dist/entries/calendar.js +13 -214
- package/dist/entries/calendar.mjs +5 -196
- package/dist/entries/carousel.d.mts +3 -3
- package/dist/entries/carousel.d.ts +3 -3
- package/dist/entries/carousel.js +17 -14
- package/dist/entries/carousel.mjs +10 -7
- package/dist/entries/chart.d.mts +1 -1
- package/dist/entries/chart.d.ts +1 -1
- package/dist/entries/chart.js +11 -11
- package/dist/entries/chart.mjs +1 -1
- package/dist/entries/command.d.mts +3 -3
- package/dist/entries/command.d.ts +3 -3
- package/dist/entries/command.js +21 -19
- package/dist/entries/command.mjs +8 -6
- package/dist/entries/drawer.d.mts +1 -1
- package/dist/entries/drawer.d.ts +1 -1
- package/dist/entries/drawer.js +9 -9
- package/dist/entries/drawer.mjs +2 -2
- package/dist/entries/input-otp.d.mts +2 -2
- package/dist/entries/input-otp.d.ts +2 -2
- package/dist/entries/input-otp.js +10 -8
- package/dist/entries/input-otp.mjs +6 -4
- package/dist/entries/resizable.d.mts +3 -2
- package/dist/entries/resizable.d.ts +3 -2
- package/dist/entries/resizable.js +8 -6
- package/dist/entries/resizable.mjs +6 -4
- package/dist/index.d.mts +974 -31
- package/dist/index.d.ts +974 -31
- package/dist/index.js +2992 -554
- package/dist/index.mjs +2486 -199
- package/dist/{use-saasflare-props-NrM2Glmp.d.mts → use-saasflare-props-BrjMhU0U.d.mts} +53 -4
- package/dist/{use-saasflare-props-NrM2Glmp.d.ts → use-saasflare-props-BrjMhU0U.d.ts} +53 -4
- package/package.json +4 -3
- package/styles/aurora.css +47 -0
- package/styles/palettes.css +483 -3
- package/styles/surfaces.css +89 -10
- package/styles/theme.css +11 -5
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>`.
|
|
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 {
|
|
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?: "
|
|
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
|
|
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"
|
|
46
|
-
* button promotes itself to
|
|
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,
|
|
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 {
|
|
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?: "
|
|
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
|
|
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"
|
|
46
|
-
* button promotes itself to
|
|
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,
|
|
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: "
|
|
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
|
-
|
|
311
|
-
|
|
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 ?? "
|
|
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 =
|
|
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:
|
|
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-
|
|
3
|
-
import { useSaasflareProps, cn } from './chunk-
|
|
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
|
|
7
|
+
import { jsx } from 'react/jsx-runtime';
|
|
9
8
|
|
|
10
9
|
var MotionSlot = m.create(Slot.Root);
|
|
11
10
|
var LEGACY_VARIANT_MAP = {
|
|
@@ -23,7 +22,8 @@ var buttonVariants = cva(
|
|
|
23
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",
|
|
24
23
|
ghost: "text-[var(--intent-text)] hover:bg-[var(--intent-text)]/10 dark:hover:bg-[var(--intent-text)]/15",
|
|
25
24
|
link: "text-[var(--intent-text)] underline-offset-4 hover:underline",
|
|
26
|
-
glass: "bg-[var(--
|
|
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
|
|
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
|
-
|
|
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:
|
|
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__ */
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
99
103
|
m.button,
|
|
100
104
|
{
|
|
101
|
-
|
|
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:
|
|
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
|
}
|