@saasflare/ui 1.0.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/LICENSE +23 -0
- package/dist/index.d.mts +2103 -0
- package/dist/index.d.ts +2103 -0
- package/dist/index.js +7244 -0
- package/dist/index.mjs +6866 -0
- package/fonts/index.ts +56 -0
- package/fonts/presets/default.ts +37 -0
- package/fonts/presets/distinctive.ts +43 -0
- package/fonts/presets/editorial.ts +44 -0
- package/fonts/presets/geometric.ts +37 -0
- package/fonts/presets/neutral.ts +41 -0
- package/fonts/presets/rounded.ts +38 -0
- package/package.json +90 -0
- package/styles/globals.css +12 -0
- package/styles/motion.css +53 -0
- package/styles/palettes.css +147 -0
- package/styles/surfaces.css +51 -0
- package/styles/theme.css +388 -0
package/fonts/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Public type surface for typography presets.
|
|
4
|
+
* @module packages/ui/fonts
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Presets are loaded via subpath imports so each consumer only bundles
|
|
8
|
+
* the fonts of the preset they actually use:
|
|
9
|
+
*
|
|
10
|
+
* import { fontVariables } from "@saasflare/ui/fonts/editorial"
|
|
11
|
+
* <SaasflareShell className={fontVariables}>
|
|
12
|
+
*
|
|
13
|
+
* Available subpaths (declared in package.json `exports`):
|
|
14
|
+
* @saasflare/ui/fonts/default Inter + Inter + JetBrains Mono
|
|
15
|
+
* @saasflare/ui/fonts/editorial Inter + Fraunces + JetBrains Mono
|
|
16
|
+
* @saasflare/ui/fonts/geometric Geist + Geist + Geist Mono
|
|
17
|
+
* @saasflare/ui/fonts/rounded Nunito + Nunito + JetBrains Mono
|
|
18
|
+
* @saasflare/ui/fonts/distinctive Inter + Bricolage Grotesque + JetBrains Mono
|
|
19
|
+
* @saasflare/ui/fonts/neutral Roboto + Roboto + Roboto Mono
|
|
20
|
+
*
|
|
21
|
+
* Internal file structure (`presets/*.ts`) is not part of the public API
|
|
22
|
+
* and may change between versions; only the subpaths above are stable.
|
|
23
|
+
*
|
|
24
|
+
* Required app setup: add `transpilePackages: ["@saasflare/ui"]` to
|
|
25
|
+
* `next.config.{js,ts}` so the Next.js compiler can process the
|
|
26
|
+
* `next/font/google` calls in the package source.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** All available font preset ids. Use as `import "@saasflare/ui/fonts/<id>"`. */
|
|
30
|
+
export type FontPreset =
|
|
31
|
+
| "default"
|
|
32
|
+
| "editorial"
|
|
33
|
+
| "geometric"
|
|
34
|
+
| "rounded"
|
|
35
|
+
| "distinctive"
|
|
36
|
+
| "neutral"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Shape of every preset module's default export contract.
|
|
40
|
+
* Each `@saasflare/ui/fonts/<preset>` module exports these names:
|
|
41
|
+
*
|
|
42
|
+
* - `fontBody` → next/font loader instance (carries `.variable` className)
|
|
43
|
+
* - `fontHeading` → next/font loader instance
|
|
44
|
+
* - `fontMono` → next/font loader instance
|
|
45
|
+
* - `fontVariables` → joined className string for `<SaasflareShell className={…}>`
|
|
46
|
+
*
|
|
47
|
+
* The loader instance type is intentionally `unknown` here — `next/font`
|
|
48
|
+
* generates a unique anonymous type per call, and we don't want this
|
|
49
|
+
* package to take a hard dependency on `next/font`'s internal types.
|
|
50
|
+
*/
|
|
51
|
+
export interface FontPresetModule {
|
|
52
|
+
fontBody: { variable: string }
|
|
53
|
+
fontHeading: { variable: string }
|
|
54
|
+
fontMono: { variable: string }
|
|
55
|
+
fontVariables: string
|
|
56
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Default typography preset — Inter + JetBrains Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/default
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* SaaS-neutral, the safest pick. Inter is system-ui-adjacent, dense,
|
|
8
|
+
* excellent across sizes. JetBrains Mono pairs cleanly without competing.
|
|
9
|
+
*
|
|
10
|
+
* Use for: dashboards, admin panels, generic SaaS UI.
|
|
11
|
+
*
|
|
12
|
+
* Public import path (do not rely on this internal location):
|
|
13
|
+
* import { fontVariables } from "@saasflare/ui/fonts/default"
|
|
14
|
+
*/
|
|
15
|
+
import { Inter, JetBrains_Mono } from "next/font/google"
|
|
16
|
+
|
|
17
|
+
export const fontBody = Inter({
|
|
18
|
+
subsets: ["latin"],
|
|
19
|
+
variable: "--font-body",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const fontHeading = Inter({
|
|
23
|
+
subsets: ["latin"],
|
|
24
|
+
variable: "--font-heading",
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const fontMono = JetBrains_Mono({
|
|
28
|
+
subsets: ["latin"],
|
|
29
|
+
variable: "--font-mono",
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/** Joined className string — pass to <SaasflareShell className={...}>. */
|
|
33
|
+
export const fontVariables = [
|
|
34
|
+
fontBody.variable,
|
|
35
|
+
fontHeading.variable,
|
|
36
|
+
fontMono.variable,
|
|
37
|
+
].join(" ")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Distinctive typography preset — Inter body + Bricolage Grotesque heading + JetBrains Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/distinctive
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Modern, with attitude. Bricolage Grotesque is a contemporary display
|
|
8
|
+
* sans with strong character — confident headings that still pair with
|
|
9
|
+
* Inter's quiet body text. Less serious than Editorial, more brand-forward
|
|
10
|
+
* than Default.
|
|
11
|
+
*
|
|
12
|
+
* `display: "swap"` on the heading: Bricolage's silhouette is distinct
|
|
13
|
+
* enough that the optional fallback would feel like a different brand.
|
|
14
|
+
*
|
|
15
|
+
* Use for: products that want a recognizable type voice without going
|
|
16
|
+
* full editorial — startups, design tools, brand-led marketing sites.
|
|
17
|
+
*
|
|
18
|
+
* Public import path:
|
|
19
|
+
* import { fontVariables } from "@saasflare/ui/fonts/distinctive"
|
|
20
|
+
*/
|
|
21
|
+
import { Inter, Bricolage_Grotesque, JetBrains_Mono } from "next/font/google"
|
|
22
|
+
|
|
23
|
+
export const fontBody = Inter({
|
|
24
|
+
subsets: ["latin"],
|
|
25
|
+
variable: "--font-body",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const fontHeading = Bricolage_Grotesque({
|
|
29
|
+
subsets: ["latin"],
|
|
30
|
+
variable: "--font-heading",
|
|
31
|
+
display: "swap",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const fontMono = JetBrains_Mono({
|
|
35
|
+
subsets: ["latin"],
|
|
36
|
+
variable: "--font-mono",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const fontVariables = [
|
|
40
|
+
fontBody.variable,
|
|
41
|
+
fontHeading.variable,
|
|
42
|
+
fontMono.variable,
|
|
43
|
+
].join(" ")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Editorial typography preset — Inter body + Fraunces heading + JetBrains Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/editorial
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Content-heavy products with strong editorial voice. Fraunces is a
|
|
8
|
+
* high-contrast contemporary serif with personality; Inter keeps body
|
|
9
|
+
* text quiet and readable. The serif/sans split signals "this is content,
|
|
10
|
+
* not chrome."
|
|
11
|
+
*
|
|
12
|
+
* `display: "swap"` on the heading: Fraunces diverges enough from system-ui
|
|
13
|
+
* that the optional fallback would feel bare during load.
|
|
14
|
+
*
|
|
15
|
+
* Use for: blogs, documentation sites, knowledge bases, magazine-style
|
|
16
|
+
* marketing pages.
|
|
17
|
+
*
|
|
18
|
+
* Public import path:
|
|
19
|
+
* import { fontVariables } from "@saasflare/ui/fonts/editorial"
|
|
20
|
+
*/
|
|
21
|
+
import { Inter, Fraunces, JetBrains_Mono } from "next/font/google"
|
|
22
|
+
|
|
23
|
+
export const fontBody = Inter({
|
|
24
|
+
subsets: ["latin"],
|
|
25
|
+
variable: "--font-body",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const fontHeading = Fraunces({
|
|
29
|
+
subsets: ["latin"],
|
|
30
|
+
variable: "--font-heading",
|
|
31
|
+
display: "swap",
|
|
32
|
+
axes: ["opsz"],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const fontMono = JetBrains_Mono({
|
|
36
|
+
subsets: ["latin"],
|
|
37
|
+
variable: "--font-mono",
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const fontVariables = [
|
|
41
|
+
fontBody.variable,
|
|
42
|
+
fontHeading.variable,
|
|
43
|
+
fontMono.variable,
|
|
44
|
+
].join(" ")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Geometric typography preset — Geist + Geist Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/geometric
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Technical-minimal aesthetic. Geist is tighter and more architectural
|
|
8
|
+
* than Inter; the matched mono keeps visual language uniform across copy
|
|
9
|
+
* and code blocks. Vercel-style minimalism.
|
|
10
|
+
*
|
|
11
|
+
* Use for: developer tools, API products, infrastructure dashboards.
|
|
12
|
+
*
|
|
13
|
+
* Public import path:
|
|
14
|
+
* import { fontVariables } from "@saasflare/ui/fonts/geometric"
|
|
15
|
+
*/
|
|
16
|
+
import { Geist, Geist_Mono } from "next/font/google"
|
|
17
|
+
|
|
18
|
+
export const fontBody = Geist({
|
|
19
|
+
subsets: ["latin"],
|
|
20
|
+
variable: "--font-body",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const fontHeading = Geist({
|
|
24
|
+
subsets: ["latin"],
|
|
25
|
+
variable: "--font-heading",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const fontMono = Geist_Mono({
|
|
29
|
+
subsets: ["latin"],
|
|
30
|
+
variable: "--font-mono",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const fontVariables = [
|
|
34
|
+
fontBody.variable,
|
|
35
|
+
fontHeading.variable,
|
|
36
|
+
fontMono.variable,
|
|
37
|
+
].join(" ")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Neutral typography preset — Roboto + Roboto Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/neutral
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Material-Design-aligned, maximally familiar. Roboto is the most-seen
|
|
8
|
+
* web font on earth — users won't notice it, which is exactly the point.
|
|
9
|
+
* Use when typography should disappear so other brand elements (color,
|
|
10
|
+
* imagery, layout) carry the identity.
|
|
11
|
+
*
|
|
12
|
+
* Use for: enterprise tools, internal dashboards, products with strong
|
|
13
|
+
* non-typographic brand systems, Android-adjacent companion web apps.
|
|
14
|
+
*
|
|
15
|
+
* Public import path:
|
|
16
|
+
* import { fontVariables } from "@saasflare/ui/fonts/neutral"
|
|
17
|
+
*/
|
|
18
|
+
import { Roboto, Roboto_Mono } from "next/font/google"
|
|
19
|
+
|
|
20
|
+
export const fontBody = Roboto({
|
|
21
|
+
subsets: ["latin"],
|
|
22
|
+
variable: "--font-body",
|
|
23
|
+
weight: ["400", "500", "700"],
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const fontHeading = Roboto({
|
|
27
|
+
subsets: ["latin"],
|
|
28
|
+
variable: "--font-heading",
|
|
29
|
+
weight: ["500", "700"],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const fontMono = Roboto_Mono({
|
|
33
|
+
subsets: ["latin"],
|
|
34
|
+
variable: "--font-mono",
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const fontVariables = [
|
|
38
|
+
fontBody.variable,
|
|
39
|
+
fontHeading.variable,
|
|
40
|
+
fontMono.variable,
|
|
41
|
+
].join(" ")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @reviewed 2026-04-19
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Rounded typography preset — Nunito + JetBrains Mono.
|
|
4
|
+
* @module packages/ui/fonts/presets/rounded
|
|
5
|
+
* @package ui
|
|
6
|
+
*
|
|
7
|
+
* Consumer-friendly, warm. Nunito's rounded terminals soften the entire
|
|
8
|
+
* UI without sacrificing legibility. JetBrains Mono keeps code blocks
|
|
9
|
+
* feeling like code so the warmth stays scoped to prose.
|
|
10
|
+
*
|
|
11
|
+
* Use for: education, kids/family apps, wellness, any product that wants
|
|
12
|
+
* warmth over precision.
|
|
13
|
+
*
|
|
14
|
+
* Public import path:
|
|
15
|
+
* import { fontVariables } from "@saasflare/ui/fonts/rounded"
|
|
16
|
+
*/
|
|
17
|
+
import { Nunito, JetBrains_Mono } from "next/font/google"
|
|
18
|
+
|
|
19
|
+
export const fontBody = Nunito({
|
|
20
|
+
subsets: ["latin"],
|
|
21
|
+
variable: "--font-body",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const fontHeading = Nunito({
|
|
25
|
+
subsets: ["latin"],
|
|
26
|
+
variable: "--font-heading",
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const fontMono = JetBrains_Mono({
|
|
30
|
+
subsets: ["latin"],
|
|
31
|
+
variable: "--font-mono",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const fontVariables = [
|
|
35
|
+
fontBody.variable,
|
|
36
|
+
fontHeading.variable,
|
|
37
|
+
fontMono.variable,
|
|
38
|
+
].join(" ")
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saasflare/ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Saasflare UI Components Library – Standalone components built on Radix UI + Tailwind CSS",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/saasflare/saasflare-ui.git",
|
|
10
|
+
"directory": "packages/ui"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"registry": "https://registry.npmjs.org",
|
|
15
|
+
"provenance": true
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.mjs",
|
|
24
|
+
"require": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./styles": "./styles/globals.css",
|
|
27
|
+
"./globals.css": "./styles/globals.css",
|
|
28
|
+
"./theme.css": "./styles/theme.css",
|
|
29
|
+
"./fonts": "./fonts/index.ts",
|
|
30
|
+
"./fonts/default": "./fonts/presets/default.ts",
|
|
31
|
+
"./fonts/editorial": "./fonts/presets/editorial.ts",
|
|
32
|
+
"./fonts/geometric": "./fonts/presets/geometric.ts",
|
|
33
|
+
"./fonts/rounded": "./fonts/presets/rounded.ts",
|
|
34
|
+
"./fonts/distinctive": "./fonts/presets/distinctive.ts",
|
|
35
|
+
"./fonts/neutral": "./fonts/presets/neutral.ts",
|
|
36
|
+
"./fonts/*": null
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"styles",
|
|
41
|
+
"fonts"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"radix-ui": "^1.4.3",
|
|
45
|
+
"@base-ui/react": "^1.2.0",
|
|
46
|
+
"class-variance-authority": "^0.7.1",
|
|
47
|
+
"clsx": "^2.1.1",
|
|
48
|
+
"cmdk": "^1.1.1",
|
|
49
|
+
"input-otp": "^1.4.2",
|
|
50
|
+
"lucide-react": "^0.577.0",
|
|
51
|
+
"nprogress": "^0.2.0",
|
|
52
|
+
"tailwind-merge": "^3.5.0",
|
|
53
|
+
"tw-animate-css": "^1.4.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"next": "16.2.3",
|
|
57
|
+
"next-themes": "^0.4.0",
|
|
58
|
+
"react": "^19.0.0",
|
|
59
|
+
"react-dom": "^19.0.0",
|
|
60
|
+
"tailwindcss": "^4.0.0",
|
|
61
|
+
"framer-motion": "^12.0.0",
|
|
62
|
+
"recharts": "^3.0.0",
|
|
63
|
+
"react-hook-form": "^7.0.0",
|
|
64
|
+
"@hookform/resolvers": "^5.0.0",
|
|
65
|
+
"zod": "^4.0.0",
|
|
66
|
+
"sonner": "^2.0.0",
|
|
67
|
+
"vaul": "^1.0.0",
|
|
68
|
+
"react-day-picker": "^9.0.0",
|
|
69
|
+
"date-fns": "^4.0.0",
|
|
70
|
+
"embla-carousel-react": "^8.0.0",
|
|
71
|
+
"react-resizable-panels": "^4.0.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/nprogress": "^0.2.3",
|
|
75
|
+
"@types/react": "^19.2.14",
|
|
76
|
+
"@types/react-dom": "^19.2.3",
|
|
77
|
+
"next": "16.2.3",
|
|
78
|
+
"next-themes": "^0.4.6",
|
|
79
|
+
"react": "^19.2.4",
|
|
80
|
+
"react-dom": "^19.2.4",
|
|
81
|
+
"tsup": "^8.5.1",
|
|
82
|
+
"typescript": "^5.9.3"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsup",
|
|
86
|
+
"dev": "tsup --watch",
|
|
87
|
+
"lint": "eslint .",
|
|
88
|
+
"typecheck": "tsc --noEmit"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Saasflare UI — single-file entry point.
|
|
3
|
+
* @module packages/ui/styles/globals
|
|
4
|
+
* @package ui
|
|
5
|
+
* @reviewed 2026-04-19
|
|
6
|
+
*
|
|
7
|
+
* Apps should import exactly this file:
|
|
8
|
+
* @import "@saasflare/ui/globals.css";
|
|
9
|
+
*
|
|
10
|
+
* theme.css transitively pulls in motion.css, surfaces.css, and themes.css.
|
|
11
|
+
*/
|
|
12
|
+
@import "./theme.css";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Motion tokens + animation kill-switch.
|
|
3
|
+
* @module packages/ui/styles/motion
|
|
4
|
+
* @package ui
|
|
5
|
+
* @reviewed 2026-04-19
|
|
6
|
+
*
|
|
7
|
+
* Duration and easing tokens consumed by components and utility classes.
|
|
8
|
+
* The [data-animated="false"] attribute — set by SaasflareProvider on <html> —
|
|
9
|
+
* acts as a global kill-switch that collapses every animation and transition,
|
|
10
|
+
* including those in third-party libraries that don't read our tokens.
|
|
11
|
+
*
|
|
12
|
+
* Reduced-motion media query is an additional safety net for users with an
|
|
13
|
+
* OS-level preference, regardless of the provider's `animated` prop.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
--duration-fast: 150ms;
|
|
18
|
+
--duration-normal: 220ms;
|
|
19
|
+
--duration-slow: 320ms;
|
|
20
|
+
--duration-overlay: 400ms;
|
|
21
|
+
|
|
22
|
+
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
23
|
+
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
24
|
+
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
25
|
+
--ease-enter: cubic-bezier(0.16, 1, 0.3, 1);
|
|
26
|
+
--ease-exit: cubic-bezier(0.3, 0, 1, 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-animated="false"],
|
|
30
|
+
[data-animated="false"] *,
|
|
31
|
+
[data-animated="false"] *::before,
|
|
32
|
+
[data-animated="false"] *::after {
|
|
33
|
+
animation-duration: 0ms !important;
|
|
34
|
+
animation-iteration-count: 1 !important;
|
|
35
|
+
transition-duration: 0ms !important;
|
|
36
|
+
scroll-behavior: auto !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
:root {
|
|
41
|
+
--duration-fast: 0ms;
|
|
42
|
+
--duration-normal: 0ms;
|
|
43
|
+
--duration-slow: 0ms;
|
|
44
|
+
--duration-overlay: 0ms;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
*, *::before, *::after {
|
|
48
|
+
animation-duration: 0ms !important;
|
|
49
|
+
animation-iteration-count: 1 !important;
|
|
50
|
+
transition-duration: 0ms !important;
|
|
51
|
+
scroll-behavior: auto !important;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 16 preset brand palettes — activate via [data-palette="id"] on <html>.
|
|
3
|
+
* @module packages/ui/styles/palettes
|
|
4
|
+
* @package ui
|
|
5
|
+
* @reviewed 2026-04-19
|
|
6
|
+
*
|
|
7
|
+
* Each preset overrides only the rebrand surface:
|
|
8
|
+
* --primary-h / --primary-c / --primary-l (brand axis)
|
|
9
|
+
* --neutral-h / --neutral-c (neutral axis, only when palette wants neutral greys)
|
|
10
|
+
*
|
|
11
|
+
* All downstream semantic tokens (secondary, muted, border, card, chart-*, …)
|
|
12
|
+
* are derived in theme.css from these axes. Adding a preset costs one line.
|
|
13
|
+
*
|
|
14
|
+
* Selectors are scoped with `:root` prefix (e.g. `:root[data-palette="ocean"]`)
|
|
15
|
+
* to guarantee specificity (0,2,0) — this beats the `:root` baseline (0,1,0)
|
|
16
|
+
* and the `.dark` class (0,1,0) regardless of @import source order.
|
|
17
|
+
*
|
|
18
|
+
* Ink & Stone are achromatic palettes (primary-c: 0). Chart palettes are
|
|
19
|
+
* overridden so dashboards stay legible with 5 distinct hues.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* ─── Saasflare (house brand) ───────────────────── */
|
|
23
|
+
:root[data-palette="saasflare"] { --primary-h: 259.1; --primary-c: 0.214; --primary-l: 0.623; }
|
|
24
|
+
:root[data-palette="saasflare"].dark { --primary-l: 0.72; }
|
|
25
|
+
|
|
26
|
+
/* ─── Ocean ─────────────────────────────────────── */
|
|
27
|
+
:root[data-palette="ocean"] { --primary-h: 230; --primary-c: 0.18; --primary-l: 0.60; }
|
|
28
|
+
:root[data-palette="ocean"].dark { --primary-l: 0.70; }
|
|
29
|
+
|
|
30
|
+
/* ─── Ink (neutral) ─────────────────────────────── */
|
|
31
|
+
:root[data-palette="ink"] {
|
|
32
|
+
--primary-h: 0;
|
|
33
|
+
--primary-c: 0;
|
|
34
|
+
--primary-l: 0.45;
|
|
35
|
+
--neutral-h: 0;
|
|
36
|
+
--neutral-c: 0;
|
|
37
|
+
}
|
|
38
|
+
:root[data-palette="ink"].dark { --primary-l: 0.75; }
|
|
39
|
+
|
|
40
|
+
/* ─── Achromatic (near-black / near-white) ──────── */
|
|
41
|
+
/* Achromatic palettes invert primary lightness across modes, so the default
|
|
42
|
+
* white --primary-foreground would collide with a near-white --primary in
|
|
43
|
+
* dark mode. Pin the foreground per-mode to keep button contrast readable. */
|
|
44
|
+
:root[data-palette="achromatic"] {
|
|
45
|
+
--primary-h: 0;
|
|
46
|
+
--primary-c: 0;
|
|
47
|
+
--primary-l: 0.15; /* ≈ #171717, near-black */
|
|
48
|
+
--neutral-h: 0;
|
|
49
|
+
--neutral-c: 0;
|
|
50
|
+
--primary-foreground: oklch(1 0 0); /* white on near-black */
|
|
51
|
+
}
|
|
52
|
+
:root[data-palette="achromatic"].dark {
|
|
53
|
+
--primary-l: 0.95; /* ≈ #f2f2f2, near-white */
|
|
54
|
+
--primary-foreground: oklch(0.15 0 0); /* near-black on near-white */
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ─── Black (pure) ──────────────────────────────── */
|
|
58
|
+
:root[data-palette="black"] {
|
|
59
|
+
--primary-h: 0;
|
|
60
|
+
--primary-c: 0;
|
|
61
|
+
--primary-l: 0; /* #000 */
|
|
62
|
+
--neutral-h: 0;
|
|
63
|
+
--neutral-c: 0;
|
|
64
|
+
--primary-foreground: oklch(1 0 0); /* white on black */
|
|
65
|
+
}
|
|
66
|
+
:root[data-palette="black"].dark {
|
|
67
|
+
--primary-l: 1; /* #fff */
|
|
68
|
+
--primary-foreground: oklch(0 0 0); /* black on white */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ─── Aurora ────────────────────────────────────── */
|
|
72
|
+
:root[data-palette="aurora"] { --primary-h: 195; --primary-c: 0.19; --primary-l: 0.65; }
|
|
73
|
+
:root[data-palette="aurora"].dark { --primary-l: 0.75; }
|
|
74
|
+
|
|
75
|
+
/* ─── Indigo ────────────────────────────────────── */
|
|
76
|
+
:root[data-palette="indigo"] { --primary-h: 265; --primary-c: 0.20; --primary-l: 0.60; }
|
|
77
|
+
:root[data-palette="indigo"].dark { --primary-l: 0.70; }
|
|
78
|
+
|
|
79
|
+
/* ─── Emerald ───────────────────────────────────── */
|
|
80
|
+
:root[data-palette="emerald"] { --primary-h: 155; --primary-c: 0.17; --primary-l: 0.55; }
|
|
81
|
+
:root[data-palette="emerald"].dark { --primary-l: 0.68; }
|
|
82
|
+
|
|
83
|
+
/* ─── Violet ────────────────────────────────────── */
|
|
84
|
+
:root[data-palette="violet"] { --primary-h: 290; --primary-c: 0.20; --primary-l: 0.60; }
|
|
85
|
+
:root[data-palette="violet"].dark { --primary-l: 0.70; }
|
|
86
|
+
|
|
87
|
+
/* ─── Coral ─────────────────────────────────────── */
|
|
88
|
+
:root[data-palette="coral"] { --primary-h: 20; --primary-c: 0.19; --primary-l: 0.65; }
|
|
89
|
+
:root[data-palette="coral"].dark { --primary-l: 0.72; }
|
|
90
|
+
|
|
91
|
+
/* ─── Stone (neutral) ───────────────────────────── */
|
|
92
|
+
:root[data-palette="stone"] {
|
|
93
|
+
--primary-h: 0;
|
|
94
|
+
--primary-c: 0;
|
|
95
|
+
--primary-l: 0.55;
|
|
96
|
+
--neutral-h: 0;
|
|
97
|
+
--neutral-c: 0;
|
|
98
|
+
}
|
|
99
|
+
:root[data-palette="stone"].dark { --primary-l: 0.78; }
|
|
100
|
+
|
|
101
|
+
/* ─── Jade ──────────────────────────────────────── */
|
|
102
|
+
:root[data-palette="jade"] { --primary-h: 165; --primary-c: 0.16; --primary-l: 0.58; }
|
|
103
|
+
:root[data-palette="jade"].dark { --primary-l: 0.70; }
|
|
104
|
+
|
|
105
|
+
/* ─── Cobalt ────────────────────────────────────── */
|
|
106
|
+
:root[data-palette="cobalt"] { --primary-h: 240; --primary-c: 0.20; --primary-l: 0.60; }
|
|
107
|
+
:root[data-palette="cobalt"].dark { --primary-l: 0.70; }
|
|
108
|
+
|
|
109
|
+
/* ─── Amber ─────────────────────────────────────── */
|
|
110
|
+
:root[data-palette="amber"] { --primary-h: 50; --primary-c: 0.17; --primary-l: 0.65; }
|
|
111
|
+
:root[data-palette="amber"].dark { --primary-l: 0.72; }
|
|
112
|
+
|
|
113
|
+
/* ─── Fuchsia ───────────────────────────────────── */
|
|
114
|
+
:root[data-palette="fuchsia"] { --primary-h: 340; --primary-c: 0.21; --primary-l: 0.62; }
|
|
115
|
+
:root[data-palette="fuchsia"].dark { --primary-l: 0.72; }
|
|
116
|
+
|
|
117
|
+
/* ─── Honey ─────────────────────────────────────── */
|
|
118
|
+
:root[data-palette="honey"] { --primary-h: 70; --primary-c: 0.16; --primary-l: 0.68; }
|
|
119
|
+
:root[data-palette="honey"].dark { --primary-l: 0.76; }
|
|
120
|
+
|
|
121
|
+
/* ─── Teal ──────────────────────────────────────── */
|
|
122
|
+
:root[data-palette="teal"] { --primary-h: 185; --primary-c: 0.15; --primary-l: 0.60; }
|
|
123
|
+
:root[data-palette="teal"].dark { --primary-l: 0.70; }
|
|
124
|
+
|
|
125
|
+
/* ─── Iris ──────────────────────────────────────── */
|
|
126
|
+
:root[data-palette="iris"] { --primary-h: 255; --primary-c: 0.16; --primary-l: 0.60; }
|
|
127
|
+
:root[data-palette="iris"].dark { --primary-l: 0.70; }
|
|
128
|
+
|
|
129
|
+
/* ─── Ruby ──────────────────────────────────────── */
|
|
130
|
+
:root[data-palette="ruby"] { --primary-h: 10; --primary-c: 0.21; --primary-l: 0.58; }
|
|
131
|
+
:root[data-palette="ruby"].dark { --primary-l: 0.68; }
|
|
132
|
+
|
|
133
|
+
/* ============================================
|
|
134
|
+
* Chart palette overrides for achromatic palettes
|
|
135
|
+
*
|
|
136
|
+
* With --primary-c: 0, the derived chart colors collapse to grayscale.
|
|
137
|
+
* Give Ink and Stone a fixed distinguishable 5-hue palette.
|
|
138
|
+
* ============================================ */
|
|
139
|
+
:root[data-palette="ink"],
|
|
140
|
+
:root[data-palette="stone"],
|
|
141
|
+
:root[data-palette="black"] {
|
|
142
|
+
--chart-1: oklch(0.60 0.18 230); /* blue */
|
|
143
|
+
--chart-2: oklch(0.65 0.17 55); /* orange */
|
|
144
|
+
--chart-3: oklch(0.60 0.15 155); /* green */
|
|
145
|
+
--chart-4: oklch(0.58 0.20 25); /* red */
|
|
146
|
+
--chart-5: oklch(0.60 0.18 290); /* violet */
|
|
147
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Surface system — flat / glass as semantic overlay.
|
|
3
|
+
* @module packages/ui/styles/surfaces
|
|
4
|
+
* @package ui
|
|
5
|
+
* @reviewed 2026-04-19
|
|
6
|
+
*
|
|
7
|
+
* Components bind to four surface tokens:
|
|
8
|
+
* --surface-bg background color or gradient
|
|
9
|
+
* --surface-border border color
|
|
10
|
+
* --surface-backdrop backdrop-filter value (blur/saturate) or "none"
|
|
11
|
+
* --surface-shadow box-shadow value
|
|
12
|
+
*
|
|
13
|
+
* Switching [data-style="…"] on <html> swaps the tokens without touching
|
|
14
|
+
* any component. Extend by adding a new selector block:
|
|
15
|
+
*
|
|
16
|
+
* [data-style="neumorphic"] { --surface-bg: …; … }
|
|
17
|
+
*
|
|
18
|
+
* and registering the id in the StyleVariant union (types.ts).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
:root {
|
|
22
|
+
--surface-bg: var(--card);
|
|
23
|
+
--surface-border: var(--border);
|
|
24
|
+
--surface-backdrop: none;
|
|
25
|
+
--surface-shadow: 0 1px 2px oklch(0 0 0 / 0.05);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[data-style="flat"] {
|
|
29
|
+
--surface-bg: var(--card);
|
|
30
|
+
--surface-border: var(--border);
|
|
31
|
+
--surface-backdrop: none;
|
|
32
|
+
--surface-shadow: 0 1px 2px oklch(0 0 0 / 0.05);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-style="glass"] {
|
|
36
|
+
--surface-bg: oklch(0.99 0.005 var(--neutral-h) / 0.6);
|
|
37
|
+
--surface-border: oklch(1 0 0 / 0.2);
|
|
38
|
+
--surface-backdrop: blur(12px) saturate(140%);
|
|
39
|
+
--surface-shadow:
|
|
40
|
+
0 8px 32px oklch(0 0 0 / 0.08),
|
|
41
|
+
inset 0 1px 0 oklch(1 0 0 / 0.4);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-style="glass"].dark,
|
|
45
|
+
.dark [data-style="glass"] {
|
|
46
|
+
--surface-bg: oklch(0.2 0.015 var(--neutral-h) / 0.5);
|
|
47
|
+
--surface-border: oklch(1 0 0 / 0.1);
|
|
48
|
+
--surface-shadow:
|
|
49
|
+
0 8px 32px oklch(0 0 0 / 0.4),
|
|
50
|
+
inset 0 1px 0 oklch(1 0 0 / 0.08);
|
|
51
|
+
}
|