@syncedco/flow 0.3.2 → 0.5.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/CHANGELOG.md +51 -0
- package/CONTRIBUTING.md +18 -5
- package/README.md +32 -9
- package/bin/synced-flow.mjs +515 -83
- package/components.css +440 -9
- package/dist/index.d.ts +69 -0
- package/dist/index.js +69 -0
- package/docs/accessibility-css.md +14 -3
- package/docs/ai-usage.md +22 -1
- package/docs/api-contract.md +10 -2
- package/docs/audit-2026-06.md +13 -12
- package/docs/cli-reference.md +9 -6
- package/docs/config-reference.md +2 -2
- package/docs/css-optimisation.md +10 -10
- package/docs/native-components.md +121 -0
- package/docs/patterns.md +18 -0
- package/docs/quick-start.md +6 -4
- package/docs/recipes.md +12 -0
- package/docs/release-readiness.md +32 -7
- package/docs/system-primitives.md +9 -2
- package/docs/tokens.md +4 -1
- package/examples/README.md +2 -2
- package/examples/next/app/synced-flow.generated.css +5 -5
- package/examples/plain-html/synced-flow.generated.css +5 -5
- package/examples/templates/README.md +2 -0
- package/examples/templates/app-settings-workflow.html +81 -0
- package/examples/vite/src/synced-flow.generated.css +5 -5
- package/examples/wordpress/assets/css/synced-flow.css +150 -23
- package/package.json +14 -3
- package/skills/synced-flow/SKILL.md +20 -3
- package/src/index.ts +69 -0
- package/src/presets.mjs +5 -5
- package/src/tokens.mjs +10 -8
- package/styles.css +452 -19
- package/tokens.css +12 -10
package/examples/README.md
CHANGED
|
@@ -7,8 +7,8 @@ These examples show the intended starting point for new projects.
|
|
|
7
7
|
- `vite` - Vite React setup
|
|
8
8
|
- `plain-html` - no framework setup with a complete website demo using only
|
|
9
9
|
Synced Flow primitives for header, hero, content, cards, form, notice, and footer
|
|
10
|
-
- `templates` - copy-ready full-page HTML templates for SaaS,
|
|
11
|
-
and coming soon pages
|
|
10
|
+
- `templates` - copy-ready full-page HTML templates for SaaS, app settings,
|
|
11
|
+
portfolio, blog, and coming soon pages
|
|
12
12
|
- `wordpress` - block theme style setup with enqueue code, templates, parts,
|
|
13
13
|
and PHP patterns
|
|
14
14
|
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -10,6 +10,7 @@ Use the CLI for framework-specific output:
|
|
|
10
10
|
pnpm exec synced-flow recipe saas-landing --markup
|
|
11
11
|
pnpm exec synced-flow recipe saas-landing --framework next --markup
|
|
12
12
|
pnpm exec synced-flow recipe saas-dashboard --framework next --markup
|
|
13
|
+
pnpm exec synced-flow recipe app-settings-workflow --markup
|
|
13
14
|
pnpm exec synced-flow recipe portfolio-scroll --framework astro --markup
|
|
14
15
|
```
|
|
15
16
|
|
|
@@ -17,6 +18,7 @@ Templates:
|
|
|
17
18
|
|
|
18
19
|
- `saas-landing.html`
|
|
19
20
|
- `saas-dashboard.html`
|
|
21
|
+
- `app-settings-workflow.html`
|
|
20
22
|
- `portfolio-scroll.html`
|
|
21
23
|
- `blog-index.html`
|
|
22
24
|
- `coming-soon.html`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Synced Flow Application Settings Template</title>
|
|
7
|
+
<link rel="stylesheet" href="../plain-html/synced-flow.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<a class="sf-skip-link" href="#settings-main">Skip to settings</a>
|
|
11
|
+
<main class="sf-section" id="settings-main">
|
|
12
|
+
<div class="sf-container sf-split">
|
|
13
|
+
<nav aria-label="Setup progress">
|
|
14
|
+
<ol class="sf-stepper">
|
|
15
|
+
<li class="sf-stepper__step" data-complete="true"><span class="sf-stepper__marker" aria-hidden="true">✓</span><span class="sf-stepper__label"><span class="sr-only">Completed: </span>Workspace</span></li>
|
|
16
|
+
<li class="sf-stepper__step" aria-current="step"><span class="sf-stepper__marker" aria-hidden="true"></span><span class="sf-stepper__label">Preferences</span></li>
|
|
17
|
+
<li class="sf-stepper__step"><span class="sf-stepper__marker" aria-hidden="true"></span><span class="sf-stepper__label">Review</span></li>
|
|
18
|
+
</ol>
|
|
19
|
+
</nav>
|
|
20
|
+
|
|
21
|
+
<form class="sf-card sf-form" aria-labelledby="preferences-title">
|
|
22
|
+
<header class="sf-stack">
|
|
23
|
+
<p class="sf-kicker">Step 2 of 3</p>
|
|
24
|
+
<h1 id="preferences-title">Workspace preferences</h1>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<label class="sf-field">
|
|
28
|
+
<span class="sf-label">Workspace URL</span>
|
|
29
|
+
<span class="sf-input-group">
|
|
30
|
+
<span class="sf-input-group__leading" aria-hidden="true">https://</span>
|
|
31
|
+
<input class="sf-input" name="workspace" autocomplete="organization" />
|
|
32
|
+
<span class="sf-input-group__trailing" aria-hidden="true">.example.com</span>
|
|
33
|
+
</span>
|
|
34
|
+
</label>
|
|
35
|
+
|
|
36
|
+
<fieldset class="sf-fieldset">
|
|
37
|
+
<legend>Billing cycle</legend>
|
|
38
|
+
<div class="sf-segmented-control">
|
|
39
|
+
<label class="sf-segmented-control__option"><input type="radio" name="cycle" value="monthly" checked /><span class="sf-segmented-control__label">Monthly</span></label>
|
|
40
|
+
<label class="sf-segmented-control__option"><input type="radio" name="cycle" value="annual" /><span class="sf-segmented-control__label">Annual</span></label>
|
|
41
|
+
</div>
|
|
42
|
+
</fieldset>
|
|
43
|
+
|
|
44
|
+
<label class="sf-switch">
|
|
45
|
+
<input type="checkbox" role="switch" name="weekly_summary" checked />
|
|
46
|
+
<span class="sf-switch__control" aria-hidden="true"></span>
|
|
47
|
+
<span class="sf-switch__label">Email weekly summary</span>
|
|
48
|
+
</label>
|
|
49
|
+
|
|
50
|
+
<label class="sf-field">
|
|
51
|
+
<span class="sf-label" id="team-size-label">Team size: <output for="team-size">12</output></span>
|
|
52
|
+
<input class="sf-range" id="team-size" type="range" name="team_size" min="1" max="50" value="12" aria-labelledby="team-size-label" />
|
|
53
|
+
</label>
|
|
54
|
+
|
|
55
|
+
<label class="sf-field">
|
|
56
|
+
<span class="sf-label">Brand mark</span>
|
|
57
|
+
<input class="sf-file-input" type="file" name="brand_mark" accept="image/png,image/svg+xml" />
|
|
58
|
+
<span class="sf-help">PNG or SVG.</span>
|
|
59
|
+
</label>
|
|
60
|
+
|
|
61
|
+
<div class="sf-field">
|
|
62
|
+
<label class="sf-label" for="owner-search">Workspace owner</label>
|
|
63
|
+
<div class="sf-combobox">
|
|
64
|
+
<input class="sf-input" id="owner-search" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="owner-options" />
|
|
65
|
+
<ul class="sf-combobox__listbox" id="owner-options" role="listbox" hidden>
|
|
66
|
+
<li class="sf-combobox__option" id="owner-option-1" role="option" aria-selected="true">Alex Morgan</li>
|
|
67
|
+
<li class="sf-combobox__option" id="owner-option-2" role="option" aria-selected="false">Jamie Lee</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</div>
|
|
70
|
+
<span class="sf-help">This static fixture shows presentation. The consuming app must implement combobox behavior.</span>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="sf-wizard-actions">
|
|
74
|
+
<button class="sf-button sf-button--outline" type="button">Back</button>
|
|
75
|
+
<button class="sf-button" type="submit"><span class="sf-spinner" aria-hidden="true" hidden></span><span>Continue</span></button>
|
|
76
|
+
</div>
|
|
77
|
+
</form>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -184,19 +184,19 @@
|
|
|
184
184
|
--color-surface-elevated: var(--sf-colour-surface-elevated);
|
|
185
185
|
--sf-colour-border: oklch(18% 0.026 250 / 0.12);
|
|
186
186
|
--color-border: var(--sf-colour-border);
|
|
187
|
-
--sf-colour-primary: oklch(
|
|
187
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
188
188
|
--color-primary: var(--sf-colour-primary);
|
|
189
|
-
--sf-colour-primary-hover: oklch(
|
|
189
|
+
--sf-colour-primary-hover: oklch(44% 0.14 40);
|
|
190
190
|
--color-primary-hover: var(--sf-colour-primary-hover);
|
|
191
191
|
--sf-colour-primary-foreground: oklch(100% 0 0);
|
|
192
192
|
--color-primary-foreground: var(--sf-colour-primary-foreground);
|
|
193
193
|
--sf-colour-accent: oklch(70% 0.12 205);
|
|
194
194
|
--color-accent: var(--sf-colour-accent);
|
|
195
|
-
--sf-colour-link: oklch(
|
|
195
|
+
--sf-colour-link: oklch(50% 0.16 40);
|
|
196
196
|
--color-link: var(--sf-colour-link);
|
|
197
|
-
--sf-colour-link-hover: oklch(
|
|
197
|
+
--sf-colour-link-hover: oklch(44% 0.14 40);
|
|
198
198
|
--color-link-hover: var(--sf-colour-link-hover);
|
|
199
|
-
--sf-colour-ring: oklch(
|
|
199
|
+
--sf-colour-ring: oklch(50% 0.16 40);
|
|
200
200
|
--color-ring: var(--sf-colour-ring);
|
|
201
201
|
--sf-button-radius: 0.5rem;
|
|
202
202
|
--button-radius: 0.5rem;
|
|
@@ -602,16 +602,6 @@
|
|
|
602
602
|
}
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
-
:where(.space-y-1, .space-y-2, .space-y-3, .space-y-4, .space-y-5, .space-y-6, .space-y-8, .space-y-10, .space-y-12) > * + * {
|
|
606
|
-
margin-block-start: var(--sf-space-y);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
:where(.space-x-1, .space-x-2, .space-x-3, .space-x-4, .space-x-5, .space-x-6, .space-x-8) > * + * {
|
|
610
|
-
margin-inline-start: var(--sf-space-x);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
.divide-y > * + * { border-block-start-width: var(--sf-divide-y); }
|
|
614
|
-
.divide-x > * + * { border-inline-start-width: var(--sf-divide-x); }
|
|
615
605
|
}
|
|
616
606
|
|
|
617
607
|
@layer components {
|
|
@@ -915,6 +905,8 @@
|
|
|
915
905
|
|
|
916
906
|
.sf-data-table :where(th button, .sf-table-sort) {
|
|
917
907
|
align-items: center;
|
|
908
|
+
background: transparent;
|
|
909
|
+
border: 0;
|
|
918
910
|
color: inherit;
|
|
919
911
|
display: inline-flex;
|
|
920
912
|
font: inherit;
|
|
@@ -923,14 +915,14 @@
|
|
|
923
915
|
padding: 0;
|
|
924
916
|
}
|
|
925
917
|
|
|
926
|
-
.sf-data-table :where(
|
|
918
|
+
.sf-data-table th[aria-sort] :where(button, .sf-table-sort)::after {
|
|
927
919
|
color: var(--color-muted-foreground);
|
|
928
920
|
content: "\2195";
|
|
929
921
|
font-size: var(--step--2);
|
|
930
922
|
}
|
|
931
923
|
|
|
932
|
-
.sf-data-table :where(
|
|
933
|
-
.sf-data-table :where(
|
|
924
|
+
.sf-data-table th[aria-sort="ascending"] :where(button, .sf-table-sort)::after { content: "\2191"; }
|
|
925
|
+
.sf-data-table th[aria-sort="descending"] :where(button, .sf-table-sort)::after { content: "\2193"; }
|
|
934
926
|
|
|
935
927
|
.sf-data-table tr[data-clickable="true"] {
|
|
936
928
|
cursor: pointer;
|
|
@@ -1005,7 +997,7 @@
|
|
|
1005
997
|
}
|
|
1006
998
|
|
|
1007
999
|
.sf-status::before {
|
|
1008
|
-
background: currentColor;
|
|
1000
|
+
background: var(--status-dot, currentColor);
|
|
1009
1001
|
block-size: .42em;
|
|
1010
1002
|
border-radius: var(--radius-full);
|
|
1011
1003
|
content: "";
|
|
@@ -1015,25 +1007,29 @@
|
|
|
1015
1007
|
.sf-status[data-tone="success"] {
|
|
1016
1008
|
--status-bg: var(--sf-colour-success-soft, oklch(62% 0.13 150 / 0.12));
|
|
1017
1009
|
--status-border: color-mix(in oklch, var(--sf-colour-success, oklch(62% 0.13 150)) 34%, transparent);
|
|
1018
|
-
--status-color:
|
|
1010
|
+
--status-color: var(--color-foreground);
|
|
1011
|
+
--status-dot: var(--sf-colour-success, oklch(62% 0.13 150));
|
|
1019
1012
|
}
|
|
1020
1013
|
|
|
1021
1014
|
.sf-status[data-tone="warning"] {
|
|
1022
1015
|
--status-bg: var(--sf-colour-warning-soft, oklch(78% 0.15 82 / 0.16));
|
|
1023
1016
|
--status-border: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 38%, transparent);
|
|
1024
|
-
--status-color:
|
|
1017
|
+
--status-color: var(--color-foreground);
|
|
1018
|
+
--status-dot: color-mix(in oklch, var(--sf-colour-warning, oklch(78% 0.15 82)) 60%, var(--color-foreground));
|
|
1025
1019
|
}
|
|
1026
1020
|
|
|
1027
1021
|
.sf-status[data-tone="danger"] {
|
|
1028
1022
|
--status-bg: var(--sf-colour-danger-soft, oklch(56% 0.2 28 / 0.12));
|
|
1029
1023
|
--status-border: color-mix(in oklch, var(--sf-colour-danger, var(--color-destructive)) 34%, transparent);
|
|
1030
|
-
--status-color:
|
|
1024
|
+
--status-color: var(--color-foreground);
|
|
1025
|
+
--status-dot: var(--sf-colour-danger, var(--color-destructive));
|
|
1031
1026
|
}
|
|
1032
1027
|
|
|
1033
1028
|
.sf-status[data-tone="info"] {
|
|
1034
1029
|
--status-bg: var(--sf-colour-info-soft, oklch(70% 0.12 205 / 0.14));
|
|
1035
1030
|
--status-border: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 34%, transparent);
|
|
1036
|
-
--status-color:
|
|
1031
|
+
--status-color: var(--color-foreground);
|
|
1032
|
+
--status-dot: color-mix(in oklch, var(--sf-colour-info, oklch(70% 0.12 205)) 68%, var(--color-foreground));
|
|
1037
1033
|
}
|
|
1038
1034
|
|
|
1039
1035
|
.sf-data-list {
|
|
@@ -1081,9 +1077,16 @@
|
|
|
1081
1077
|
background: var(--color-surface-alt);
|
|
1082
1078
|
}
|
|
1083
1079
|
|
|
1080
|
+
.sf-form {
|
|
1081
|
+
display: grid;
|
|
1082
|
+
gap: var(--form-gap, var(--space-s));
|
|
1083
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1084
1086
|
.sf-field {
|
|
1085
1087
|
display: grid;
|
|
1086
1088
|
gap: var(--space-2xs);
|
|
1089
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1087
1090
|
}
|
|
1088
1091
|
|
|
1089
1092
|
.sf-form[data-density="compact"],
|
|
@@ -1146,6 +1149,130 @@
|
|
|
1146
1149
|
outline: 0.125rem solid color-mix(in oklch, var(--color-ring) 24%, transparent);
|
|
1147
1150
|
outline-offset: 0;
|
|
1148
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
.sf-spinner {
|
|
1154
|
+
animation: sf-spinner-rotate .75s linear infinite;
|
|
1155
|
+
block-size: var(--spinner-size, 1em);
|
|
1156
|
+
border: .125em solid color-mix(in oklch, currentColor 28%, transparent);
|
|
1157
|
+
border-block-start-color: currentColor;
|
|
1158
|
+
border-radius: var(--radius-full);
|
|
1159
|
+
display: inline-block;
|
|
1160
|
+
flex: 0 0 auto;
|
|
1161
|
+
inline-size: var(--spinner-size, 1em);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
@keyframes sf-spinner-rotate { to { transform: rotate(1turn); } }
|
|
1165
|
+
|
|
1166
|
+
.sf-input-group {
|
|
1167
|
+
align-items: stretch;
|
|
1168
|
+
background: var(--color-card);
|
|
1169
|
+
border: 1px solid var(--color-border);
|
|
1170
|
+
border-radius: var(--input-radius, var(--radius-md));
|
|
1171
|
+
display: flex;
|
|
1172
|
+
inline-size: 100%;
|
|
1173
|
+
min-block-size: var(--input-block-size, 2.75rem);
|
|
1174
|
+
overflow: clip;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.sf-input-group:focus-within {
|
|
1178
|
+
border-color: var(--color-ring);
|
|
1179
|
+
outline: .125rem solid color-mix(in oklch, var(--color-ring) 24%, transparent);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.sf-input-group > .sf-input { background: transparent; border: 0; border-radius: 0; flex: 1 1 0%; inline-size: auto; min-inline-size: 0; outline: 0; }
|
|
1183
|
+
.sf-input-group__leading,
|
|
1184
|
+
.sf-input-group__trailing { align-items: center; color: var(--color-muted-foreground); display: inline-flex; flex: 0 0 auto; padding-inline: var(--space-xs); white-space: nowrap; }
|
|
1185
|
+
.sf-input-group__leading { border-inline-end: 1px solid var(--color-border); }
|
|
1186
|
+
.sf-input-group__trailing,
|
|
1187
|
+
.sf-input-group__action { border-inline-start: 1px solid var(--color-border); }
|
|
1188
|
+
.sf-input-group__action { background: transparent; border-block: 0; border-inline-end: 0; color: var(--color-foreground); font-weight: 700; padding-inline: var(--space-xs); }
|
|
1189
|
+
.sf-input-group[data-invalid="true"],
|
|
1190
|
+
.sf-field[data-invalid="true"] .sf-input-group { border-color: var(--color-destructive, var(--color-danger)); }
|
|
1191
|
+
.sf-input-group:has(:disabled) { background: var(--color-surface-alt); opacity: .72; }
|
|
1192
|
+
|
|
1193
|
+
.sf-file-input {
|
|
1194
|
+
background: var(--color-card);
|
|
1195
|
+
border: 1px solid var(--color-border);
|
|
1196
|
+
border-radius: var(--input-radius, var(--radius-md));
|
|
1197
|
+
color: var(--color-muted-foreground);
|
|
1198
|
+
inline-size: 100%;
|
|
1199
|
+
min-block-size: var(--input-block-size, 2.75rem);
|
|
1200
|
+
padding: var(--space-3xs);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.sf-file-input::file-selector-button { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-foreground); font: inherit; font-weight: 700; margin-inline-end: var(--space-xs); min-block-size: 2.25rem; padding-inline: var(--space-xs); }
|
|
1204
|
+
.sf-range { accent-color: var(--color-primary); inline-size: 100%; min-block-size: var(--input-block-size, 2.75rem); }
|
|
1205
|
+
.sf-range:focus-visible,
|
|
1206
|
+
.sf-file-input:focus-visible { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1207
|
+
.sf-file-input[aria-invalid="true"],
|
|
1208
|
+
.sf-field[data-invalid="true"] .sf-file-input { border-color: var(--color-destructive, var(--color-danger)); }
|
|
1209
|
+
:where(.sf-file-input, .sf-range):disabled { opacity: .55; }
|
|
1210
|
+
|
|
1211
|
+
.sf-switch { align-items: center; display: inline-grid; gap: var(--space-xs); grid-template-columns: auto 1fr; position: relative; }
|
|
1212
|
+
.sf-switch > input[type="checkbox"],
|
|
1213
|
+
.sf-segmented-control__option > input[type="radio"] { block-size: .0625rem; clip: rect(0 0 0 0); clip-path: inset(50%); inline-size: .0625rem; overflow: hidden; position: absolute; white-space: nowrap; }
|
|
1214
|
+
.sf-switch__control { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); block-size: 1.5rem; display: inline-flex; inline-size: 2.75rem; padding: .1875rem; }
|
|
1215
|
+
.sf-switch__control::before { background: var(--color-foreground); border-radius: var(--radius-full); content: ""; inline-size: 1rem; transition: transform var(--duration-fast) var(--ease-standard); }
|
|
1216
|
+
.sf-switch > input:checked + .sf-switch__control { background: var(--color-primary); border-color: var(--color-primary); }
|
|
1217
|
+
.sf-switch > input:checked + .sf-switch__control::before { background: var(--color-primary-foreground); transform: translateX(1.1875rem); }
|
|
1218
|
+
.sf-switch > input:focus-visible + .sf-switch__control { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1219
|
+
.sf-switch__label { color: var(--color-foreground); font-weight: 700; }
|
|
1220
|
+
.sf-switch:has(input:disabled) { cursor: not-allowed; opacity: .55; }
|
|
1221
|
+
|
|
1222
|
+
.sf-segmented-control { background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-md); display: inline-flex; flex-wrap: wrap; gap: var(--space-3xs); padding: var(--space-3xs); }
|
|
1223
|
+
.sf-segmented-control__option { cursor: pointer; position: relative; }
|
|
1224
|
+
.sf-segmented-control__label { align-items: center; border: 1px solid transparent; border-radius: var(--radius-sm); color: var(--color-muted-foreground); display: inline-flex; font-size: var(--step--1); font-weight: 700; min-block-size: 2.25rem; padding-inline: var(--space-xs); }
|
|
1225
|
+
.sf-segmented-control__option > input:checked + .sf-segmented-control__label { background: var(--color-card); border-color: var(--color-border); color: var(--color-foreground); }
|
|
1226
|
+
.sf-segmented-control__option > input:focus + .sf-segmented-control__label { outline: .125rem solid var(--color-ring); outline-offset: .125rem; }
|
|
1227
|
+
.sf-segmented-control__option:has(input:disabled) { cursor: not-allowed; opacity: .55; }
|
|
1228
|
+
|
|
1229
|
+
.sf-combobox { position: relative; }
|
|
1230
|
+
.sf-combobox__listbox { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); display: grid; gap: var(--space-3xs); inset-block-start: calc(100% + var(--space-3xs)); inset-inline: 0; list-style: none; max-block-size: min(18rem, 50dvh); overflow: auto; padding: var(--space-2xs); position: absolute; z-index: 40; }
|
|
1231
|
+
.sf-combobox__option { border-radius: var(--radius-sm); cursor: pointer; padding: var(--space-2xs) var(--space-xs); }
|
|
1232
|
+
.sf-combobox__option:hover,
|
|
1233
|
+
.sf-combobox__option[aria-selected="true"],
|
|
1234
|
+
.sf-combobox__option[data-active="true"] { background: var(--color-surface-alt); }
|
|
1235
|
+
.sf-combobox__empty { color: var(--color-muted-foreground); padding: var(--space-xs); }
|
|
1236
|
+
|
|
1237
|
+
.sf-bulk-actions { align-items: center; background: color-mix(in oklch, var(--color-primary) 12%, transparent); border: 1px solid color-mix(in oklch, var(--color-primary) 30%, var(--color-border)); border-radius: var(--radius-md); display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: space-between; padding: var(--space-xs); }
|
|
1238
|
+
.sf-bulk-actions__count { font-size: var(--step--1); font-weight: 800; }
|
|
1239
|
+
.sf-bulk-actions__controls,
|
|
1240
|
+
.sf-pagination__controls,
|
|
1241
|
+
.sf-pagination__pages,
|
|
1242
|
+
.sf-pagination__jump { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-2xs); }
|
|
1243
|
+
|
|
1244
|
+
.sf-pagination { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-2xs); list-style: none; padding-inline-start: 0; }
|
|
1245
|
+
.sf-pagination[data-layout="complete"] { justify-content: space-between; row-gap: var(--space-xs); }
|
|
1246
|
+
.sf-pagination__summary,
|
|
1247
|
+
.sf-pagination__jump { color: var(--color-muted-foreground); font-size: var(--step--1); }
|
|
1248
|
+
.sf-pagination__jump .sf-input { inline-size: 5rem; }
|
|
1249
|
+
|
|
1250
|
+
.sf-stepper { counter-reset: sf-step; display: grid; gap: var(--space-xs); list-style: none; padding-inline-start: 0; }
|
|
1251
|
+
.sf-stepper__step { align-items: center; color: var(--color-muted-foreground); counter-increment: sf-step; display: grid; gap: var(--space-xs); grid-template-columns: auto 1fr; }
|
|
1252
|
+
.sf-stepper__marker { align-items: center; border: 1px solid var(--color-border); border-radius: var(--radius-full); block-size: 2rem; display: inline-flex; font-size: var(--step--1); font-weight: 800; inline-size: 2rem; justify-content: center; }
|
|
1253
|
+
.sf-stepper__marker:empty::before { content: counter(sf-step); }
|
|
1254
|
+
.sf-stepper__step[aria-current="step"] { color: var(--color-foreground); }
|
|
1255
|
+
.sf-stepper__step[aria-current="step"] .sf-stepper__marker { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-primary-foreground); }
|
|
1256
|
+
.sf-stepper__label { font-weight: 700; }
|
|
1257
|
+
.sf-wizard-actions { align-items: center; display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: space-between; }
|
|
1258
|
+
|
|
1259
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1260
|
+
.sf-spinner { animation: none; border-color: currentColor; border-inline-end-color: transparent; }
|
|
1261
|
+
.sf-switch__control::before { transition: none; }
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
@media (forced-colors: active) {
|
|
1265
|
+
:where(.sf-input-group, .sf-file-input, .sf-combobox__listbox, .sf-bulk-actions) { border-color: ButtonText; forced-color-adjust: auto; }
|
|
1266
|
+
:where(.sf-switch__control, .sf-segmented-control__label, .sf-stepper__marker) { background: Canvas; border-color: ButtonText; color: ButtonText; forced-color-adjust: none; }
|
|
1267
|
+
.sf-switch > input:focus-visible + .sf-switch__control,
|
|
1268
|
+
.sf-segmented-control__option > input:focus + .sf-segmented-control__label { outline: .125rem solid Highlight; outline-offset: .125rem; }
|
|
1269
|
+
.sf-switch > input:checked + .sf-switch__control,
|
|
1270
|
+
.sf-segmented-control__option > input:checked + .sf-segmented-control__label,
|
|
1271
|
+
.sf-stepper__step[aria-current="step"] .sf-stepper__marker { background: Highlight; border-color: Highlight; color: HighlightText; }
|
|
1272
|
+
.sf-switch__control::before { background: ButtonText; }
|
|
1273
|
+
.sf-switch > input:checked + .sf-switch__control::before { background: HighlightText; }
|
|
1274
|
+
.sf-stepper__step[data-complete="true"] .sf-stepper__marker { background: Canvas; border-color: LinkText; color: LinkText; }
|
|
1275
|
+
}
|
|
1149
1276
|
}
|
|
1150
1277
|
|
|
1151
1278
|
@layer utilities {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncedco/flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI-friendly fluid CSS design system for brand-consistent websites and agency builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.2.2",
|
|
@@ -77,16 +77,27 @@
|
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
79
79
|
"build:css": "node scripts/build-css.mjs",
|
|
80
|
-
"
|
|
80
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
81
|
+
"check": "pnpm check:generated && tsc --noEmit && node scripts/guardrails.mjs",
|
|
82
|
+
"check:generated": "pnpm build:types && node scripts/check-generated-css.mjs",
|
|
81
83
|
"guardrails": "node scripts/guardrails.mjs",
|
|
82
84
|
"clean": "rm -rf dist",
|
|
83
85
|
"prepack": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
84
86
|
"prepare": "node scripts/build-css.mjs && tsc -p tsconfig.build.json",
|
|
85
|
-
"test": "pnpm build &&
|
|
87
|
+
"test": "pnpm check:generated && pnpm build && pnpm test:unit",
|
|
88
|
+
"test:unit": "node --test tests/*.test.mjs",
|
|
89
|
+
"test:browser": "playwright test --project=chromium",
|
|
90
|
+
"test:browser:all": "playwright test",
|
|
86
91
|
"type-check": "tsc --noEmit"
|
|
87
92
|
},
|
|
88
93
|
"devDependencies": {
|
|
94
|
+
"@axe-core/playwright": "^4.13.0",
|
|
95
|
+
"@playwright/test": "^1.63.0",
|
|
89
96
|
"@types/node": "^22.15.21",
|
|
97
|
+
"@types/react": "^19.2.18",
|
|
98
|
+
"@types/react-test-renderer": "19.1.0",
|
|
99
|
+
"react": "^19.2.8",
|
|
100
|
+
"react-test-renderer": "19.2.8",
|
|
90
101
|
"typescript": "^5.8.3"
|
|
91
102
|
},
|
|
92
103
|
"engines": {
|
|
@@ -91,14 +91,24 @@ intact. Leave out `utilities.css` unless static helpers such as `sf-text-*`,
|
|
|
91
91
|
`sf-app-backdrop`, `sf-frame`, and `sf-flow`.
|
|
92
92
|
- Prefer component primitives for common UI: `sf-button`, `sf-card`,
|
|
93
93
|
`sf-badge`, `sf-status`, `sf-field`, `sf-input`, `sf-search`,
|
|
94
|
-
`sf-
|
|
94
|
+
`sf-input-group`, `sf-file-input`, `sf-range`, `sf-switch`,
|
|
95
|
+
`sf-segmented-control`, `sf-spinner`, `sf-data-table`, `sf-table-sort`,
|
|
96
|
+
`sf-tab__count`, and `sf-toast-stack`.
|
|
95
97
|
- Use `saas-landing` for public SaaS marketing pages. Use `saas-dashboard` when
|
|
96
98
|
a full authenticated starter is useful, but prefer neutral app patterns for
|
|
97
99
|
reusable UI in admin panels, portals, CRMs, analytics dashboards, internal
|
|
98
100
|
tools, account areas, and product workspaces.
|
|
99
101
|
- Use app patterns before writing project CSS:
|
|
100
102
|
`app-shell-layout`, `row-action-menu`, `tabs-with-counts`,
|
|
101
|
-
`data-table-actions`, `search-field`, `modal-form`,
|
|
103
|
+
`data-table-actions`, `search-field`, `modal-form`, `toast-stack`,
|
|
104
|
+
`searchable-select`, `bulk-actions-toolbar`, `complete-pagination`, and
|
|
105
|
+
`multi-step-form`.
|
|
106
|
+
- Use `switch-control` for independent binary settings and
|
|
107
|
+
`segmented-control` for one choice from a small set. Do not use either as a
|
|
108
|
+
substitute for tabs.
|
|
109
|
+
- Treat `searchable-select`, async loading, bulk selection, and multi-step
|
|
110
|
+
navigation as app-owned behavior. Flow supplies presentation and accessible
|
|
111
|
+
markup guidance, not JavaScript state machines.
|
|
102
112
|
- Use app shell states instead of bespoke layout CSS: `data-layout="fixed-header"`
|
|
103
113
|
for fixed app headers, `data-collapsed="true"` for collapsed sidebars, and
|
|
104
114
|
`data-mobile="drawer"` with `data-sidebar-open="true"` for mobile drawer
|
|
@@ -160,9 +170,16 @@ pnpm flow:doctor
|
|
|
160
170
|
For package or repo changes, also run:
|
|
161
171
|
|
|
162
172
|
```bash
|
|
173
|
+
pnpm check:generated
|
|
163
174
|
pnpm check
|
|
164
175
|
pnpm test
|
|
176
|
+
pnpm test:browser
|
|
177
|
+
pnpm test:browser:all
|
|
165
178
|
pnpm pack --dry-run
|
|
166
179
|
```
|
|
167
180
|
|
|
168
|
-
|
|
181
|
+
Run `pnpm check:generated` before a build or lifecycle command can refresh
|
|
182
|
+
tracked CSS. It covers package CSS and the plain HTML, Vite, Next, Astro, and
|
|
183
|
+
WordPress generated outputs. Use the Chromium browser gate during development
|
|
184
|
+
and all three browser engines before a release. If `doctor` warns about stale
|
|
185
|
+
project CSS, run `pnpm flow:build`.
|
package/src/index.ts
CHANGED
|
@@ -48,10 +48,79 @@ export const fluidSystem = {
|
|
|
48
48
|
},
|
|
49
49
|
components: {
|
|
50
50
|
button: 'sf-button',
|
|
51
|
+
buttonGroup: 'sf-button-group',
|
|
52
|
+
icon: 'sf-icon',
|
|
53
|
+
iconButton: 'sf-icon-button',
|
|
51
54
|
card: 'sf-card',
|
|
55
|
+
surface: 'sf-surface',
|
|
56
|
+
hero: 'sf-hero',
|
|
57
|
+
logoCloud: 'sf-logo-cloud',
|
|
58
|
+
feature: 'sf-feature',
|
|
59
|
+
stats: 'sf-stats',
|
|
60
|
+
testimonial: 'sf-testimonial',
|
|
61
|
+
pricingGrid: 'sf-pricing-grid',
|
|
62
|
+
faq: 'sf-faq',
|
|
63
|
+
callToAction: 'sf-cta',
|
|
64
|
+
footer: 'sf-footer',
|
|
52
65
|
badge: 'sf-badge',
|
|
66
|
+
avatar: 'sf-avatar',
|
|
67
|
+
alert: 'sf-alert',
|
|
68
|
+
status: 'sf-status',
|
|
69
|
+
sectionHeader: 'sf-section-header',
|
|
70
|
+
kicker: 'sf-kicker',
|
|
71
|
+
nav: 'sf-nav',
|
|
72
|
+
form: 'sf-form',
|
|
73
|
+
fieldset: 'sf-fieldset',
|
|
53
74
|
field: 'sf-field',
|
|
75
|
+
label: 'sf-label',
|
|
76
|
+
help: 'sf-help',
|
|
77
|
+
error: 'sf-error',
|
|
54
78
|
input: 'sf-input',
|
|
79
|
+
inputGroup: 'sf-input-group',
|
|
80
|
+
select: 'sf-select',
|
|
81
|
+
textarea: 'sf-textarea',
|
|
82
|
+
check: 'sf-check',
|
|
83
|
+
switch: 'sf-switch',
|
|
84
|
+
fileInput: 'sf-file-input',
|
|
85
|
+
range: 'sf-range',
|
|
86
|
+
segmentedControl: 'sf-segmented-control',
|
|
87
|
+
spinner: 'sf-spinner',
|
|
88
|
+
search: 'sf-search',
|
|
89
|
+
combobox: 'sf-combobox',
|
|
90
|
+
filterBar: 'sf-filter-bar',
|
|
91
|
+
bulkActions: 'sf-bulk-actions',
|
|
92
|
+
dataTable: 'sf-data-table',
|
|
93
|
+
tableSort: 'sf-table-sort',
|
|
94
|
+
dataList: 'sf-data-list',
|
|
95
|
+
emptyState: 'sf-empty-state',
|
|
96
|
+
settingsSection: 'sf-settings-section',
|
|
97
|
+
detailPanel: 'sf-detail-panel',
|
|
98
|
+
dialog: 'sf-dialog',
|
|
99
|
+
popover: 'sf-popover',
|
|
100
|
+
drawer: 'sf-drawer',
|
|
101
|
+
tooltip: 'sf-tooltip',
|
|
102
|
+
toast: 'sf-toast',
|
|
103
|
+
toastStack: 'sf-toast-stack',
|
|
104
|
+
banner: 'sf-banner',
|
|
105
|
+
disclosure: 'sf-disclosure',
|
|
106
|
+
accordion: 'sf-accordion',
|
|
107
|
+
tabs: 'sf-tabs',
|
|
108
|
+
tab: 'sf-tab',
|
|
109
|
+
menu: 'sf-menu',
|
|
110
|
+
breadcrumb: 'sf-breadcrumb',
|
|
111
|
+
pagination: 'sf-pagination',
|
|
112
|
+
stepper: 'sf-stepper',
|
|
113
|
+
wizardActions: 'sf-wizard-actions',
|
|
114
|
+
progress: 'sf-progress',
|
|
115
|
+
skeleton: 'sf-skeleton',
|
|
116
|
+
chart: 'sf-chart',
|
|
117
|
+
meter: 'sf-meter',
|
|
118
|
+
codeWindow: 'sf-code-window',
|
|
119
|
+
codeLines: 'sf-code-lines',
|
|
120
|
+
tokenStrip: 'sf-token-strip',
|
|
121
|
+
marquee: 'sf-marquee',
|
|
122
|
+
commandList: 'sf-command-list',
|
|
123
|
+
platformCard: 'sf-platform-card',
|
|
55
124
|
},
|
|
56
125
|
utilities: {
|
|
57
126
|
visuallyHidden: 'sf-visually-hidden',
|
package/src/presets.mjs
CHANGED
|
@@ -12,13 +12,13 @@ export const themePresets = {
|
|
|
12
12
|
surfaceAlt: 'oklch(96.4% 0.011 80)',
|
|
13
13
|
surfaceElevated: 'var(--sf-colour-surface-raised)',
|
|
14
14
|
border: 'oklch(18% 0.026 250 / 0.12)',
|
|
15
|
-
primary: 'oklch(
|
|
16
|
-
primaryHover: 'oklch(
|
|
15
|
+
primary: 'oklch(50% 0.16 40)',
|
|
16
|
+
primaryHover: 'oklch(44% 0.14 40)',
|
|
17
17
|
primaryForeground: 'oklch(100% 0 0)',
|
|
18
18
|
accent: 'oklch(70% 0.12 205)',
|
|
19
|
-
link: 'oklch(
|
|
20
|
-
linkHover: 'oklch(
|
|
21
|
-
ring: 'oklch(
|
|
19
|
+
link: 'oklch(50% 0.16 40)',
|
|
20
|
+
linkHover: 'oklch(44% 0.14 40)',
|
|
21
|
+
ring: 'oklch(50% 0.16 40)',
|
|
22
22
|
},
|
|
23
23
|
components: {
|
|
24
24
|
button: {
|