@dryui/ui 2.0.1 → 3.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/dist/accordion/accordion-button-trigger.svelte +1 -0
- package/dist/button/button.svelte +2 -1
- package/dist/color-picker/color-picker-channel-input.svelte +0 -1
- package/dist/combobox/combobox-content.svelte +0 -1
- package/dist/file-select/file-select-root.svelte +75 -15
- package/dist/file-select/file-select-root.svelte.d.ts +2 -0
- package/dist/file-upload/file-upload-item.svelte +0 -1
- package/dist/internal/modal-content.svelte +4 -6
- package/dist/internal/picker-popover-content.svelte +0 -1
- package/dist/kbd/kbd.svelte +1 -1
- package/dist/mega-menu/mega-menu-panel.svelte +1 -1
- package/dist/menubar/menubar-content.svelte +0 -1
- package/dist/menubar/menubar-root.svelte +0 -1
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +0 -1
- package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +7 -3
- package/dist/navigation-menu/navigation-menu-link.svelte +0 -1
- package/dist/option-picker/option-picker-preview.svelte +1 -0
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +0 -1
- package/dist/tags-input/tags-input-root.svelte +0 -1
- package/dist/toast/toast-root.svelte +0 -1
- package/dist/toolbar/toolbar-root.svelte +0 -1
- package/dist/tree/tree-item-label.svelte +0 -2
- package/dist/tree/tree-root.svelte +0 -1
- package/package.json +5 -5
- package/skills/dryui/SKILL.md +75 -53
- package/skills/dryui/rules/composition.md +3 -3
- package/skills/dryui/rules/compound-components.md +2 -2
- package/skills/dryui/rules/native-web-transitions.md +1 -1
- package/skills/dryui/rules/design.md +0 -71
|
@@ -179,6 +179,7 @@
|
|
|
179
179
|
--_dry-btn-color: var(--dry-btn-color, var(--_dry-btn-on-accent));
|
|
180
180
|
--_dry-btn-border: var(--dry-btn-border, transparent);
|
|
181
181
|
--_dry-btn-radius: var(--dry-btn-radius, var(--dry-radius-md));
|
|
182
|
+
--_dry-btn-active-transform: var(--dry-btn-active-transform, scale(0.98));
|
|
182
183
|
--_dry-btn-padding-x: var(--dry-btn-padding-x, var(--dry-space-4));
|
|
183
184
|
--_dry-btn-padding-y: var(--dry-btn-padding-y, var(--dry-space-2_5));
|
|
184
185
|
--_dry-btn-font-size: var(
|
|
@@ -236,7 +237,7 @@
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
&:active:not([data-disabled]) {
|
|
239
|
-
transform:
|
|
240
|
+
transform: var(--_dry-btn-active-transform);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
&[data-disabled] {
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
font-family: var(--dry-font-mono);
|
|
60
60
|
color: var(--dry-color-text-strong);
|
|
61
61
|
background: var(--dry-color-bg-raised);
|
|
62
|
-
/* dryui-allow solid-border-on-raised: numeric color channel inputs use field affordance inside a raised picker. */
|
|
63
62
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
64
63
|
border-radius: var(--dry-radius-md);
|
|
65
64
|
transition:
|
|
@@ -94,7 +94,6 @@
|
|
|
94
94
|
max-content
|
|
95
95
|
);
|
|
96
96
|
background: var(--dry-color-bg-overlay);
|
|
97
|
-
/* dryui-allow solid-border-on-raised: popover listbox keeps a crisp edge for option scanning. */
|
|
98
97
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
99
98
|
border-radius: var(--dry-radius-md);
|
|
100
99
|
box-shadow: var(--dry-shadow-lg);
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
value?: string | null;
|
|
7
|
+
accept?: string | undefined;
|
|
8
|
+
directory?: boolean | undefined;
|
|
7
9
|
onrequest?: (() => Promise<string | null>) | undefined;
|
|
8
10
|
onchange?: ((value: string | null) => void) | undefined;
|
|
9
11
|
disabled?: boolean | undefined;
|
|
@@ -12,6 +14,8 @@
|
|
|
12
14
|
|
|
13
15
|
let {
|
|
14
16
|
value = $bindable(null),
|
|
17
|
+
accept = '',
|
|
18
|
+
directory = true,
|
|
15
19
|
onrequest,
|
|
16
20
|
onchange,
|
|
17
21
|
disabled = false,
|
|
@@ -19,20 +23,56 @@
|
|
|
19
23
|
}: Props = $props();
|
|
20
24
|
|
|
21
25
|
let loading = $state(false);
|
|
26
|
+
let fallbackInputEl: HTMLInputElement | null = null;
|
|
27
|
+
|
|
28
|
+
function commitValue(nextValue: string | null) {
|
|
29
|
+
if (nextValue === null) return;
|
|
30
|
+
|
|
31
|
+
value = nextValue;
|
|
32
|
+
onchange?.(nextValue);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getFallbackInputValue(files: FileList | null): string | null {
|
|
36
|
+
const file = files?.[0];
|
|
37
|
+
if (!file) return null;
|
|
38
|
+
|
|
39
|
+
const directoryName = file.webkitRelativePath.split('/').find(Boolean);
|
|
40
|
+
return directory && directoryName ? directoryName : file.name;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function openFallbackInput() {
|
|
44
|
+
if (!fallbackInputEl) return;
|
|
45
|
+
|
|
46
|
+
fallbackInputEl.value = '';
|
|
47
|
+
fallbackInputEl.click();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function attachFallbackInput(node: HTMLInputElement) {
|
|
51
|
+
fallbackInputEl = node;
|
|
52
|
+
return () => {
|
|
53
|
+
if (fallbackInputEl === node) {
|
|
54
|
+
fallbackInputEl = null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
22
58
|
|
|
23
59
|
async function defaultRequest(): Promise<string | null> {
|
|
24
60
|
if (typeof window === 'undefined') return null;
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
if (directory && 'showDirectoryPicker' in window) {
|
|
62
|
+
try {
|
|
63
|
+
const handle = await (
|
|
64
|
+
window as unknown as {
|
|
65
|
+
showDirectoryPicker: (opts: { mode: string }) => Promise<{ name: string }>;
|
|
66
|
+
}
|
|
67
|
+
).showDirectoryPicker({ mode: 'read' });
|
|
68
|
+
return handle.name;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof DOMException && error.name === 'AbortError') return null;
|
|
71
|
+
}
|
|
35
72
|
}
|
|
73
|
+
|
|
74
|
+
openFallbackInput();
|
|
75
|
+
return null;
|
|
36
76
|
}
|
|
37
77
|
|
|
38
78
|
async function request() {
|
|
@@ -41,15 +81,17 @@
|
|
|
41
81
|
try {
|
|
42
82
|
const pick = onrequest ?? defaultRequest;
|
|
43
83
|
const result = await pick();
|
|
44
|
-
|
|
45
|
-
value = result;
|
|
46
|
-
onchange?.(result);
|
|
47
|
-
}
|
|
84
|
+
commitValue(result);
|
|
48
85
|
} finally {
|
|
49
86
|
loading = false;
|
|
50
87
|
}
|
|
51
88
|
}
|
|
52
89
|
|
|
90
|
+
function handleFallbackInputChange(event: Event & { currentTarget: HTMLInputElement }) {
|
|
91
|
+
commitValue(getFallbackInputValue(event.currentTarget.files));
|
|
92
|
+
event.currentTarget.value = '';
|
|
93
|
+
}
|
|
94
|
+
|
|
53
95
|
function clear() {
|
|
54
96
|
if (disabled) return;
|
|
55
97
|
value = null;
|
|
@@ -72,10 +114,29 @@
|
|
|
72
114
|
</script>
|
|
73
115
|
|
|
74
116
|
<div data-file-select data-disabled={disabled || undefined}>
|
|
117
|
+
<input
|
|
118
|
+
{@attach attachFallbackInput}
|
|
119
|
+
type="file"
|
|
120
|
+
{accept}
|
|
121
|
+
webkitdirectory={directory}
|
|
122
|
+
disabled={disabled || undefined}
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
tabindex="-1"
|
|
125
|
+
class="file-select-hidden-input"
|
|
126
|
+
onchange={handleFallbackInputChange}
|
|
127
|
+
/>
|
|
128
|
+
|
|
75
129
|
{@render children()}
|
|
76
130
|
</div>
|
|
77
131
|
|
|
78
132
|
<style>
|
|
133
|
+
.file-select-hidden-input {
|
|
134
|
+
display: none;
|
|
135
|
+
position: absolute;
|
|
136
|
+
height: 0;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
[data-file-select] {
|
|
80
141
|
container-type: inline-size;
|
|
81
142
|
display: grid;
|
|
@@ -85,7 +146,6 @@
|
|
|
85
146
|
padding: var(--dry-space-2) var(--dry-space-3);
|
|
86
147
|
font-family: var(--dry-font-sans);
|
|
87
148
|
background: var(--dry-color-bg-raised);
|
|
88
|
-
/* dryui-allow solid-border-on-raised: file select is a form control and needs a persistent field edge. */
|
|
89
149
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
90
150
|
border-radius: var(--dry-radius-md);
|
|
91
151
|
transition:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
interface Props {
|
|
3
3
|
value?: string | null;
|
|
4
|
+
accept?: string | undefined;
|
|
5
|
+
directory?: boolean | undefined;
|
|
4
6
|
onrequest?: (() => Promise<string | null>) | undefined;
|
|
5
7
|
onchange?: ((value: string | null) => void) | undefined;
|
|
6
8
|
disabled?: boolean | undefined;
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
font-family: var(--dry-font-sans);
|
|
28
28
|
color: var(--dry-color-text-strong);
|
|
29
29
|
background: var(--dry-color-bg-raised);
|
|
30
|
-
/* dryui-allow solid-border-on-raised: uploaded file rows need a visible item boundary in dense lists. */
|
|
31
30
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
32
31
|
border-radius: var(--dry-radius-md);
|
|
33
32
|
}
|
|
@@ -106,9 +106,9 @@
|
|
|
106
106
|
/* ---------- Dialog (center) ---------- */
|
|
107
107
|
|
|
108
108
|
[data-modal-content][data-variant='dialog'] {
|
|
109
|
-
/* dryui-allow width */
|
|
109
|
+
/* dryui-allow width: resets native <dialog> sizing so the outer grid track owns the centered panel width. */
|
|
110
110
|
width: 100vw;
|
|
111
|
-
/* dryui-allow width */
|
|
111
|
+
/* dryui-allow width: paired with the width reset above. */
|
|
112
112
|
max-width: none;
|
|
113
113
|
display: grid;
|
|
114
114
|
grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
|
|
@@ -172,9 +172,9 @@
|
|
|
172
172
|
/* ---------- AlertDialog (center) ---------- */
|
|
173
173
|
|
|
174
174
|
[data-modal-content][data-variant='alert-dialog'] {
|
|
175
|
-
/* dryui-allow width */
|
|
175
|
+
/* dryui-allow width: resets native <dialog> sizing so the outer grid track owns the centered panel width. */
|
|
176
176
|
width: 100vw;
|
|
177
|
-
/* dryui-allow width */
|
|
177
|
+
/* dryui-allow width: paired with the width reset above. */
|
|
178
178
|
max-width: none;
|
|
179
179
|
display: grid;
|
|
180
180
|
grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
|
|
@@ -319,7 +319,6 @@
|
|
|
319
319
|
|
|
320
320
|
[data-modal-content][data-variant='drawer'][data-side='top'] [data-modal-panel] {
|
|
321
321
|
--_drawer-rest-transform: translateY(0);
|
|
322
|
-
/* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a top drawer, not the exit animation. */
|
|
323
322
|
--_drawer-enter-transform: translateY(-100%);
|
|
324
323
|
grid-row: 1;
|
|
325
324
|
height: var(--dry-drawer-size);
|
|
@@ -328,7 +327,6 @@
|
|
|
328
327
|
|
|
329
328
|
[data-modal-content][data-variant='drawer'][data-side='bottom'] [data-modal-panel] {
|
|
330
329
|
--_drawer-rest-transform: translateY(0);
|
|
331
|
-
/* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a bottom drawer, not the exit animation. */
|
|
332
330
|
--_drawer-enter-transform: translateY(100%);
|
|
333
331
|
grid-row: 2;
|
|
334
332
|
height: var(--dry-drawer-size);
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
margin: 0;
|
|
83
83
|
display: inline-grid;
|
|
84
84
|
padding: var(--dry-space-3);
|
|
85
|
-
/* dryui-allow solid-border-on-raised: date picker popovers need a clear calendar boundary. */
|
|
86
85
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
87
86
|
border-radius: var(--dry-radius-lg);
|
|
88
87
|
background: var(--dry-color-bg-overlay);
|
package/dist/kbd/kbd.svelte
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
font-size: var(--dry-kbd-font-size);
|
|
46
46
|
font-weight: 600;
|
|
47
47
|
line-height: 1;
|
|
48
|
-
box-shadow: inset 0
|
|
48
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dry-color-text-strong) 10%, transparent);
|
|
49
49
|
user-select: none;
|
|
50
50
|
white-space: nowrap;
|
|
51
51
|
}
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
border-radius: var(--dry-radius-lg, 0.5rem);
|
|
118
118
|
box-shadow: var(--dry-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
|
|
119
119
|
padding: var(--dry-space-4, 1rem);
|
|
120
|
-
/* dryui-allow width */
|
|
120
|
+
/* dryui-allow width: viewport cap for the anchored floating panel, not a child-layout sizing rule. */
|
|
121
121
|
max-inline-size: var(--dry-mega-menu-panel-max-width, min(92vw, 60rem));
|
|
122
122
|
display: grid;
|
|
123
123
|
grid-auto-flow: column;
|
|
@@ -123,7 +123,6 @@
|
|
|
123
123
|
|
|
124
124
|
background: var(--dry-color-bg-overlay);
|
|
125
125
|
color: var(--dry-color-text-strong);
|
|
126
|
-
/* dryui-allow solid-border-on-raised: menu popover keeps a visible edge while floating above varied content. */
|
|
127
126
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
128
127
|
border-radius: var(--dry-radius-popover);
|
|
129
128
|
box-shadow: var(--dry-shadow-overlay);
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
gap: var(--dry-space-1);
|
|
83
83
|
padding: var(--dry-space-1);
|
|
84
84
|
background: var(--dry-color-bg-overlay);
|
|
85
|
-
/* dryui-allow solid-border-on-raised: menubar root presents a persistent command strip boundary. */
|
|
86
85
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
87
86
|
border-radius: var(--dry-radius-lg);
|
|
88
87
|
}
|
|
@@ -87,7 +87,6 @@
|
|
|
87
87
|
margin: 0;
|
|
88
88
|
grid-template-columns: minmax(anchor-size(inline), max-content);
|
|
89
89
|
background: var(--dry-color-bg-overlay);
|
|
90
|
-
/* dryui-allow solid-border-on-raised: popover menu keeps an explicit edge for contrast against busy app surfaces. */
|
|
91
90
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
92
91
|
border-radius: var(--dry-radius-md);
|
|
93
92
|
box-shadow: var(--dry-shadow-lg);
|
|
@@ -82,11 +82,16 @@
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function selectValue(itemValue: string) {
|
|
85
|
-
if (disabled ||
|
|
85
|
+
if (disabled || !canSelectValue(itemValue)) {
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
if (value.includes(itemValue)) {
|
|
90
|
+
setSelectedValues(value.filter((entry) => entry !== itemValue));
|
|
91
|
+
} else {
|
|
92
|
+
setSelectedValues([...value, itemValue]);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
setQueryValue('');
|
|
91
96
|
activeItemId = '';
|
|
92
97
|
focusInput();
|
|
@@ -251,7 +256,6 @@
|
|
|
251
256
|
justify-content: start;
|
|
252
257
|
gap: var(--dry-space-1_5);
|
|
253
258
|
padding: var(--dry-space-2);
|
|
254
|
-
/* dryui-allow solid-border-on-raised: composite input needs a persistent field edge plus raised fill for token contrast. */
|
|
255
259
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
256
260
|
border-radius: var(--dry-radius-md);
|
|
257
261
|
background: var(--dry-color-bg-raised);
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
var(--_preset-color) 58%,
|
|
88
88
|
color-mix(in srgb, black 12%, var(--_preset-color) 88%) 100%
|
|
89
89
|
);
|
|
90
|
+
/* dryui-allow inset-shadow: 1px top highlight + 1px bottom shadow give the preset color swatch a beveled material look. */
|
|
90
91
|
box-shadow:
|
|
91
92
|
inset 0 1px 0 color-mix(in srgb, white 22%, transparent),
|
|
92
93
|
inset 0 -1px 0 color-mix(in srgb, black 10%, transparent);
|
|
@@ -516,7 +516,6 @@
|
|
|
516
516
|
gap: var(--dry-space-2);
|
|
517
517
|
padding: var(--dry-space-2);
|
|
518
518
|
background: var(--dry-color-bg-overlay);
|
|
519
|
-
/* dryui-allow solid-border-on-raised: inline link editor is a small popover that needs a distinct edge. */
|
|
520
519
|
border: 1px solid var(--dry-rte-border);
|
|
521
520
|
border-radius: var(--dry-radius-md);
|
|
522
521
|
box-shadow: var(--dry-shadow-md);
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
[data-part='root'] {
|
|
87
87
|
display: block;
|
|
88
88
|
padding: var(--dry-space-2);
|
|
89
|
-
/* dryui-allow solid-border-on-raised: tag editor is a form field with token chips on a raised input surface. */
|
|
90
89
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
91
90
|
border-radius: var(--dry-radius-md);
|
|
92
91
|
background: var(--dry-color-bg-raised);
|
|
@@ -89,7 +89,6 @@
|
|
|
89
89
|
gap: var(--dry-space-1);
|
|
90
90
|
padding: var(--dry-space-1);
|
|
91
91
|
background: var(--dry-color-bg-overlay);
|
|
92
|
-
/* dryui-allow solid-border-on-raised: toolbar chrome needs a visible control group boundary. */
|
|
93
92
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
94
93
|
border-radius: var(--dry-radius-lg);
|
|
95
94
|
}
|
|
@@ -51,8 +51,6 @@
|
|
|
51
51
|
[data-part='label'][data-selected] {
|
|
52
52
|
background: var(--dry-tree-item-selected-bg, var(--dry-color-fill-brand-weak));
|
|
53
53
|
color: var(--dry-tree-item-selected-color, var(--dry-color-text-brand));
|
|
54
|
-
box-shadow: inset 2px 0 0
|
|
55
|
-
var(--dry-tree-item-selected-indicator, var(--dry-color-stroke-selected));
|
|
56
54
|
font-weight: 600;
|
|
57
55
|
}
|
|
58
56
|
</style>
|
|
@@ -238,7 +238,6 @@
|
|
|
238
238
|
--dry-tree-item-hover-bg: var(--dry-color-fill);
|
|
239
239
|
--dry-tree-item-selected-bg: var(--dry-color-fill-brand-weak);
|
|
240
240
|
--dry-tree-item-selected-color: var(--dry-color-text-brand);
|
|
241
|
-
--dry-tree-item-selected-indicator: var(--dry-color-stroke-selected);
|
|
242
241
|
--dry-tree-icon-size: 1rem;
|
|
243
242
|
}
|
|
244
243
|
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dryui/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Zero-dependency styled Svelte 5 components with scoped styles and --dry-* CSS variable theming.",
|
|
5
5
|
"author": "Rob Balfre",
|
|
6
6
|
"license": "MIT",
|
|
@@ -817,14 +817,14 @@
|
|
|
817
817
|
"check:publish-hygiene": "bun run check:publint && bun run check:attw"
|
|
818
818
|
},
|
|
819
819
|
"dependencies": {
|
|
820
|
-
"@dryui/primitives": "^
|
|
820
|
+
"@dryui/primitives": "^3.0.0"
|
|
821
821
|
},
|
|
822
822
|
"peerDependencies": {
|
|
823
|
-
"svelte": "^5.55.
|
|
823
|
+
"svelte": "^5.55.5"
|
|
824
824
|
},
|
|
825
825
|
"devDependencies": {
|
|
826
|
-
"@dryui/lint": "^0.
|
|
827
|
-
"svelte": "^5.55.
|
|
826
|
+
"@dryui/lint": "^1.0.0",
|
|
827
|
+
"svelte": "^5.55.5",
|
|
828
828
|
"@sveltejs/package": "^2.5.7",
|
|
829
829
|
"svelte-check": "^4.4.6",
|
|
830
830
|
"typescript": "^6.0.3"
|
package/skills/dryui/SKILL.md
CHANGED
|
@@ -9,22 +9,47 @@ Zero-dependency Svelte 5 components. All imports from `@dryui/ui`. Requires a th
|
|
|
9
9
|
|
|
10
10
|
**Tradeoff:** These rules bias toward correctness over speed. For throwaway prototypes, use judgment.
|
|
11
11
|
|
|
12
|
+
## UI Creation Pipeline
|
|
13
|
+
|
|
14
|
+
DryUI work is explicit. Confirm contracts, build, then validate.
|
|
15
|
+
|
|
16
|
+
1. **User brief** — one line capturing what you are building and for whom.
|
|
17
|
+
2. **DryUI lookup/plan** — use `dryui ask` or MCP `ask` to confirm component contracts, tokens, recipes, and accessibility notes before choosing components.
|
|
18
|
+
3. **Implementation** — build with DryUI components, Svelte 5 runes, grid layout, `--dry-*` tokens, and accessible composition.
|
|
19
|
+
4. **Deterministic check** — run `dryui check [path]` or MCP `check` to catch contract drift, accessibility regressions, token drift, and CSS discipline violations.
|
|
20
|
+
|
|
21
|
+
## Design guidance, critique, polish
|
|
22
|
+
|
|
23
|
+
DryUI is zero-dependency components + tokens + contracts. It deliberately does NOT ship design opinion. For design-quality flows like brief, critique, polish, visual review, or anti-pattern detection, use [impeccable](https://impeccable.style), which is installed alongside DryUI by `dryui init` or via `npx impeccable skills install`.
|
|
24
|
+
|
|
25
|
+
Invoke from your AI harness:
|
|
26
|
+
|
|
27
|
+
- `/impeccable teach` — one-time: scaffold `PRODUCT.md` + `DESIGN.md`
|
|
28
|
+
- `/impeccable craft` — design-then-build a feature
|
|
29
|
+
- `/impeccable shape` — plan UX/UI before writing code
|
|
30
|
+
- `/impeccable critique <target>` — UX design review
|
|
31
|
+
- `/impeccable audit <target>` — a11y, performance, responsive checks
|
|
32
|
+
- `/impeccable polish <target>` — final pass before shipping
|
|
33
|
+
- Full catalog: https://impeccable.style/cheatsheet
|
|
34
|
+
|
|
35
|
+
`PRODUCT.md` and `DESIGN.md` at the project root are impeccable-owned. DryUI tools do not read or write them. Anti-pattern detection: `npx impeccable detect <path-or-url>`.
|
|
36
|
+
|
|
12
37
|
## 1. Look Up Before You Write
|
|
13
38
|
|
|
14
39
|
**Never guess a component API. Always verify first.**
|
|
15
40
|
|
|
16
|
-
- Call `dryui
|
|
17
|
-
- Component APIs vary
|
|
18
|
-
- Compound vs simple, required parts, available props — all differ per component
|
|
19
|
-
- If you skip the lookup, you'll write plausible-looking code that silently breaks
|
|
41
|
+
- Call `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"` before using any component for the first time. MCP `ask` is the equivalent surface.
|
|
42
|
+
- Component APIs vary. `bind:value`, `bind:open`, `bind:checked` are NOT interchangeable.
|
|
43
|
+
- Compound vs simple, required parts, available props — all differ per component.
|
|
44
|
+
- If you skip the lookup, you'll write plausible-looking code that silently breaks.
|
|
20
45
|
|
|
21
|
-
The test: can you point to a `dryui
|
|
46
|
+
The test: can you point to a `dryui ask` or MCP `ask` call for every component or pattern in your output?
|
|
22
47
|
|
|
23
48
|
## 2. Everything is Compound Until Proven Otherwise
|
|
24
49
|
|
|
25
50
|
**Use `.Root`. Always check.**
|
|
26
51
|
|
|
27
|
-
Most DryUI components are compound
|
|
52
|
+
Most DryUI components are compound. They require `<Card.Root>`, not `<Card>`. The bare name silently fails or renders wrong. Assume compound, verify with `ask --scope component`.
|
|
28
53
|
|
|
29
54
|
```svelte
|
|
30
55
|
<!-- Wrong -->
|
|
@@ -33,7 +58,7 @@ Most DryUI components are compound — they require `<Card.Root>`, not `<Card>`.
|
|
|
33
58
|
<Card.Root>content</Card.Root>
|
|
34
59
|
```
|
|
35
60
|
|
|
36
|
-
Compound components are tracked in the manifest at `packages/mcp/src/component-catalog.ts`. Verify with `
|
|
61
|
+
Compound components are tracked in the manifest at `packages/mcp/src/component-catalog.ts`. Verify with `ask --scope component` before you assume a bare name works, then use `.Root` and wrap the parts inside it.
|
|
37
62
|
|
|
38
63
|
The test: every compound component in your markup uses `.Root`, and its parts are wrapped inside it. See `rules/compound-components.md` for the parts reference.
|
|
39
64
|
|
|
@@ -41,11 +66,11 @@ The test: every compound component in your markup uses `.Root`, and its parts ar
|
|
|
41
66
|
|
|
42
67
|
**Import it. Use its tokens. Don't fight it.**
|
|
43
68
|
|
|
44
|
-
- Import `@dryui/ui/themes/default.css` (and `dark.css`) before any component use
|
|
45
|
-
- Use `--dry-color-*` and `--dry-space-*` tokens
|
|
46
|
-
- Don't add decorative CSS (gradients, shadows, colored borders)
|
|
47
|
-
- Override semantic tokens (Tier 2) in `:root`, not component tokens (Tier 3)
|
|
48
|
-
- Prefer `<html class="theme-auto"
|
|
69
|
+
- Import `@dryui/ui/themes/default.css` (and `dark.css`) before any component use.
|
|
70
|
+
- Use `--dry-color-*` and `--dry-space-*` tokens. Never hardcode colors or spacing.
|
|
71
|
+
- Don't add decorative CSS (gradients, shadows, colored borders). The theme handles appearance.
|
|
72
|
+
- Override semantic tokens (Tier 2) in `:root`, not component tokens (Tier 3).
|
|
73
|
+
- Prefer `<html class="theme-auto">`. Use `data-theme="light|dark"` only for explicit overrides.
|
|
49
74
|
|
|
50
75
|
```css
|
|
51
76
|
/* Wrong */
|
|
@@ -63,14 +88,16 @@ The test: every compound component in your markup uses `.Root`, and its parts ar
|
|
|
63
88
|
|
|
64
89
|
The test: does your CSS contain zero hex colors, zero `rgb()` values, and zero inline styles?
|
|
65
90
|
|
|
91
|
+
Theming precedence beats design opinion. If impeccable guidance conflicts with DryUI theme contracts, tokens, or accessibility rules, DryUI wins.
|
|
92
|
+
|
|
66
93
|
## 4. Grid for Layout. Container for Width. @container for Responsive.
|
|
67
94
|
|
|
68
95
|
**Nothing else.**
|
|
69
96
|
|
|
70
|
-
- All layout is `display: grid` with `--dry-space-*` tokens in scoped `<style
|
|
71
|
-
- `Container` (simple component, no `.Root`) for constrained content width
|
|
72
|
-
- Use `@container` queries for responsive sizing
|
|
73
|
-
- No flexbox. No inline styles. No `width`/`min-width`/`max-width` properties
|
|
97
|
+
- All layout is `display: grid` with `--dry-space-*` tokens in scoped `<style>`.
|
|
98
|
+
- `Container` (simple component, no `.Root`) for constrained content width.
|
|
99
|
+
- Use `@container` queries for responsive sizing. Never `@media` for layout breakpoints.
|
|
100
|
+
- No flexbox. No inline styles. No `width`/`min-width`/`max-width` properties.
|
|
74
101
|
|
|
75
102
|
```svelte
|
|
76
103
|
<div class="layout">...</div>
|
|
@@ -83,29 +110,29 @@ The test: does your CSS contain zero hex colors, zero `rgb()` values, and zero i
|
|
|
83
110
|
</style>
|
|
84
111
|
```
|
|
85
112
|
|
|
86
|
-
The test: grep your output for `display: flex`, `style=`, `@media
|
|
113
|
+
The test: grep your output for `display: flex`, `style=`, `@media`. All should return nothing.
|
|
87
114
|
|
|
88
115
|
## 4A. Escape Hatches Mean Stop.
|
|
89
116
|
|
|
90
117
|
**If lint or the compiler pushes you toward an escape hatch, the structure is usually wrong.**
|
|
91
118
|
|
|
92
|
-
- Never add `:global()`, `!important`, `all: unset`, `<svelte:element>`, or `<!-- svelte-ignore ... -->` just to make a selector or warning go away
|
|
93
|
-
- Never add `width`, `min-width`, `max-width`, `inline-size`, `min-inline-size`, or `max-inline-size` to solve layout pressure
|
|
94
|
-
- Never use raw native elements outside their canonical DryUI component directories just because composition feels inconvenient
|
|
95
|
-
- Never pass `class=` to DryUI components expecting it to style their internals
|
|
96
|
-
- When blocked, restructure the markup instead: add a local wrapper, split explicit `{#if}` branches, move sizing to parent grid tracks, or promote the pattern into the canonical component where the raw element belongs
|
|
97
|
-
- Treat `dryui/no-global`, `dryui/no-important`, `dryui/no-width`, `dryui/no-raw-native-element`, `dryui/no-css-ignore`, and `dryui/no-svelte-element` as design feedback, not obstacles to suppress
|
|
119
|
+
- Never add `:global()`, `!important`, `all: unset`, `<svelte:element>`, or `<!-- svelte-ignore ... -->` just to make a selector or warning go away.
|
|
120
|
+
- Never add `width`, `min-width`, `max-width`, `inline-size`, `min-inline-size`, or `max-inline-size` to solve layout pressure.
|
|
121
|
+
- Never use raw native elements outside their canonical DryUI component directories just because composition feels inconvenient.
|
|
122
|
+
- Never pass `class=` to DryUI components expecting it to style their internals. Use wrapper elements, component props, `data-*` attributes, or `--dry-*` tokens instead.
|
|
123
|
+
- When blocked, restructure the markup instead: add a local wrapper, split explicit `{#if}` branches, move sizing to parent grid tracks, or promote the pattern into the canonical component where the raw element belongs.
|
|
124
|
+
- Treat `dryui/no-global`, `dryui/no-important`, `dryui/no-width`, `dryui/no-raw-native-element`, `dryui/no-css-ignore`, and `dryui/no-svelte-element` as design feedback, not obstacles to suppress.
|
|
98
125
|
|
|
99
|
-
The test: grep your output for `:global(`, `!important`, `all: unset`, `svelte-ignore`, `svelte:element`, raw `<button`, raw `<input`, raw `<select`, raw `<dialog`, raw `<hr`, raw `<table`, and `width
|
|
126
|
+
The test: grep your output for `:global(`, `!important`, `all: unset`, `svelte-ignore`, `svelte:element`, raw `<button`, raw `<input`, raw `<select`, raw `<dialog`, raw `<hr`, raw `<table`, and `width:`. All should return nothing unless you are editing the canonical component that owns that native element.
|
|
100
127
|
|
|
101
128
|
## 5. Every Input Gets a Field.Root
|
|
102
129
|
|
|
103
130
|
**Accessibility isn't optional.**
|
|
104
131
|
|
|
105
|
-
- Wrap every form input in `Field.Root` with a `Label
|
|
106
|
-
- Use `AlertDialog` (not `Dialog`) for destructive confirmations
|
|
107
|
-
- Add `aria-label` to every icon-only button
|
|
108
|
-
- Use `type="submit"` on primary form action buttons
|
|
132
|
+
- Wrap every form input in `Field.Root` with a `Label`.
|
|
133
|
+
- Use `AlertDialog` (not `Dialog`) for destructive confirmations.
|
|
134
|
+
- Add `aria-label` to every icon-only button.
|
|
135
|
+
- Use `type="submit"` on primary form action buttons.
|
|
109
136
|
|
|
110
137
|
```svelte
|
|
111
138
|
<!-- Wrong -->
|
|
@@ -125,9 +152,9 @@ The test: every `<Input>`, `<Select.Root>`, `<Textarea>` is inside a `Field.Root
|
|
|
125
152
|
|
|
126
153
|
**If a DryUI component exists for it, use it.**
|
|
127
154
|
|
|
128
|
-
`DatePicker` not `<input type="date">`. `Select.Root` not `<select>`. `Dialog.Root` not `<dialog>`. `Separator` not `<hr>`. `Button` not `<button>`. DryUI components handle theming and accessibility automatically
|
|
155
|
+
`DatePicker` not `<input type="date">`. `Select.Root` not `<select>`. `Dialog.Root` not `<dialog>`. `Separator` not `<hr>`. `Button` not `<button>`. DryUI components handle theming and accessibility automatically. Native elements don't.
|
|
129
156
|
|
|
130
|
-
The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>`, `<hr>`, `<table
|
|
157
|
+
The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>`, `<hr>`, `<table>`. Each should be a DryUI component instead.
|
|
131
158
|
|
|
132
159
|
## 7. Ask the Svelte MCP for Svelte Questions
|
|
133
160
|
|
|
@@ -135,7 +162,7 @@ The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>
|
|
|
135
162
|
|
|
136
163
|
For Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`), snippets, SvelteKit load fns, `+page.server.ts` shape, form actions, and anything Svelte-syntax adjacent: call the official `svelte-autofixer` and `get-documentation` tools from `@sveltejs/mcp` before guessing from memory.
|
|
137
164
|
|
|
138
|
-
- `dryui setup --install` registers `@sveltejs/mcp` by default
|
|
165
|
+
- `dryui setup --install` registers `@sveltejs/mcp` by default. Pass `--no-svelte-mcp` to skip.
|
|
139
166
|
- If it's not registered, the fallback is the remote endpoint `https://mcp.svelte.dev/mcp` or a one-liner like `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`.
|
|
140
167
|
- Scope split: DryUI `ask`/`check` cover component APIs, theming, composition, and validation. Svelte MCP covers the runtime, compiler, and framework idioms.
|
|
141
168
|
|
|
@@ -159,13 +186,13 @@ dryui
|
|
|
159
186
|
|
|
160
187
|
```bash
|
|
161
188
|
dryui init # existing project
|
|
162
|
-
dryui init my-app # new project
|
|
189
|
+
dryui init my-app # new project, scaffolds SvelteKit + DryUI in one step
|
|
163
190
|
cd my-app && bun run dev
|
|
164
191
|
```
|
|
165
192
|
|
|
166
193
|
This works for greenfield (empty directory), brownfield (existing non-SvelteKit project), and existing SvelteKit projects. On existing projects, `dryui install` prints the ordered plan and `dryui detect` verifies that setup is complete.
|
|
167
194
|
|
|
168
|
-
> **No global install?** `bunx @dryui/cli <cmd>` and `npx -y @dryui/cli <cmd>` work anywhere without installing
|
|
195
|
+
> **No global install?** `bunx @dryui/cli <cmd>` and `npx -y @dryui/cli <cmd>` work anywhere without installing. Same commands, just slower (re-fetches on each call).
|
|
169
196
|
|
|
170
197
|
**4. Add the editor integration layer** after the CLI is working:
|
|
171
198
|
|
|
@@ -174,12 +201,12 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
174
201
|
- OpenCode: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .opencode/skills/dryui` + add the `dryui` and `dryui-feedback` local MCP servers in `opencode.json` (OpenCode also loads `.agents/skills/dryui` and reads `AGENTS.md`)
|
|
175
202
|
- Copilot/Cursor/Windsurf: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .agents/skills/dryui` + add MCP config (see https://dryui.dev/tools)
|
|
176
203
|
|
|
177
|
-
**5. Register the Svelte MCP companion.** `dryui setup --install` does this automatically for Copilot, Cursor, OpenCode, Windsurf, and Zed. For Claude Code run `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp
|
|
204
|
+
**5. Register the Svelte MCP companion.** `dryui setup --install` does this automatically for Copilot, Cursor, OpenCode, Windsurf, and Zed. For Claude Code run `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`. For Codex add `[mcp_servers.svelte] command = "npx", args = ["-y", "@sveltejs/mcp"]` to `~/.codex/config.toml`. See rule 7 above.
|
|
178
205
|
|
|
179
206
|
### Manual setup
|
|
180
207
|
|
|
181
208
|
1. `bun add @dryui/ui`
|
|
182
|
-
2. `bun add -d @dryui/lint
|
|
209
|
+
2. `bun add -d @dryui/lint`. Enforces grid-only layout, bans flexbox/inline-style/width at build time. Without this step the CSS discipline rules are not enforced at build time, and only post-write `check` / CLI validation remain.
|
|
183
210
|
3. Wire the lint preprocessor in `svelte.config.js` (add `dryuiLint` as the **first** item in the `preprocess` array):
|
|
184
211
|
|
|
185
212
|
```js
|
|
@@ -199,7 +226,7 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
199
226
|
export default config;
|
|
200
227
|
```
|
|
201
228
|
|
|
202
|
-
4. Add `class="theme-auto"` to `<html>` in `src/app.html
|
|
229
|
+
4. Add `class="theme-auto"` to `<html>` in `src/app.html`.
|
|
203
230
|
5. In root layout (`src/routes/+layout.svelte`), import themes:
|
|
204
231
|
```svelte
|
|
205
232
|
<script>
|
|
@@ -207,18 +234,18 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
207
234
|
import '@dryui/ui/themes/dark.css';
|
|
208
235
|
</script>
|
|
209
236
|
```
|
|
210
|
-
6. Import `app.css` AFTER theme CSS if you have custom styles
|
|
237
|
+
6. Import `app.css` AFTER theme CSS if you have custom styles.
|
|
211
238
|
|
|
212
|
-
> `dryui init` applies all six steps automatically
|
|
239
|
+
> `dryui init` applies all six steps automatically. Prefer it over manual setup when you can.
|
|
213
240
|
|
|
214
|
-
## Bindable Props
|
|
241
|
+
## Bindable Props, Common Confusion
|
|
215
242
|
|
|
216
|
-
Always verify with `
|
|
243
|
+
Always verify with `ask --scope component`, but these are the most common mistakes:
|
|
217
244
|
|
|
218
245
|
- `bind:value` (Input, Select, Tabs...) vs `bind:checked` (Checkbox, Switch) vs `bind:pressed` (Toggle) vs `bind:open` (Dialog, Popover, Drawer...)
|
|
219
|
-
- Select and Combobox support both `bind:value` and `bind:open
|
|
220
|
-
- ColorPicker also exposes `bind:alpha
|
|
221
|
-
- Tour uses `bind:active`, not `bind:open
|
|
246
|
+
- Select and Combobox support both `bind:value` and `bind:open`.
|
|
247
|
+
- ColorPicker also exposes `bind:alpha`. Transfer uses `bind:sourceItems` / `bind:targetItems`.
|
|
248
|
+
- Tour uses `bind:active`, not `bind:open`.
|
|
222
249
|
|
|
223
250
|
## Tools
|
|
224
251
|
|
|
@@ -226,9 +253,9 @@ Use these to look up APIs, discover components, plan setup, and validate code.
|
|
|
226
253
|
|
|
227
254
|
### Recommended workflow
|
|
228
255
|
|
|
229
|
-
1.
|
|
256
|
+
1. Resolve any component or recipe uncertainty with `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"`. If MCP is available, `ask --scope component` and `ask --scope recipe` are the equivalent surface.
|
|
230
257
|
2. Build the route or component with raw CSS grid, `Container` for constrained width, and `@container` for responsive layout.
|
|
231
|
-
3. Run `dryui check [path]` or MCP `check` after implementation to catch composition drift, layout violations,
|
|
258
|
+
3. Run `dryui check [path]` or MCP `check` after implementation to catch composition drift, layout violations, accessibility regressions, and token drift.
|
|
232
259
|
4. Never guess component shape from memory. DryUI is intentionally strict, and the lookup cost is lower than rework.
|
|
233
260
|
|
|
234
261
|
### CLI (default entry point)
|
|
@@ -239,12 +266,10 @@ Install once with `bun install -g @dryui/cli@latest` (or `npm install -g @dryui/
|
|
|
239
266
|
dryui # default onboarding entry point
|
|
240
267
|
dryui setup # explicit onboarding subcommand
|
|
241
268
|
dryui init [path] [--pm bun] # Bootstrap SvelteKit + DryUI project
|
|
242
|
-
dryui
|
|
243
|
-
dryui compose "date input" # Composition guidance
|
|
269
|
+
dryui ask <scope> "<query>" # Look up components, recipes, tokens, setup
|
|
244
270
|
dryui detect [path] # Check project setup
|
|
245
271
|
dryui install [path] # Print install plan
|
|
246
272
|
dryui check [path] # Validate file, theme, directory, or workspace
|
|
247
|
-
dryui check --visual <url> # Screenshot a URL and critique rendered polish
|
|
248
273
|
dryui list # List components
|
|
249
274
|
dryui tokens --category color # Browse design tokens
|
|
250
275
|
dryui ambient # SessionStart context
|
|
@@ -252,7 +277,7 @@ dryui install-hook --dry-run # Preview Claude hook wiring
|
|
|
252
277
|
dryui feedback init # Feedback tooling setup
|
|
253
278
|
```
|
|
254
279
|
|
|
255
|
-
Without a global install, prefix any command with `bunx @dryui/cli …` or `npx -y @dryui/cli
|
|
280
|
+
Without a global install, prefix any command with `bunx @dryui/cli …` or `npx -y @dryui/cli …`. Same behaviour, just slower (re-fetches on each call).
|
|
256
281
|
|
|
257
282
|
### MCP tools (same workflow in-editor)
|
|
258
283
|
|
|
@@ -262,7 +287,6 @@ Without a global install, prefix any command with `bunx @dryui/cli …` or `npx
|
|
|
262
287
|
| Lookup & composition | `ask --scope component`, `ask --scope recipe`, `ask --scope list` |
|
|
263
288
|
| Validation | `check <file.svelte>`, `check <theme.css>` |
|
|
264
289
|
| Audit | `check`, `check <directory>` |
|
|
265
|
-
| Rendered UI | `check` with `visualUrl`, or direct `check-vision` |
|
|
266
290
|
|
|
267
291
|
Categories: action, input, form, layout, navigation, overlay, display, feedback, interaction, utility
|
|
268
292
|
|
|
@@ -275,10 +299,8 @@ Read these when you need deeper guidance:
|
|
|
275
299
|
- **`rules/composition.md`** — Form patterns, page layouts, composition recipes
|
|
276
300
|
- **`rules/accessibility.md`** — Field.Root, ARIA, focus management, pre-ship checklist
|
|
277
301
|
- **`rules/svelte.md`** — Runes, snippets, native browser APIs, styling rules
|
|
278
|
-
- **`rules/design.md`** — Minimal code, no premature abstraction, naming conventions
|
|
279
|
-
- **`rules/visual-effects-performance.md`** — Tiered budgets and implementation rules for shader, blur, glass, and pointer-reactive effects
|
|
280
302
|
- **`rules/native-web-transitions.md`** — View Transition API, scroll animations, reduced-motion
|
|
281
303
|
|
|
282
304
|
---
|
|
283
305
|
|
|
284
|
-
**These rules are working if:** every component traces to a lookup, diffs contain zero hardcoded colors, and
|
|
306
|
+
**These rules are working if:** every component traces to a lookup, diffs contain zero hardcoded colors, and `dryui check` finds nothing.
|
|
@@ -398,7 +398,7 @@ Use Field.Error to show validation messages.
|
|
|
398
398
|
|
|
399
399
|
## Component Selection Quick Reference
|
|
400
400
|
|
|
401
|
-
Before using any component, call `dryui
|
|
401
|
+
Before using any component, call `dryui ask --scope recipe "<pattern>"` (for layouts) or `dryui ask --scope component "<Component>"` (for APIs) to get the correct component and usage snippet. This table is a quick reference. The CLI and MCP surfaces both return the fuller snippets and anti-patterns.
|
|
402
402
|
|
|
403
403
|
| UI Need | Use This | NOT This |
|
|
404
404
|
| ----------------- | -------------------------------------- | ---------------------------- |
|
|
@@ -431,7 +431,7 @@ Before using any component, call `dryui compose "<query>"` or `dryui info <Compo
|
|
|
431
431
|
|
|
432
432
|
## Composition Recipes
|
|
433
433
|
|
|
434
|
-
Call `dryui
|
|
434
|
+
Call `dryui ask --scope recipe "<recipe>"` with any recipe name to get a full working snippet.
|
|
435
435
|
|
|
436
436
|
| Recipe | Description | Key Components |
|
|
437
437
|
| ------------------------- | ------------------------- | -------------------------------------- |
|
|
@@ -457,7 +457,7 @@ DryUI is a presentation and accessibility system, not a workflow engine. For dep
|
|
|
457
457
|
- Normalize route/session state in script before rendering DryUI inputs.
|
|
458
458
|
- Reset dependent `Select.Root` values when their parent choice changes; do not rely on stale child state surviving domain changes.
|
|
459
459
|
- Use raw CSS grid to lay out planner sections, and keep orchestration logic in route-level stores or derived state.
|
|
460
|
-
- Run `dryui
|
|
460
|
+
- Run `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"` before introducing a new field shape, then run `dryui check [path]` or MCP `check` after the flow is wired.
|
|
461
461
|
|
|
462
462
|
```svelte
|
|
463
463
|
<script lang="ts">
|
|
@@ -18,7 +18,7 @@ Every compound component uses `.Root` as the container. Never use the bare name.
|
|
|
18
18
|
|
|
19
19
|
## Parts Reference
|
|
20
20
|
|
|
21
|
-
Below are the parts for the most commonly used compound components. Prefer `dryui
|
|
21
|
+
Below are the parts for the most commonly used compound components. Prefer `dryui ask --scope component "<name>"` for the full, up-to-date parts list.
|
|
22
22
|
|
|
23
23
|
### Card
|
|
24
24
|
|
|
@@ -307,6 +307,6 @@ Parts: Root, Input, Content, Item, Empty
|
|
|
307
307
|
|
|
308
308
|
## Full Compound Component List
|
|
309
309
|
|
|
310
|
-
Run `dryui
|
|
310
|
+
Run `dryui ask --scope component "<name>"` for any component's complete parts list:
|
|
311
311
|
|
|
312
312
|
Accordion, AlertDialog, Breadcrumb, Card, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, DataGrid, DatePicker, Dialog, DragAndDrop, Drawer, DropdownMenu, EmptyState, Field, FileUpload, FloatButton, Pagination, Popover, RadioGroup, RichTextEditor, Select, Splitter, Stepper, Table, Tabs, TagsInput, Toast, ToggleGroup, Toolbar, Tooltip, Tour, Transfer
|
|
@@ -80,7 +80,7 @@ Always include a reduced-motion override:
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
The `@media` block comes after the default rule in the same scoped stylesheet, so equal specificity plus source order handles the override without `!important`
|
|
83
|
+
The `@media` block comes after the default rule in the same scoped stylesheet, so equal specificity plus source order handles the override without `!important` (banned by `@dryui/lint` via `dryui/no-important`). In JS, skip delayed typewriter/replay steps and render the final state immediately when reduced motion is active.
|
|
84
84
|
|
|
85
85
|
## Svelte notes
|
|
86
86
|
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Clean Code Standards
|
|
2
|
-
|
|
3
|
-
## Core Principle
|
|
4
|
-
|
|
5
|
-
Write the minimum correct code. Every line must earn its place.
|
|
6
|
-
|
|
7
|
-
## Rules
|
|
8
|
-
|
|
9
|
-
### No Premature Abstraction
|
|
10
|
-
|
|
11
|
-
- Three similar lines > one premature helper
|
|
12
|
-
- Only extract when you have 3+ real call sites
|
|
13
|
-
- No "just in case" configurability, feature flags, or extension points
|
|
14
|
-
- Delete code you don't need -- don't comment it out
|
|
15
|
-
|
|
16
|
-
### No Noise
|
|
17
|
-
|
|
18
|
-
- No comments that restate what code does
|
|
19
|
-
- Comments only where the _why_ is non-obvious
|
|
20
|
-
- No empty catch blocks -- handle or rethrow
|
|
21
|
-
- No unused imports, variables, or parameters
|
|
22
|
-
- No `console.log` left in production code
|
|
23
|
-
- No commented-out code -- use git history
|
|
24
|
-
|
|
25
|
-
### Names Are Documentation
|
|
26
|
-
|
|
27
|
-
- Functions: verb + noun (`getUser`, `handleClick`, `parseDate`)
|
|
28
|
-
- Booleans: `is`/`has`/`should` prefix (`isOpen`, `hasError`)
|
|
29
|
-
- Collections: plural (`users`, `items`)
|
|
30
|
-
- Callbacks: `on` + event (`onClose`, `onChange`)
|
|
31
|
-
- Constants: UPPER_SNAKE only for true compile-time constants
|
|
32
|
-
|
|
33
|
-
### Functions
|
|
34
|
-
|
|
35
|
-
- One job per function
|
|
36
|
-
- Max 3 parameters -- use an options object for more
|
|
37
|
-
- Return early to avoid nesting
|
|
38
|
-
- Pure functions where possible -- side effects at the edges
|
|
39
|
-
|
|
40
|
-
### Error Handling
|
|
41
|
-
|
|
42
|
-
- Only validate at system boundaries (user input, API responses, file reads)
|
|
43
|
-
- Trust internal code -- don't null-check values you just created
|
|
44
|
-
- Use specific error types, not generic strings
|
|
45
|
-
- Handle errors at the level that can meaningfully respond
|
|
46
|
-
|
|
47
|
-
### File Organization
|
|
48
|
-
|
|
49
|
-
- One concept per file
|
|
50
|
-
- Keep files under 300 lines -- split if growing
|
|
51
|
-
- Colocate related code (component + styles + tests in same directory)
|
|
52
|
-
- Index files only for re-exports, never for logic
|
|
53
|
-
|
|
54
|
-
## Anti-Patterns -- Stop and Fix
|
|
55
|
-
|
|
56
|
-
| If you're about to... | Instead... |
|
|
57
|
-
| --------------------------------------- | ------------------------------------------------------------ |
|
|
58
|
-
| Add a "utils" file | Put it where it's used (shared module only at 3+ call sites) |
|
|
59
|
-
| Create a base class | Use composition |
|
|
60
|
-
| Add a config option nobody asked for | Don't |
|
|
61
|
-
| Write a comment explaining _what_ | Rename so it's obvious |
|
|
62
|
-
| Add error handling for impossible cases | Trust internal code |
|
|
63
|
-
| Create a wrapper around a simple API | Use the API directly |
|
|
64
|
-
|
|
65
|
-
## 5 Rules of Programming
|
|
66
|
-
|
|
67
|
-
1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places.
|
|
68
|
-
2. Measure. Don't tune for speed until you've measured.
|
|
69
|
-
3. Fancy algorithms are slow when n is small, and n is usually small.
|
|
70
|
-
4. Fancy algorithms are buggier than simple ones. Use simple algorithms and simple data structures.
|
|
71
|
-
5. Data dominates. If you've chosen the right data structures, the algorithms will be self-evident.
|