@scattered-light/loomiere 0.6.0 → 0.7.1
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/build/build-tokens.mjs +33 -6
- package/exporters/chakra.mjs +74 -0
- package/exporters/component-styles.mjs +64 -0
- package/exporters/design-system.mjs +23 -7
- package/exporters/mui.mjs +60 -0
- package/figma/library/pages/18-custom/01-custom.js +57 -0
- package/figma/library/pages/18-custom/02-labels.js +7 -0
- package/figma/library/pages/18-custom/03-doc.js +28 -0
- package/figma/library/pages/18-custom/04-date-picker.js +327 -0
- package/figma/library/pages/18-custom/05-date-picker-labels.js +9 -0
- package/figma/library/pages/18-custom/06-date-picker-doc.js +25 -0
- package/figma/library/pages/18-custom/07-money-field.js +107 -0
- package/figma/library/pages/18-custom/08-money-field-labels.js +8 -0
- package/figma/library/pages/18-custom/09-money-field-doc.js +101 -0
- package/figma/library/pages/18-custom/10-splitter.js +100 -0
- package/figma/library/pages/18-custom/11-splitter-labels.js +8 -0
- package/figma/library/pages/18-custom/12-splitter-doc.js +92 -0
- package/figma/library/pages/18-custom/13-empty-state.js +114 -0
- package/figma/library/pages/18-custom/14-empty-state-labels.js +8 -0
- package/figma/library/pages/18-custom/15-empty-state-doc.js +58 -0
- package/figma/library/pages/18-custom/16-collection.js +255 -0
- package/figma/library/pages/18-custom/17-collection-doc.js +43 -0
- package/figma/library/run-order.json +1 -0
- package/lib/contrast.mjs +17 -0
- package/package.json +1 -1
- package/tokens/README.md +10 -3
- package/tokens/component/badge.json +53 -0
- package/tokens/component/collection.json +53 -0
- package/tokens/component/list-detail.json +29 -0
- package/tokens/component/money-field.json +9 -0
- package/tokens/component/splitter.json +25 -0
- package/tokens/semantic/accessibility.json +4 -0
- package/tokens/semantic/calendar.json +75 -0
- package/tokens/semantic/empty.json +79 -0
- package/tokens/semantic/field.json +4 -0
- package/tokens/semantic/popup.json +2 -2
- package/tokens/semantic/table.json +8 -0
- package/tools/brand-builder/builder.js +45 -0
- package/tools/brand-builder/index.html +6 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Custom: Collection parts. Run after Empty state. Not named Custom, so it does not replace the Badge set.
|
|
2
|
+
var AF = Object.getPrototypeOf(async function () {}).constructor;
|
|
3
|
+
var C = await new AF('figma', figma.root.getSharedPluginData('loomiere', 'compHelpers'))(figma);
|
|
4
|
+
var fill = C.fill, bind = C.bind, add = C.add, T = C.T, page = C.page, comp = C.comp, textProp = C.textProp, place = C.place, removeOld = C.removeOld;
|
|
5
|
+
await page('Custom');
|
|
6
|
+
|
|
7
|
+
function frameSet(set, cols, colW, rowH) {
|
|
8
|
+
set.layoutMode = 'NONE';
|
|
9
|
+
set.children.forEach(function (c, i) { c.x = 24 + (i % cols) * colW; c.y = 24 + Math.floor(i / cols) * rowH; });
|
|
10
|
+
fill(set, 'Theme:surface/canvas');
|
|
11
|
+
set.strokes = [{ type: 'SOLID', color: { r: 0.592, g: 0.278, b: 1 } }];
|
|
12
|
+
set.dashPattern = [6, 4]; set.strokeWeight = 1; set.cornerRadius = 8; set.clipsContent = false;
|
|
13
|
+
set.resize(24 + cols * colW, 24 + Math.ceil(set.children.length / cols) * rowH);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function label(text, style, color, width) {
|
|
17
|
+
var node = await T(text, style, color, width);
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rowFill(state) {
|
|
22
|
+
if (state === 'hover') return 'Theme:table/row-hover';
|
|
23
|
+
if (state === 'selected') return 'Theme:table/row-selected';
|
|
24
|
+
if (state === 'current') return 'Theme:table/row-current';
|
|
25
|
+
return 'Theme:table/background';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hugHeight(node, token) {
|
|
29
|
+
node.counterAxisSizingMode = 'FIXED';
|
|
30
|
+
node.primaryAxisSizingMode = 'AUTO';
|
|
31
|
+
node.resize(8, 32);
|
|
32
|
+
bind(node, 'height', token);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function symbol(name) {
|
|
36
|
+
var node = await T(name, 'body-condense', 'Theme:table/subtle-color');
|
|
37
|
+
var fonts = await figma.listAvailableFontsAsync();
|
|
38
|
+
var font = null;
|
|
39
|
+
for (var i = 0; i < fonts.length; i++) if (fonts[i].fontName.family === 'Material Symbols Outlined') font = fonts[i].fontName;
|
|
40
|
+
if (font) { await figma.loadFontAsync(font); node.fontName = font; }
|
|
41
|
+
return node;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function variantNamed(set, name) {
|
|
45
|
+
var found = set.children.filter(function (node) { return node.name === name; })[0] || set.defaultVariant;
|
|
46
|
+
return found.createInstance();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var checkboxSet = await C.setOn('Fields', 'Checkbox');
|
|
50
|
+
var tabSet = await C.setOn('Disclosure and navigation', 'Tab');
|
|
51
|
+
var inputSet = await C.setOn('Fields', 'Input');
|
|
52
|
+
var selectSet = await C.setOn('Fields', 'Select');
|
|
53
|
+
var buttonSet = await C.setOn('Buttons', 'Button');
|
|
54
|
+
|
|
55
|
+
removeOld('Header cell');
|
|
56
|
+
var headerComps = [];
|
|
57
|
+
var sorts = ['none', 'ascending', 'descending'];
|
|
58
|
+
var aligns = ['start', 'end'];
|
|
59
|
+
for (var s = 0; s < sorts.length; s++) for (var a = 0; a < aligns.length; a++) {
|
|
60
|
+
var hc = comp('Sort=' + sorts[s] + ', Align=' + aligns[a], 'HORIZONTAL');
|
|
61
|
+
hc.primaryAxisAlignItems = aligns[a] === 'end' ? 'MAX' : 'MIN';
|
|
62
|
+
hc.counterAxisAlignItems = 'CENTER';
|
|
63
|
+
bind(hc, ['paddingLeft', 'paddingRight'], 'Size:table/cell-padding-inline');
|
|
64
|
+
fill(hc, 'Theme:table/header-background');
|
|
65
|
+
hugHeight(hc, 'Size:collection/header-height-sm');
|
|
66
|
+
add(hc, [await label('Date', 'subtitle-condense', 'Theme:table/header-color')]);
|
|
67
|
+
if (sorts[s] === 'ascending') add(hc, [await symbol('arrow_upward')]);
|
|
68
|
+
if (sorts[s] === 'descending') add(hc, [await symbol('arrow_downward')]);
|
|
69
|
+
headerComps.push(hc);
|
|
70
|
+
}
|
|
71
|
+
var headerSet = figma.combineAsVariants(headerComps, figma.currentPage);
|
|
72
|
+
headerSet.name = 'Header cell';
|
|
73
|
+
frameSet(headerSet, aligns.length, 200, 72);
|
|
74
|
+
textProp(headerSet, 'Text', 'Date', 'Text');
|
|
75
|
+
headerSet.description = 'A sortable header. Sort is none, ascending or descending. Align is start or end. The label is one text value across the set.';
|
|
76
|
+
place(headerSet);
|
|
77
|
+
|
|
78
|
+
removeOld('Cell');
|
|
79
|
+
var kinds = ['text', 'money', 'date', 'status', 'checkbox', 'actions'];
|
|
80
|
+
var cellComps = [];
|
|
81
|
+
for (var k = 0; k < kinds.length; k++) for (var a2 = 0; a2 < aligns.length; a2++) {
|
|
82
|
+
var cc = comp('Kind=' + kinds[k] + ', Align=' + aligns[a2], 'HORIZONTAL');
|
|
83
|
+
cc.primaryAxisAlignItems = aligns[a2] === 'end' ? 'MAX' : 'MIN';
|
|
84
|
+
cc.counterAxisAlignItems = 'CENTER';
|
|
85
|
+
bind(cc, ['paddingLeft', 'paddingRight'], 'Size:table/cell-padding-inline');
|
|
86
|
+
fill(cc, 'Theme:table/background');
|
|
87
|
+
hugHeight(cc, 'Size:table/row-height');
|
|
88
|
+
if (kinds[k] === 'checkbox') add(cc, [variantNamed(checkboxSet, 'Size=sm, Value=Off, State=Default')]);
|
|
89
|
+
else if (kinds[k] === 'actions') add(cc, [await symbol('more_horiz')]);
|
|
90
|
+
else {
|
|
91
|
+
var sample = kinds[k] === 'money' ? '$12.00' : kinds[k] === 'date' ? 'Oct 3, 2026' : kinds[k] === 'status' ? 'For Review' : 'Joshua Tree';
|
|
92
|
+
add(cc, [await label(sample, 'body-condense', 'Theme:table/color')]);
|
|
93
|
+
}
|
|
94
|
+
cellComps.push(cc);
|
|
95
|
+
}
|
|
96
|
+
var cellSet = figma.combineAsVariants(cellComps, figma.currentPage);
|
|
97
|
+
cellSet.name = 'Cell';
|
|
98
|
+
frameSet(cellSet, aligns.length, 200, 64);
|
|
99
|
+
textProp(cellSet, 'Text', 'Joshua Tree', 'Text');
|
|
100
|
+
cellSet.description = 'A data cell. Kind is text, money, date, status, checkbox or actions. Money and dates align end. The text prop is shared across the set.';
|
|
101
|
+
place(cellSet);
|
|
102
|
+
|
|
103
|
+
removeOld('Row');
|
|
104
|
+
var states = ['default', 'hover', 'selected', 'current'];
|
|
105
|
+
var rowComps = [];
|
|
106
|
+
for (var r = 0; r < states.length; r++) {
|
|
107
|
+
var rc = comp('State=' + states[r], 'HORIZONTAL');
|
|
108
|
+
rc.counterAxisAlignItems = 'CENTER';
|
|
109
|
+
fill(rc, rowFill(states[r]));
|
|
110
|
+
hugHeight(rc, 'Size:table/row-height');
|
|
111
|
+
if (states[r] === 'current') {
|
|
112
|
+
var bar = figma.createFrame(); bar.name = 'Current mark'; bar.layoutAlign = 'STRETCH';
|
|
113
|
+
bar.resize(3, 8); bind(bar, 'width', 'Size:stroke/strong'); fill(bar, 'Theme:border/accent');
|
|
114
|
+
add(rc, [bar]);
|
|
115
|
+
}
|
|
116
|
+
add(rc, [variantNamed(cellSet, 'Kind=text, Align=start'), variantNamed(cellSet, 'Kind=date, Align=end'), variantNamed(cellSet, 'Kind=money, Align=end')]);
|
|
117
|
+
rowComps.push(rc);
|
|
118
|
+
}
|
|
119
|
+
var rowSet = figma.combineAsVariants(rowComps, figma.currentPage);
|
|
120
|
+
rowSet.name = 'Row';
|
|
121
|
+
frameSet(rowSet, 2, 520, 64);
|
|
122
|
+
rowSet.description = 'A data row. Hover, selected and current use the table row tokens. Current is the open row and is quieter than selected.';
|
|
123
|
+
place(rowSet);
|
|
124
|
+
|
|
125
|
+
removeOld('Group row');
|
|
126
|
+
var groupComps = [];
|
|
127
|
+
for (var g = 0; g < 2; g++) {
|
|
128
|
+
var expanded = g === 0;
|
|
129
|
+
var gc = comp('Expanded=' + (expanded ? 'true' : 'false'), 'HORIZONTAL');
|
|
130
|
+
gc.counterAxisAlignItems = 'CENTER';
|
|
131
|
+
bind(gc, ['paddingLeft', 'paddingRight'], 'Size:table/cell-padding-inline');
|
|
132
|
+
fill(gc, 'Theme:table/group-header-background');
|
|
133
|
+
hugHeight(gc, 'Size:table/row-height');
|
|
134
|
+
add(gc, [await symbol(expanded ? 'expand_more' : 'chevron_right'), variantNamed(cellSet, 'Kind=text, Align=start'), variantNamed(cellSet, 'Kind=money, Align=end')]);
|
|
135
|
+
groupComps.push(gc);
|
|
136
|
+
}
|
|
137
|
+
var groupSet = figma.combineAsVariants(groupComps, figma.currentPage);
|
|
138
|
+
groupSet.name = 'Group row';
|
|
139
|
+
frameSet(groupSet, 1, 520, 64);
|
|
140
|
+
textProp(groupSet, 'Label', 'Joshua Tree', 'Label');
|
|
141
|
+
textProp(groupSet, 'Count', '12', 'Count');
|
|
142
|
+
textProp(groupSet, 'Subtotal', '$120.00', 'Subtotal');
|
|
143
|
+
groupSet.description = 'A group header. Expanded shows the chevron open. The subtotal lines up with the amount column.';
|
|
144
|
+
place(groupSet);
|
|
145
|
+
|
|
146
|
+
removeOld('Footer row');
|
|
147
|
+
var footer = comp('Footer', 'HORIZONTAL');
|
|
148
|
+
footer.counterAxisAlignItems = 'CENTER';
|
|
149
|
+
fill(footer, 'Theme:table/header-background');
|
|
150
|
+
hugHeight(footer, 'Size:table/row-height');
|
|
151
|
+
add(footer, [variantNamed(cellSet, 'Kind=text, Align=start'), variantNamed(cellSet, 'Kind=money, Align=end')]);
|
|
152
|
+
var footerSet = figma.combineAsVariants([footer], figma.currentPage);
|
|
153
|
+
footerSet.name = 'Footer row';
|
|
154
|
+
frameSet(footerSet, 1, 520, 64);
|
|
155
|
+
footerSet.description = 'Column totals over the filtered rows. The first cell says Total.';
|
|
156
|
+
place(footerSet);
|
|
157
|
+
|
|
158
|
+
removeOld('List item');
|
|
159
|
+
var listModes = [
|
|
160
|
+
['Selected=false, Current=false, Status=false, Extra=false'],
|
|
161
|
+
['Selected=true, Current=false, Status=false, Extra=false'],
|
|
162
|
+
['Selected=false, Current=true, Status=true, Extra=false'],
|
|
163
|
+
['Selected=false, Current=false, Status=true, Extra=true'],
|
|
164
|
+
];
|
|
165
|
+
var listComps = [];
|
|
166
|
+
for (var i = 0; i < listModes.length; i++) {
|
|
167
|
+
var lc = comp(listModes[i], 'VERTICAL');
|
|
168
|
+
bind(lc, 'itemSpacing', 'Size:collection/list-row-gap');
|
|
169
|
+
bind(lc, ['paddingTop', 'paddingBottom'], 'Size:collection/list-row-padding-block');
|
|
170
|
+
bind(lc, ['paddingLeft', 'paddingRight'], 'Size:table/cell-padding-inline');
|
|
171
|
+
var selected = listModes[i].indexOf('Selected=true') >= 0;
|
|
172
|
+
var current = listModes[i].indexOf('Current=true') >= 0;
|
|
173
|
+
fill(lc, selected ? 'Theme:table/row-selected' : current ? 'Theme:table/row-current' : 'Theme:table/background');
|
|
174
|
+
lc.primaryAxisSizingMode = 'AUTO';
|
|
175
|
+
lc.counterAxisSizingMode = 'AUTO';
|
|
176
|
+
if (current) {
|
|
177
|
+
var listBar = figma.createFrame(); listBar.name = 'Current mark'; listBar.resize(3, 8);
|
|
178
|
+
bind(listBar, 'width', 'Size:stroke/strong'); fill(listBar, 'Theme:border/accent'); add(lc, [listBar]);
|
|
179
|
+
}
|
|
180
|
+
add(lc, [await label('Chase deposit', 'subtitle-condense', 'Theme:table/color', 240)]);
|
|
181
|
+
add(lc, [await label('Joshua Tree · Oct 3, 2026', 'body-condense', 'Theme:table/subtle-color', 240)]);
|
|
182
|
+
if (listModes[i].indexOf('Extra=true') >= 0) add(lc, [await label('Account', 'body-condense', 'Theme:table/subtle-color', 240)]);
|
|
183
|
+
listComps.push(lc);
|
|
184
|
+
}
|
|
185
|
+
var listSet = figma.combineAsVariants(listComps, figma.currentPage);
|
|
186
|
+
listSet.name = 'List item';
|
|
187
|
+
frameSet(listSet, 2, 400, 110);
|
|
188
|
+
listSet.description = 'One row of a DataList. Selected and current use the same table tokens as the table. Extra is the inline slot under the meta line.';
|
|
189
|
+
place(listSet);
|
|
190
|
+
|
|
191
|
+
removeOld('Collection toolbar');
|
|
192
|
+
var toolbarComps = [];
|
|
193
|
+
for (var m = 0; m < 2; m++) {
|
|
194
|
+
var modeName = m === 0 ? 'filters' : 'selection';
|
|
195
|
+
var tc = comp('Mode=' + modeName, 'HORIZONTAL');
|
|
196
|
+
tc.counterAxisAlignItems = 'CENTER';
|
|
197
|
+
bind(tc, 'itemSpacing', 'Size:collection/toolbar-gap');
|
|
198
|
+
hugHeight(tc, 'Size:control/height-md');
|
|
199
|
+
add(tc, [variantNamed(tabSet, tabSet.defaultVariant.name), variantNamed(inputSet, inputSet.defaultVariant.name)]);
|
|
200
|
+
add(tc, [variantNamed(modeName === 'selection' ? buttonSet : selectSet, (modeName === 'selection' ? buttonSet : selectSet).defaultVariant.name)]);
|
|
201
|
+
toolbarComps.push(tc);
|
|
202
|
+
}
|
|
203
|
+
var toolbarSet = figma.combineAsVariants(toolbarComps, figma.currentPage);
|
|
204
|
+
toolbarSet.name = 'Collection toolbar';
|
|
205
|
+
frameSet(toolbarSet, 1, 680, 72);
|
|
206
|
+
toolbarSet.description = 'Filters mode shows the selects. Selection mode swaps only the trailing controls. Tabs and search stay.';
|
|
207
|
+
place(toolbarSet);
|
|
208
|
+
|
|
209
|
+
removeOld('Pagination');
|
|
210
|
+
var pag = comp('Pagination', 'HORIZONTAL');
|
|
211
|
+
bind(pag, 'itemSpacing', 'Size:collection/toolbar-gap');
|
|
212
|
+
pag.counterAxisAlignItems = 'CENTER';
|
|
213
|
+
hugHeight(pag, 'Size:control/height-md');
|
|
214
|
+
add(pag, [await label('1–25 of 312', 'body-condense', 'Theme:table/subtle-color', 140), await label('Previous', 'body-condense', 'Theme:text/primary', 80), await label('Next', 'body-condense', 'Theme:text/primary', 60)]);
|
|
215
|
+
var pagSet = figma.combineAsVariants([pag], figma.currentPage);
|
|
216
|
+
pagSet.name = 'Pagination';
|
|
217
|
+
frameSet(pagSet, 1, 400, 64);
|
|
218
|
+
pagSet.description = 'Which page you are on. Previous and Next are quiet. It does not need a collection.';
|
|
219
|
+
place(pagSet);
|
|
220
|
+
|
|
221
|
+
removeOld('Editable row');
|
|
222
|
+
var editComps = [];
|
|
223
|
+
var layouts = ['row', 'stacked'];
|
|
224
|
+
var editStates = ['default', 'invalid'];
|
|
225
|
+
for (var L = 0; L < layouts.length; L++) for (var E = 0; E < editStates.length; E++) {
|
|
226
|
+
var ec = comp('Layout=' + layouts[L] + ', State=' + editStates[E], layouts[L] === 'stacked' ? 'VERTICAL' : 'HORIZONTAL');
|
|
227
|
+
bind(ec, 'itemSpacing', 'Size:collection/toolbar-gap');
|
|
228
|
+
fill(ec, editStates[E] === 'invalid' ? 'Theme:status/error-light' : 'Theme:table/background');
|
|
229
|
+
if (layouts[L] === 'stacked') ec.primaryAxisSizingMode = 'AUTO';
|
|
230
|
+
else hugHeight(ec, 'Size:table/row-height');
|
|
231
|
+
add(ec, [await label('Fees', 'body-condense', 'Theme:table/color', 120), await label('$60.00', 'body-condense', 'Theme:table/color', 80), await label('Entrance', 'body-condense', 'Theme:table/subtle-color', 120)]);
|
|
232
|
+
editComps.push(ec);
|
|
233
|
+
}
|
|
234
|
+
var editSet = figma.combineAsVariants(editComps, figma.currentPage);
|
|
235
|
+
editSet.name = 'Editable row';
|
|
236
|
+
frameSet(editSet, 2, 520, 150);
|
|
237
|
+
editSet.description = 'A repeating form row. Stacked is the narrow layout: the same fields, with the column names visible. Invalid outlines the row.';
|
|
238
|
+
place(editSet);
|
|
239
|
+
|
|
240
|
+
removeOld('Balance');
|
|
241
|
+
var tones = [['balanced', 'Allocated $100.00 of $100.00 · balanced'], ['under', 'Allocated $97.00 of $100.00 · $3.00 left'], ['over', 'Allocated $103.00 of $100.00 · $3.00 over']];
|
|
242
|
+
var balComps = [];
|
|
243
|
+
for (var b = 0; b < tones.length; b++) {
|
|
244
|
+
var bc = comp('Tone=' + tones[b][0], 'HORIZONTAL');
|
|
245
|
+
bc.counterAxisAlignItems = 'CENTER';
|
|
246
|
+
hugHeight(bc, 'Size:control/height-md');
|
|
247
|
+
add(bc, [await label(tones[b][1], 'body-condense', 'Theme:text/primary', 400)]);
|
|
248
|
+
balComps.push(bc);
|
|
249
|
+
}
|
|
250
|
+
var balSet = figma.combineAsVariants(balComps, figma.currentPage);
|
|
251
|
+
balSet.name = 'Balance';
|
|
252
|
+
frameSet(balSet, 1, 460, 56);
|
|
253
|
+
textProp(balSet, 'Text', 'Allocated $100.00 of $100.00 · balanced', 'Text');
|
|
254
|
+
balSet.description = 'How an editable list adds up. Balanced, under or over. The words are one text value across the set; the tone is the variant.';
|
|
255
|
+
place(balSet);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Custom: Collection doc. Run after 16. Not named Custom, so it does not replace the Badge doc.
|
|
2
|
+
var AF = Object.getPrototypeOf(async function () {}).constructor;
|
|
3
|
+
var K = await new AF('figma', figma.root.getSharedPluginData('loomiere', 'docHelpers'))(figma);
|
|
4
|
+
await K.page('Custom');
|
|
5
|
+
var L = await new AF('figma', figma.root.getSharedPluginData('loomiere', 'docKit'))(figma);
|
|
6
|
+
var AL = L.AL, add = L.add, T = L.T, docFrame = L.docFrame, section = L.section, hrow = L.hrow, divider = L.divider;
|
|
7
|
+
var inst = L.inst, notes = L.notes, captioned = L.captioned, usage = L.usage, stage = L.stage;
|
|
8
|
+
|
|
9
|
+
var doc = await docFrame('Collection', 'COMPONENTS', 'Collection', 'The same rows as a table or as a list. People work through them. A spreadsheet is AG Grid, and the code should say why.');
|
|
10
|
+
// The Custom docs sit on a 1600 grid. This one starts the row under Date picker, clear of Money field and Empty state.
|
|
11
|
+
doc.x = 0; doc.y = 2400;
|
|
12
|
+
|
|
13
|
+
var which = await section(doc, 'Which one when', 'One set of columns. Change the view, not the data.');
|
|
14
|
+
var rows = [
|
|
15
|
+
['Data list', 'A review queue, an inbox, search results, a narrow pane.'],
|
|
16
|
+
['Data table', 'Rows compared across several columns.'],
|
|
17
|
+
['Plain table', 'A static table or a formatted report. No search, no selection.'],
|
|
18
|
+
['Editable list', 'Repeating form rows: line items, allocations, source fields.'],
|
|
19
|
+
['AG Grid', 'Cell-by-cell editing, ranges, or tens of thousands of rows.'],
|
|
20
|
+
];
|
|
21
|
+
var table = AL('VERTICAL', { name: 'Which' }); add(which, [table]); table.layoutSizingHorizontal = 'FILL';
|
|
22
|
+
for (var i = 0; i < rows.length; i++) {
|
|
23
|
+
var line = hrow(table); divider(line);
|
|
24
|
+
add(line, [await T(rows[i][0], 'subtitle-base', 'Theme:text/accent', 200), await T(rows[i][1], 'body-condense', 'Theme:text/secondary', 640)]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var look = await section(doc, 'Same data, two views', 'Park fees. The table compares columns. The list is the same rows for a queue.');
|
|
28
|
+
var side = stage(look, 'Views');
|
|
29
|
+
add(side, [await captioned(inst('Row', 'State=default'), 'Table row')]);
|
|
30
|
+
add(side, [await captioned(inst('List item', 'Selected=false, Current=false, Status=false, Extra=false'), 'List row')]);
|
|
31
|
+
add(side, [await captioned(inst('Group row', 'Expanded=true'), 'Group')]);
|
|
32
|
+
add(side, [await captioned(inst('Collection toolbar', 'Mode=filters'), 'Toolbar')]);
|
|
33
|
+
add(side, [await captioned(inst('Balance', 'Tone=under'), 'Balance')]);
|
|
34
|
+
|
|
35
|
+
await usage(look,
|
|
36
|
+
['For Review as a list, Posted as a table, from one collection.', 'A chart of accounts as a tree. Search keeps the parents.', 'Allocations that have to add up, with a balance line.'],
|
|
37
|
+
['A report or a statement. That stays a plain table.', 'A spreadsheet. That is AG Grid, and the code says why.', 'User column show/hide, row reorder, or inline cell editing. Those are not built.']);
|
|
38
|
+
await notes(look, [
|
|
39
|
+
['Open row', 'table/row-current', 'Quieter than a selected row, so both can show.'],
|
|
40
|
+
['Group', 'table/group-header-background', 'The tray, like the column header.'],
|
|
41
|
+
['Narrow', 'collection/narrow-width', 'Below this, a table can become the list.'],
|
|
42
|
+
['Stack', 'collection/editable-list-stack-width', 'Below this, editable rows stack.'],
|
|
43
|
+
]);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
{ "page": "Display", "files": ["pages/14-display/01-display.js", "pages/14-display/02-more-display.js", "pages/14-display/03-labels.js", "pages/14-display/04-doc.js"] },
|
|
19
19
|
{ "page": "Placeholders", "files": ["pages/16-placeholders/01-placeholders.js", "pages/16-placeholders/02-labels.js", "pages/16-placeholders/03-doc.js"] },
|
|
20
20
|
{ "page": "Annotations", "files": ["pages/17-annotations/01-annotations.js", "pages/17-annotations/02-labels.js", "pages/17-annotations/03-doc.js"] },
|
|
21
|
+
{ "page": "Custom", "files": ["pages/18-custom/01-custom.js", "pages/18-custom/02-labels.js", "pages/18-custom/03-doc.js", "pages/18-custom/04-date-picker.js", "pages/18-custom/05-date-picker-labels.js", "pages/18-custom/06-date-picker-doc.js", "pages/18-custom/07-money-field.js", "pages/18-custom/08-money-field-labels.js", "pages/18-custom/09-money-field-doc.js", "pages/18-custom/10-splitter.js", "pages/18-custom/11-splitter-labels.js", "pages/18-custom/12-splitter-doc.js", "pages/18-custom/13-empty-state.js", "pages/18-custom/14-empty-state-labels.js", "pages/18-custom/15-empty-state-doc.js", "pages/18-custom/16-collection.js", "pages/18-custom/17-collection-doc.js"] },
|
|
21
22
|
{ "page": "Cover", "files": ["pages/15-cover.js"] }
|
|
22
23
|
]
|
|
23
24
|
}
|
package/lib/contrast.mjs
CHANGED
|
@@ -55,11 +55,16 @@ const PAIRS = [
|
|
|
55
55
|
['content.prose-color', 'content.page-background', TEXT, 'prose'],
|
|
56
56
|
['content.prose-color', 'content.mark-background', TEXT, 'marked text'],
|
|
57
57
|
['text.on-inverse-surface', 'surface.inverse', TEXT, 'tooltip text'],
|
|
58
|
+
// popup.background aliases surface.base, which aliases canvas. The description still gets its own
|
|
59
|
+
// pair so a popup surface that stops being the page color is checked on its own.
|
|
60
|
+
['text.primary', 'popup.background', TEXT, 'empty title on a popup'],
|
|
61
|
+
['text.secondary', 'popup.background', TEXT, 'empty description on a popup'],
|
|
58
62
|
['text.on-inverse', 'fill.inverse', TEXT, 'keycap text'],
|
|
59
63
|
['text.on-tint', 'interactive.tint', TEXT, 'text on a tint'],
|
|
60
64
|
['text.on-tint-strong', 'interactive.tint-strong', TEXT, 'text on a strong tint'],
|
|
61
65
|
...['error', 'warning', 'success', 'info'].map((s) => [`status.${s}-dark`, `status.${s}-light`, TEXT, 'alert text']),
|
|
62
66
|
...['error', 'warning', 'success', 'info'].map((s) => [`scope.${s}.on-soft`, `scope.${s}.soft`, TEXT, 'tinted toast text']),
|
|
67
|
+
...['success', 'warning', 'error'].map((s) => [`scope.${s}.on-soft`, `scope.${s}.soft`, TEXT, 'editable list balance']),
|
|
63
68
|
...['accent', 'primary', 'secondary', 'tertiary', 'quaternary', 'error', 'warning', 'success', 'info'].map((s) => [`scope.${s}.solid`, 'gauge.track-color', UI, 'meter fill on its track']),
|
|
64
69
|
['table.color', 'table.background', TEXT, 'grid cell text'],
|
|
65
70
|
['table.subtle-color', 'table.background', TEXT, 'grid secondary text'],
|
|
@@ -67,6 +72,12 @@ const PAIRS = [
|
|
|
67
72
|
['table.subtle-color', 'table.header-background', TEXT, 'grid side bar text'],
|
|
68
73
|
['table.color', 'table.row-hover', TEXT, 'grid text on a hovered row'],
|
|
69
74
|
['table.color', 'table.row-selected', TEXT, 'grid text on a selected row'],
|
|
75
|
+
['table.subtle-color', 'table.row-hover', TEXT, 'secondary text on a hovered row'],
|
|
76
|
+
['table.subtle-color', 'table.row-selected', TEXT, 'secondary text on a selected row'],
|
|
77
|
+
['table.color', 'table.row-current', TEXT, 'text on the open row'],
|
|
78
|
+
['table.subtle-color', 'table.row-current', TEXT, 'secondary text on the open row'],
|
|
79
|
+
['table.header-color', 'table.group-header-background', TEXT, 'group header text'],
|
|
80
|
+
['table.subtle-color', 'table.group-header-background', TEXT, 'group header count'],
|
|
70
81
|
['table.color', 'table.range-background', TEXT, 'grid text in a range selection'],
|
|
71
82
|
['table.range-border', 'table.background', UI, 'focused cell edge'],
|
|
72
83
|
['table.range-border', 'table.row-selected', UI, 'focused cell edge on a selected row'],
|
|
@@ -78,6 +89,12 @@ const PAIRS = [
|
|
|
78
89
|
['focus.ring', 'surface.raised', UI, 'focus ring'],
|
|
79
90
|
['scope.chrome.focus', 'surface.canvas', UI, 'neutral focus ring'],
|
|
80
91
|
['scope.accent.focus', 'surface.canvas', UI, 'accent focus ring'],
|
|
92
|
+
['text.on-tint-strong', 'interactive.tint-active', TEXT, 'days inside a date range'],
|
|
93
|
+
['border.accent', 'surface.canvas', UI, 'today ring on a calendar that sits on the page'],
|
|
94
|
+
['border.accent', 'popup.background', UI, 'today ring on the calendar popup'],
|
|
95
|
+
['border.accent', 'interactive.tint-active', UI, 'date range band edge'],
|
|
96
|
+
['text.secondary', 'field.background', TEXT, 'money field affix'],
|
|
97
|
+
['text.secondary', 'field.background-disabled', TEXT, 'money field affix in a disabled field'],
|
|
81
98
|
];
|
|
82
99
|
|
|
83
100
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scattered-light/loomiere",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Turns a small brand file into a full design token theme: OKLCH color ramps, a type scale, light, dark and high-contrast modes, plus Tailwind, MUI, Chakra, AG Grid and Figma exports.",
|
|
5
5
|
"keywords": ["design-tokens", "theme", "style-dictionary", "dtcg", "oklch", "base-ui", "ag-grid", "tailwind"],
|
|
6
6
|
"repository": {
|
package/tokens/README.md
CHANGED
|
@@ -95,7 +95,7 @@ The accent can be any brand color; a red brand might keep red for errors and pic
|
|
|
95
95
|
|
|
96
96
|
**Concept picks.** `roles.concepts` in the brand file replaces a role's default step for one concept, in light, dark or both: `"chrome.fg": { "light": "primary.1000" }` sets body text to the brand's text color without touching any palette. A pick is `palette.step`, `white` (paper), `black` (ink) or a hex. The Concepts section of `loomiere studio` shows every concept in light and dark with its contrast and what uses it, and Reset drops a pick. Spot palettes share a contrast table, so `solid` has the same contrast in every hue; that is why any palette can stand in for another. `--sl-scope-<slot>` is the scope in effect: the accent by default, any palette under `data-color="secondary"` (or `error`, `chrome`…).
|
|
97
97
|
|
|
98
|
-
**Emphasis.** Styles are `solid`, `soft`, `outline` and `ghost`. `data-emphasis="subtle"` sets `--sl-e-bg`, `-bg-hover`, `-bg-active`, `-fg`, `-border` and `-border-hover` from the level's style. Accent levels follow the scope around them, so a `data-color` on a section re-colors them; chrome and named-palette levels stay put unless the element sets its own `data-color`. In React: `<Button emphasis="bold">`, `<Button emphasis="subtle" palette="error">`. Emphasis isn't only for buttons: each component that has a loudness choice starts at a level of its own (Button bold, Toggle's pressed state standard, Avatar initials bold) and paints with `--sl-e-*`, so `data-emphasis` on it picks another level, and on a Toggle Group sets every toggle in it. A new component joins by reading `--sl-e-*` and getting a default level in `DEFAULT_LEVELS` (build/build-tokens.mjs).
|
|
98
|
+
**Emphasis.** Styles are `solid`, `soft`, `outline` and `ghost`. `data-emphasis="subtle"` sets `--sl-e-bg`, `-bg-hover`, `-bg-active`, `-fg`, `-border` and `-border-hover` from the level's style. Accent levels follow the scope around them, so a `data-color` on a section re-colors them; chrome and named-palette levels stay put unless the element sets its own `data-color`. A default level is either pinned or follows the scope (`followsScope` on its `DEFAULT_LEVELS` entry). Pinned keeps the level's own color when a parent has `data-color` (Badge stays chrome subtle). Follows-scope also drops that pin for an ancestor `data-color`, so the section recolors it (an empty state's circle). In React: `<Button emphasis="bold">`, `<Button emphasis="subtle" palette="error">`. Emphasis isn't only for buttons: each component that has a loudness choice starts at a level of its own (Button bold, Toggle's pressed state standard, Avatar initials bold, Badge subtle, a selected calendar day bold, empty-state media subtle) and paints with `--sl-e-*`, so `data-emphasis` on it picks another level, and on a Toggle Group sets every toggle in it. A new component joins by reading `--sl-e-*` and getting a default level in `DEFAULT_LEVELS` (build/build-tokens.mjs).
|
|
99
99
|
|
|
100
100
|
**Statuses.** `data-status="info|success|warning|error"` on any element gives every emphasis level inside it that status's version: by default the level's style in the status's palette, chrome levels included, so `<div data-status="warning" data-emphasis="subtle">` is a soft warning banner and a bold button inside it is solid warning. A toast's type is its status. `roles.status` in the brand file lets a brand pair stand in for a level, per status: `{ "default": { "bold": "mirage-deep" }, "warning": { "subtle": "dune-soft" } }`. The pair paints the element: `bg` and `fg`, with `bg-hover` and `bg-active` mixed from them (`pair.<name>.bg-hover`, kept at AA). Levels left out stay automatic. The tokens are `status-level.<status>.<level>.<part>` (`tokens/semantic/status.json`, generated); `emphasis.<level>.<part>` is the default status's row.
|
|
101
101
|
|
|
@@ -134,15 +134,22 @@ Reduced motion is handled once, in `reset-accessibility.css`: every transition a
|
|
|
134
134
|
|
|
135
135
|
Some looks belong to a family of components, not to one. Those are concepts in the semantic layer, so the family stays consistent without one component borrowing another's tokens:
|
|
136
136
|
|
|
137
|
-
- `field.*` (`semantic/field.json`): anything you type into or pick a value from. Input, Field, Select, Combobox, Autocomplete and
|
|
137
|
+
- `field.*` (`semantic/field.json`): anything you type into or pick a value from. Input, Field, Select, Combobox, Autocomplete, Number field and Money field point here. `field.affix-color` is the muted prefix or suffix inside the field (a currency symbol). Sizes stay in `control.*`. Money field keeps only a picker width in `component/money-field.json`.
|
|
138
138
|
- `popup.*` (`semantic/popup.json`): the floating layer of menus, listboxes, popovers and preview cards. `popup.radius-field` is for lists that drop from a field. Arrows take `popup.arrow-*`, so an arrow always matches its popup in both themes. `popup.min-width` is the narrowest a menu or popover gets. Context menu, Menu and Menubar have no component tokens; they are built from `popup.*` and `item.*`.
|
|
139
139
|
- `modal.*` (`semantic/modal.json`): dialogs, alert dialogs and drawers. One surface (`surface.modal`, a Concepts row: paper in light, a step above the page in dark), scrim, heading, spacing and motion. Dialog and Alert dialog have no component tokens; Drawer keeps only its size and grab bar.
|
|
140
140
|
- `bar.*` (`semantic/bar.json`): the strip behind a row of controls (toolbars, toggle groups), on `surface.tray` (a Concepts row). Toggle group has no component tokens; Toolbar keeps only its field width.
|
|
141
141
|
- `disclosure.*` (`semantic/disclosure.json`): controls that show and hide a panel (Accordion, Collapsible, Tabs): hover and press fills, chevron, divider, selected tab line and motion. None of the three has component tokens.
|
|
142
142
|
- Tooltip, Preview card and Navigation menu are built from existing concepts. Tooltip uses `surface.inverse` with `text.on-inverse-surface`, which has its own Concepts row. Preview card uses `popup.*`. Navigation menu uses `bar.*` for the strip, `popup.*` for the panel, and item hover for links. Each keeps only size tokens.
|
|
143
|
+
- `empty.*` (`semantic/empty.json`): the block a page, pane, table, list or popup shows when there is nothing to show. Sizes only (padding, gap, circle, the description's measure). The media tint is the subtle emphasis level, so a status or a parent `data-color` recolors the circle and the title stays the normal text color. `popup.empty-padding-block` points at the compact padding, so a plain "No results" line and a compact empty state take the same space.
|
|
144
|
+
- `table.row-current` is the open row beside a detail pane. It uses the tray, a step off the page, because secondary text on the subtle fill misses AA in dark. A selected row keeps its own tint, so both can show. `table.group-header-background` is the same tray, so a group reads as chrome. Collection sizes live in `component/collection.json` (row and header heights, tree indent, list padding, toolbar gap, the width where a table becomes a list, the width where an editable list stacks, the selection column). No colors.
|
|
143
145
|
- `notice.*` (`semantic/notice.json`): toasts, on the popup surface. A toast whose type is success, warning, error or info takes that palette's soft fill, edge and text (the build points the scope at it); `data-color` picks any other palette. Toast keeps only the stack's width and corner margins.
|
|
144
146
|
- `gauge.*` (`semantic/gauge.json`): Meter and Progress share one bar. The track is `fill.track` (a Concepts row, near the page in dark mode so every palette's fill reads 3:1 on it); the fill is the scope's solid color, so `data-color` on the root sets a zone or status. Neither has component tokens. Slider is a gauge you can drag: its track, fill and label row are `gauge.*` (thin size), the knob is field-colored and ringed in the scope's solid color, and it keeps only its knob size and vertical length.
|
|
145
|
-
- Avatar, Separator and Scroll area are built from existing tokens. Avatar initials paint with the bold emphasis level (data-emphasis picks another), its ring is the page color, and it keeps only its five sizes. Separator is `border.default`; Scroll area is `content.scrollbar-*`. Neither has component tokens.
|
|
147
|
+
- Avatar, Separator and Scroll area are built from existing tokens. Avatar initials paint with the bold emphasis level (data-emphasis picks another), its ring is the page color, and it keeps only its five sizes. Separator is `border.default`; Scroll area is `content.scrollbar-*`. Neither has component tokens. Card and Dropdown token files were removed: no style read them (Base UI has no Card; menus and selects use `popup.*` and `item.*`).
|
|
148
|
+
- Badge is a Loomiere component, not a Base UI part. It paints with the emphasis levels (subtle unless `data-emphasis` says otherwise) and keeps only its sizes in `component/badge.json`: height, inline padding, gap and dot. Type is the `trigger-*` style of the same tier, and the shape is `radius.pill`.
|
|
149
|
+
- `calendar.*` (`semantic/calendar.json`): the month grid for DatePicker. Day size, radius, the range band and the today ring. Selected days are the bold emphasis level (`--sl-e-*`), not a calendar color, so they follow `data-color`. No component token file: the trigger is a field, and every size the grid needs is in this concept.
|
|
150
|
+
- Money field is a Loomiere component, not a Base UI part. It is a field: border, fill, focus, error and disabled come from `field.*`, and the affix uses `field.affix-color`. The only component token is `money-field.picker-min-inline-size`, because the currency list is wider than the code trigger. Text is the same body style as Input at that size.
|
|
151
|
+
- Splitter keeps only sizes in `component/splitter.json`: the hairline, the coarse hit area (`a11y.min-touch-target`, 44px), the grip dot, and the arrow-key steps. A fine pointer uses `a11y.min-target` (24px, WCAG 2.5.8) so the grab area stays off the scrollbar. The line is `border.default`, and hover and dragging use `border.strong`. Collapse uses `duration.normal`.
|
|
152
|
+
- List detail keeps only sizes in `component/list-detail.json`: the width where it becomes a splitter, the detail's share, and the header's gap and padding. The list stays on the page. The detail uses `surface.raised`. A narrow drawer uses the modal concept instead.
|
|
146
153
|
- `item.*` (`semantic/item.json`): rows inside menus and listboxes, and the check or dot that marks a chosen row. `item.padding-block-dense` is for menus.
|
|
147
154
|
- `icon-button.*` (`semantic/icon-button.json`): small icon-only buttons, such as the X that dismisses a popover, dialog or toast, and a field's clear and open buttons.
|
|
148
155
|
- `check.*` (`semantic/check.json`): the box or circle you tick. Checkbox, Radio and the Switch track share it, and AG Grid's checkboxes point at it.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"badge": {
|
|
3
|
+
"$description": "Badge sizes only. Colors are the emphasis levels (--sl-e-bg, --sl-e-fg, --sl-e-border), type is the trigger text style of the same tier, and the shape is radius.pill. Control heights are touch targets, so a badge keeps its own shorter steps.",
|
|
4
|
+
"height-minimize": {
|
|
5
|
+
"$value": "{space.5}",
|
|
6
|
+
"$description": "20px. The smallest pill that still holds trigger-minimize."
|
|
7
|
+
},
|
|
8
|
+
"height-condense": {
|
|
9
|
+
"$value": "{space.6}",
|
|
10
|
+
"$description": "24px, the default. Sits in a line of text and in dense rows."
|
|
11
|
+
},
|
|
12
|
+
"height-base": {
|
|
13
|
+
"$value": "{space.7}",
|
|
14
|
+
"$description": "28px, the small control height, for a badge beside base text."
|
|
15
|
+
},
|
|
16
|
+
"padding-inline-minimize": {
|
|
17
|
+
"$value": "{space.1}",
|
|
18
|
+
"$description": "4px. Tighter than a control so a short label stays a pill, not a button."
|
|
19
|
+
},
|
|
20
|
+
"padding-inline-condense": {
|
|
21
|
+
"$value": "{space.1-5}",
|
|
22
|
+
"$description": "6px, the default."
|
|
23
|
+
},
|
|
24
|
+
"padding-inline-base": {
|
|
25
|
+
"$value": "{space.2}",
|
|
26
|
+
"$description": "8px, still inside the small control's inline padding."
|
|
27
|
+
},
|
|
28
|
+
"gap-minimize": {
|
|
29
|
+
"$value": "{space.1}",
|
|
30
|
+
"$description": "4px between a dot or icon and the label at the smallest size."
|
|
31
|
+
},
|
|
32
|
+
"gap-condense": {
|
|
33
|
+
"$value": "{space.1}",
|
|
34
|
+
"$description": "4px, the default. The same gap as the smallest control."
|
|
35
|
+
},
|
|
36
|
+
"gap-base": {
|
|
37
|
+
"$value": "{space.1-5}",
|
|
38
|
+
"$description": "6px, a step wider once the label is base-sized."
|
|
39
|
+
},
|
|
40
|
+
"dot-minimize": {
|
|
41
|
+
"$value": "{space.1-5}",
|
|
42
|
+
"$description": "6px status dot. Smaller than icon-size-minimize so it reads as a mark, not an icon."
|
|
43
|
+
},
|
|
44
|
+
"dot-condense": {
|
|
45
|
+
"$value": "{space.2}",
|
|
46
|
+
"$description": "8px, the default."
|
|
47
|
+
},
|
|
48
|
+
"dot-base": {
|
|
49
|
+
"$value": "{space.2}",
|
|
50
|
+
"$description": "8px. The base badge is taller, but the dot stays a mark."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"collection": {
|
|
3
|
+
"$description": "Size-only measures for a collection: the table and list sizes, the toolbar, tree indent and the width where a table becomes a list or an editable list stacks. Colors stay on the table concept.",
|
|
4
|
+
"row-height-sm": {
|
|
5
|
+
"$value": "{control.height-sm}",
|
|
6
|
+
"$description": "The dense row, the same height the plain table already uses, so a checkbox fits."
|
|
7
|
+
},
|
|
8
|
+
"row-height-md": {
|
|
9
|
+
"$value": "{control.height-md}",
|
|
10
|
+
"$description": "A roomier row when the collection is the page rather than a dense register."
|
|
11
|
+
},
|
|
12
|
+
"header-height-sm": {
|
|
13
|
+
"$value": "{control.height-md}",
|
|
14
|
+
"$description": "The dense header, a step above the dense row, matching today's table header."
|
|
15
|
+
},
|
|
16
|
+
"header-height-md": {
|
|
17
|
+
"$value": "{control.height-lg}",
|
|
18
|
+
"$description": "The header for the roomier row."
|
|
19
|
+
},
|
|
20
|
+
"tree-indent": {
|
|
21
|
+
"$value": "{space.4}",
|
|
22
|
+
"$description": "How far each deeper level steps in, so a child sits under its parent's text."
|
|
23
|
+
},
|
|
24
|
+
"list-row-padding-block": {
|
|
25
|
+
"$value": "{space.3}",
|
|
26
|
+
"$description": "Vertical padding of a list row. The row is not a fixed height, because a second line and an extra slot have to fit."
|
|
27
|
+
},
|
|
28
|
+
"list-row-gap": {
|
|
29
|
+
"$value": "{space.2}",
|
|
30
|
+
"$description": "Between the checkbox, the text, the amount and the actions in a list row."
|
|
31
|
+
},
|
|
32
|
+
"toolbar-gap": {
|
|
33
|
+
"$value": "{space.2}",
|
|
34
|
+
"$description": "Between the search, the filters and the actions."
|
|
35
|
+
},
|
|
36
|
+
"search-min-width": {
|
|
37
|
+
"$value": "{space.60}",
|
|
38
|
+
"$description": "The narrowest the toolbar search gets beside the filters and actions. Under it, the search wraps to its own line, so a few words of a query still show."
|
|
39
|
+
},
|
|
40
|
+
"narrow-width": {
|
|
41
|
+
"$value": "{space.96}",
|
|
42
|
+
"$description": "Below this, a table with narrow=\"list\" becomes a list, and the toolbar's search takes the full width. A 360px pane is under it."
|
|
43
|
+
},
|
|
44
|
+
"editable-list-stack-width": {
|
|
45
|
+
"$value": "{space.80}",
|
|
46
|
+
"$description": "Below this, editable rows stack and show their own labels. 320px matches."
|
|
47
|
+
},
|
|
48
|
+
"selection-column-width": {
|
|
49
|
+
"$value": "{space.10}",
|
|
50
|
+
"$description": "The checkbox column. Wide enough for the control, no wider, so the data keeps the room."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list-detail": {
|
|
3
|
+
"$description": "Sizes for a list beside its detail. The list stays on the page surface. The detail uses surface.raised. Drawer mode uses the modal concept instead of these colors. No colors live here.",
|
|
4
|
+
"split-min-width": {
|
|
5
|
+
"$value": "720px",
|
|
6
|
+
"$description": "Side by side once this element, not the window, is this wide. Dryden used 1100px of the window. A container needs less: a list around 320px and a form around 400px still read at 720px. Narrower, the detail becomes a drawer or a page."
|
|
7
|
+
},
|
|
8
|
+
"detail-default-size": {
|
|
9
|
+
"$value": "45%",
|
|
10
|
+
"$description": "The detail's share when it opens. The list fills the rest, about 55%, which is the split Dryden started from."
|
|
11
|
+
},
|
|
12
|
+
"detail-min-size": {
|
|
13
|
+
"$value": "35%",
|
|
14
|
+
"$description": "The detail cannot be dragged narrower than this, so the form stays readable."
|
|
15
|
+
},
|
|
16
|
+
"detail-max-size": {
|
|
17
|
+
"$value": "65%",
|
|
18
|
+
"$description": "The detail cannot cover more than this, so the list stays usable. Dryden kept the detail between 35% and 65%."
|
|
19
|
+
},
|
|
20
|
+
"header-gap": {
|
|
21
|
+
"$value": "{space.3}",
|
|
22
|
+
"$description": "Space between the title, the actions and Close."
|
|
23
|
+
},
|
|
24
|
+
"header-padding": {
|
|
25
|
+
"$value": "{space.4}",
|
|
26
|
+
"$description": "Padding inside the detail header. One step under a dialog, because this is a pane, not a modal."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"money-field": {
|
|
3
|
+
"$description": "Sizes only. Colors, the border and the focus halo are the field concept. Text is the same text style as Input at that size.",
|
|
4
|
+
"picker-min-inline-size": {
|
|
5
|
+
"$value": "{space.56}",
|
|
6
|
+
"$description": "The currency list shows a code, a name and a symbol, so the popup is wider than the code trigger inside the field."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"splitter": {
|
|
3
|
+
"$description": "Sizes for a window splitter. The line and its hover use border.default and border.strong. Focus uses the shared focus ring. No colors live here.",
|
|
4
|
+
"handle-size": {
|
|
5
|
+
"$value": "{stroke.default}",
|
|
6
|
+
"$description": "The visible separator. One stroke, the same hairline as Separator, so a splitter reads as a divider you can grab."
|
|
7
|
+
},
|
|
8
|
+
"handle-hit-size": {
|
|
9
|
+
"$value": "{a11y.min-touch-target}",
|
|
10
|
+
"$description": "The part a finger grabs. The line stays one stroke. A fine pointer uses a11y.min-target (24px) instead, so the grab area does not cover the panel's scrollbar. Coarse pointers keep this 44px."
|
|
11
|
+
},
|
|
12
|
+
"grip-size": {
|
|
13
|
+
"$value": "{space.1}",
|
|
14
|
+
"$description": "4px. The diameter of one grip dot. Small enough to sit on the hairline without becoming a second control."
|
|
15
|
+
},
|
|
16
|
+
"key-step": {
|
|
17
|
+
"$value": "5%",
|
|
18
|
+
"$description": "One arrow press moves the handle by 5% of the splitter. Dryden used 5%: a nudge you can see, not a jump."
|
|
19
|
+
},
|
|
20
|
+
"key-step-large": {
|
|
21
|
+
"$value": "10%",
|
|
22
|
+
"$description": "The Shift step. Twice the arrow step, so a few presses cross the panel without holding the key."
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"$value": "0.125em",
|
|
21
21
|
"$description": "A slight em-based rounding so inline focus rings hug the text."
|
|
22
22
|
},
|
|
23
|
+
"min-target": {
|
|
24
|
+
"$value": "24px",
|
|
25
|
+
"$description": "Smallest target for a fine pointer (WCAG 2.5.8). A mouse can hit 24px; a finger still needs min-touch-target. The splitter handle uses this so the grab area does not cover the scrollbar beside it."
|
|
26
|
+
},
|
|
23
27
|
"min-touch-target": {
|
|
24
28
|
"$value": "44px",
|
|
25
29
|
"$description": "Smallest tap area for any control (WCAG 2.5.5 and Apple HIG both use 44px)."
|