@cahyo-dimas/freeday 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/COMPONENTS.md +68 -7
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/USAGE.md +10 -0
- package/adapters/blazor/FdyCfl.razor +24 -18
- package/adapters/blazor/FdyCfl.razor.cs +13 -2
- package/adapters/blazor/FdyModal.razor.cs +1 -1
- package/adapters/react/components/FdyCfl.tsx +79 -44
- package/adapters/react/components/FdyModal.tsx +2 -1
- package/adapters/react/index.d.ts +1 -1
- package/adapters/vue/components/FdyCfl.vue +56 -40
- package/adapters/vue/components/FdyModal.vue +4 -1
- package/dist/freeday.bundle.css +106 -10
- package/dist/freeday.css +104 -8
- package/dist/freeday.tokens.css +2 -2
- package/docs/agent-onboarding.md +6 -0
- package/docs/getting-started.md +1 -1
- package/package.json +2 -2
- package/src/components/app-shell.css +10 -3
- package/src/components/cfl.css +66 -2
- package/src/components/input.css +10 -0
- package/src/components/tabs.css +18 -3
- package/tokens/tokens.json +1 -1
|
@@ -72,6 +72,14 @@ const props = defineProps<{
|
|
|
72
72
|
describedby?: string;
|
|
73
73
|
id?: string;
|
|
74
74
|
ariaLabelledby?: string;
|
|
75
|
+
/** Render the dialog and NO field, for a caller whose trigger is already its own — a chip, a table
|
|
76
|
+
* cell, a menu item — opened by ref: `picker.value?.open()`. The raw path has had exactly this
|
|
77
|
+
* since the enhancer's `[data-fdy-cfl-open]`; the typed wrappers were a field *plus* a dialog
|
|
78
|
+
* with no way in, so a screen that could not spend a 22rem readonly input per trigger had to
|
|
79
|
+
* hand-roll the picker (#054). The host generates no box, so it can sit anywhere. `placeholder`,
|
|
80
|
+
* `clearable`, `id` and the field's aria props have nothing to name here and are ignored;
|
|
81
|
+
* `disabled` and `readonly` still refuse to open. */
|
|
82
|
+
dialogOnly?: boolean;
|
|
75
83
|
}>();
|
|
76
84
|
|
|
77
85
|
const emit = defineEmits<{
|
|
@@ -99,6 +107,7 @@ const loading: Ref<boolean> = ref(false);
|
|
|
99
107
|
const error: Ref<Error | null> = ref(null);
|
|
100
108
|
const activeIndex: Ref<number> = ref(-1);
|
|
101
109
|
|
|
110
|
+
const isDialogOnly: ComputedRef<boolean> = computed((): boolean => props.dialogOnly === true);
|
|
102
111
|
const isDisabled: ComputedRef<boolean> = computed((): boolean => props.disabled === true);
|
|
103
112
|
const isReadonly: ComputedRef<boolean> = computed((): boolean => props.readonly === true);
|
|
104
113
|
|
|
@@ -321,6 +330,11 @@ function onClose(): void {
|
|
|
321
330
|
triggerEl.value?.focus();
|
|
322
331
|
}
|
|
323
332
|
|
|
333
|
+
/* The dialog is reachable from outside, which is what makes `dialogOnly` usable at all: with no
|
|
334
|
+
field there is no trigger, and `open()` is the only door. Both guard exactly as the trigger did,
|
|
335
|
+
so a programmatic open cannot bypass `disabled`/`readonly`. */
|
|
336
|
+
defineExpose({ open: openDialog, close: closeDialog });
|
|
337
|
+
|
|
324
338
|
function cellText(row: Row, key: keyof Row & string): string {
|
|
325
339
|
const value: unknown = row[key];
|
|
326
340
|
return value === null || value === undefined ? '' : String(value);
|
|
@@ -332,45 +346,47 @@ onBeforeUnmount((): void => {
|
|
|
332
346
|
</script>
|
|
333
347
|
|
|
334
348
|
<template>
|
|
335
|
-
<div class="fdy-input-group">
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
349
|
+
<div :class="isDialogOnly ? 'fdy-cfl__host' : 'fdy-input-group'">
|
|
350
|
+
<template v-if="!isDialogOnly">
|
|
351
|
+
<input
|
|
352
|
+
:id="fieldId"
|
|
353
|
+
class="fdy-input"
|
|
354
|
+
type="text"
|
|
355
|
+
readonly
|
|
356
|
+
:value="displayValue"
|
|
357
|
+
:placeholder="placeholder"
|
|
358
|
+
:aria-labelledby="ariaLabelledby"
|
|
359
|
+
:aria-invalid="isInvalid ? 'true' : undefined"
|
|
360
|
+
:aria-describedby="describedby"
|
|
361
|
+
:disabled="isDisabled"
|
|
362
|
+
/>
|
|
363
|
+
<button
|
|
364
|
+
v-if="showClear"
|
|
365
|
+
type="button"
|
|
366
|
+
class="fdy-input-group__btn"
|
|
367
|
+
:aria-label="clearLabelText"
|
|
368
|
+
@click="clearValue"
|
|
369
|
+
>
|
|
370
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
|
371
|
+
<path d="M6 6l12 12M18 6L6 18"></path>
|
|
372
|
+
</svg>
|
|
373
|
+
</button>
|
|
374
|
+
<button
|
|
375
|
+
ref="triggerEl"
|
|
376
|
+
type="button"
|
|
377
|
+
class="fdy-input-group__btn"
|
|
378
|
+
aria-haspopup="dialog"
|
|
379
|
+
:aria-labelledby="ariaLabelledby"
|
|
380
|
+
:aria-label="ariaLabelledby ? undefined : (openLabel ?? 'Open search')"
|
|
381
|
+
:disabled="isDisabled || isReadonly"
|
|
382
|
+
@click="openDialog"
|
|
383
|
+
>
|
|
384
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
385
|
+
<circle cx="11" cy="11" r="7"></circle>
|
|
386
|
+
<path d="m21 21-4.35-4.35"></path>
|
|
387
|
+
</svg>
|
|
388
|
+
</button>
|
|
389
|
+
</template>
|
|
374
390
|
|
|
375
391
|
<dialog ref="dialogEl" class="fdy-modal fdy-modal--cfl" :aria-labelledby="titleId" @close="onClose" @keydown="onKeydown">
|
|
376
392
|
<div class="fdy-modal__header">
|
|
@@ -380,7 +396,7 @@ onBeforeUnmount((): void => {
|
|
|
380
396
|
|
|
381
397
|
<div class="fdy-modal__body">
|
|
382
398
|
<div class="fdy-cfl__search">
|
|
383
|
-
<div class="fdy-input-group"
|
|
399
|
+
<div class="fdy-input-group">
|
|
384
400
|
<span class="fdy-input-group__addon fdy-input-group__addon--icon">
|
|
385
401
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
386
402
|
<circle cx="11" cy="11" r="7"></circle>
|
|
@@ -16,7 +16,10 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
16
16
|
const props = withDefaults(defineProps<{
|
|
17
17
|
open: boolean;
|
|
18
18
|
title: string;
|
|
19
|
-
|
|
19
|
+
/* 'cfl' is the width the choose-from-list dialog uses (46rem). It was reachable from the CSS
|
|
20
|
+
and from nowhere else until #053: FdyCfl sets the class itself, so the gap only ever showed up
|
|
21
|
+
when an app built its own dialog at that width and had to hand-write the class. */
|
|
22
|
+
size?: 'sm' | 'md' | 'lg' | 'wide' | 'cfl';
|
|
20
23
|
dismissible?: boolean;
|
|
21
24
|
/** aria-label for the × button. Default 'Close'. Blazor has had `CloseLabel` since it
|
|
22
25
|
* shipped; these two hard-coded the string, so a non-English app could not rename it. */
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
--color-surface-raised: var(--color-surface);
|
|
324
324
|
--color-text: var(--slate-100);
|
|
325
325
|
--color-text-muted: var(--slate-400);
|
|
326
|
-
--color-text-subtle: var(--slate-
|
|
326
|
+
--color-text-subtle: var(--slate-450);
|
|
327
327
|
--color-on-brand: var(--slate-0);
|
|
328
328
|
--color-border: var(--slate-800);
|
|
329
329
|
--color-border-strong: var(--slate-700);
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
--color-surface-raised: var(--color-surface);
|
|
388
388
|
--color-text: var(--slate-100);
|
|
389
389
|
--color-text-muted: var(--slate-400);
|
|
390
|
-
--color-text-subtle: var(--slate-
|
|
390
|
+
--color-text-subtle: var(--slate-450);
|
|
391
391
|
--color-on-brand: var(--slate-0);
|
|
392
392
|
--color-border: var(--slate-800);
|
|
393
393
|
--color-border-strong: var(--slate-700);
|
|
@@ -963,8 +963,15 @@ a { color: var(--color-primary); }
|
|
|
963
963
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
964
964
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
965
965
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
966
|
-
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
967
|
-
|
|
966
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
967
|
+
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
968
|
+
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
969
|
+
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
970
|
+
* its content, so the scrolling child pushes this box — and the shell — open instead of scrolling.
|
|
971
|
+
* `flex:none` on the topbar is the same story one line down: it is a flex item in this column, and
|
|
972
|
+
* a topbar whose content wraps past `min-height` is a topbar that can be squeezed. Both consoles
|
|
973
|
+
* wrote these two rules independently, in app.css, before the fifth screen. */
|
|
974
|
+
.fdy-app__content{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;}
|
|
968
975
|
/* Brand is a fixed header, same height as the topbar so they align, and only
|
|
969
976
|
* the nav below it scrolls. Bare text (no children) renders as one bold
|
|
970
977
|
* display line via the rules on .fdy-app__brand itself. For a mark + two-line
|
|
@@ -1052,7 +1059,7 @@ a { color: var(--color-primary); }
|
|
|
1052
1059
|
also apply — it would fight the transform for the same panel. */
|
|
1053
1060
|
.fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
|
|
1054
1061
|
}
|
|
1055
|
-
.fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
1062
|
+
.fdy-app__topbar{position:sticky;top:0;z-index:20;flex:none;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
1056
1063
|
.fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1057
1064
|
.fdy-app__main{flex:1;padding:var(--space-8);}
|
|
1058
1065
|
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
|
|
@@ -1469,10 +1476,26 @@ a { color: var(--color-primary); }
|
|
|
1469
1476
|
|
|
1470
1477
|
/* Search bar, single leading-icon field; Enter/typing filters the results. */
|
|
1471
1478
|
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1479
|
+
/* The search group spans the bar. Four files wrote this as `style="max-width:none"` — both typed
|
|
1480
|
+
adapters and both docs examples — which is the same missing vocabulary as #055 §1, one class over. */
|
|
1481
|
+
.fdy-cfl__search .fdy-input-group{max-width:none;}
|
|
1482
|
+
|
|
1483
|
+
/* Field-less host (#054). With `dialogOnly` the typed wrappers render the dialog and no field, for a
|
|
1484
|
+
caller whose trigger is its own — a chip, a table cell, a menu item — which is what the raw path
|
|
1485
|
+
has always had in `[data-fdy-cfl-open]`. `display:contents` so the host generates no box at all:
|
|
1486
|
+
a zero-size div would still be a flex item or a grid cell in whatever laid the trigger out. */
|
|
1487
|
+
.fdy-cfl__host{display:contents;}
|
|
1472
1488
|
|
|
1473
|
-
/* Results, dense rows, sticky header, its own scroll region.
|
|
1474
|
-
|
|
1489
|
+
/* Results, dense rows, sticky header, its own scroll region.
|
|
1490
|
+
*
|
|
1491
|
+
* `container-type: inline-size` makes this box the query container for the stacked layout below.
|
|
1492
|
+
* Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
|
|
1493
|
+
* size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
|
|
1494
|
+
* takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
|
|
1495
|
+
* its own content, so it never sized to the table to begin with. */
|
|
1496
|
+
.fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
|
|
1475
1497
|
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
|
|
1498
|
+
|
|
1476
1499
|
.fdy-cfl__row{cursor:pointer;}
|
|
1477
1500
|
.fdy-cfl__row:hover{background:var(--color-surface-2);}
|
|
1478
1501
|
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
|
|
@@ -1490,6 +1513,54 @@ a { color: var(--color-primary); }
|
|
|
1490
1513
|
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
1491
1514
|
.fdy-cfl__actions{display:flex;gap:var(--space-2);}
|
|
1492
1515
|
|
|
1516
|
+
/* Narrow: the same rows as list items, not table cells (#053).
|
|
1517
|
+
*
|
|
1518
|
+
* A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
|
|
1519
|
+
* space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
|
|
1520
|
+
* whole job is "search, look, click one". So the first cell becomes the title and the rest become
|
|
1521
|
+
* one detail line under it — the shape a picker takes when it is narrow.
|
|
1522
|
+
*
|
|
1523
|
+
* A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
|
|
1524
|
+
* on a desktop page and narrow inside a docked panel, and only the container knows which.
|
|
1525
|
+
*
|
|
1526
|
+
* No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
|
|
1527
|
+
* received wisdom from the responsive-table era is that `display:block` on a table box drops its
|
|
1528
|
+
* implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
|
|
1529
|
+
* that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
|
|
1530
|
+
* with the rows at `display:flex`, stripping every `role` attribute leaves
|
|
1531
|
+
* `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
|
|
1532
|
+
* Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
|
|
1533
|
+
* OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
|
|
1534
|
+
* `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
|
|
1535
|
+
*
|
|
1536
|
+
* Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
|
|
1537
|
+
* row that costs a third of the width buys nothing when each row is labelled by its own title. */
|
|
1538
|
+
@container fdy-cfl (max-width: 30rem){
|
|
1539
|
+
.fdy-cfl__results .fdy-table,
|
|
1540
|
+
.fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
|
|
1541
|
+
.fdy-cfl__results .fdy-table thead{display:none;}
|
|
1542
|
+
.fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1543
|
+
.fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
|
|
1544
|
+
.fdy-cfl__row > td{display:block;padding:0;border:0;}
|
|
1545
|
+
/* The title takes the whole first line, which is what pushes every remaining cell onto the second
|
|
1546
|
+
one — no selector has to know how many columns there are, or which of them is last. */
|
|
1547
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
|
|
1548
|
+
.fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
1549
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
|
|
1550
|
+
/* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
|
|
1551
|
+
.fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
|
|
1552
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
|
|
1553
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
1554
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
|
|
1555
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
|
|
1556
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
|
|
1557
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
|
|
1558
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
|
|
1559
|
+
.fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
|
|
1560
|
+
/* The checkbox keeps its own line position beside the title rather than above it. */
|
|
1561
|
+
.fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1493
1564
|
/* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
|
|
1494
1565
|
* Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
|
|
1495
1566
|
|
|
@@ -1979,6 +2050,16 @@ a { color: var(--color-primary); }
|
|
|
1979
2050
|
|
|
1980
2051
|
/* Freeday. Field & Input */
|
|
1981
2052
|
.fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
|
|
2053
|
+
/* A field marked --full fills whatever contains it (#055 §1). The cap above is for a field standing
|
|
2054
|
+
on its own in a row — a filter bar, a toolbar — and it is right there. But every real form is a
|
|
2055
|
+
single-column flex stack (a settings pane, a login card, a dialog at 420px), and the only release
|
|
2056
|
+
the kit shipped was `.fdy-form-grid > .fdy-field`, which a flex column can never reach:
|
|
2057
|
+
.fdy-form-grid is `repeat(auto-fit,minmax(14rem,1fr))`, which pairs fields up the moment there is
|
|
2058
|
+
room. So a stack had no vocabulary at all and said it on the container instead — 23 times across
|
|
2059
|
+
two consoles, and four times in this kit's OWN reference page as `style="max-width:none"`.
|
|
2060
|
+
Inside .fdy-form-grid this changes nothing (that rule already says it); outside one the modifier
|
|
2061
|
+
finally means what its name says, and the grid keeps `grid-column` as its own concern. */
|
|
2062
|
+
.fdy-field--full{max-width:none;}
|
|
1982
2063
|
/* Inside a labelled field, a control fills the field (#051). The 22rem cap those controls carry is
|
|
1983
2064
|
for one standing on its own, with no field to take a width from; #017 already released the date
|
|
1984
2065
|
and time pickers here for exactly this reason, and its comment claims a picker then fills its
|
|
@@ -2428,9 +2509,24 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2428
2509
|
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
2429
2510
|
|
|
2430
2511
|
/* Freeday. Tabs (WAI-ARIA APG) */
|
|
2431
|
-
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
2432
|
-
|
|
2433
|
-
|
|
2512
|
+
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
2513
|
+
*
|
|
2514
|
+
* The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
|
|
2515
|
+
* (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
|
|
2516
|
+
* to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
|
|
2517
|
+
* broken by the `overflow-x:auto` beside it, in two ways at once:
|
|
2518
|
+
* 1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
|
|
2519
|
+
* to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
|
|
2520
|
+
* box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
|
|
2521
|
+
* invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
|
|
2522
|
+
* 2. A scroll container clips its descendants to the padding box, so the pixel the negative
|
|
2523
|
+
* margin pushed out was clipped away and the strip's own line painted in its place: the
|
|
2524
|
+
* active underline rendered ONE pixel of primary, never the two it asks for.
|
|
2525
|
+
* An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
|
|
2526
|
+
* tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
|
|
2527
|
+
* To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
|
|
2528
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
|
|
2529
|
+
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
2434
2530
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
2435
2531
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
2436
2532
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
package/dist/freeday.css
CHANGED
|
@@ -85,8 +85,15 @@ a { color: var(--color-primary); }
|
|
|
85
85
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
86
86
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
87
87
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
88
|
-
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
89
|
-
|
|
88
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
89
|
+
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
90
|
+
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
91
|
+
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
92
|
+
* its content, so the scrolling child pushes this box — and the shell — open instead of scrolling.
|
|
93
|
+
* `flex:none` on the topbar is the same story one line down: it is a flex item in this column, and
|
|
94
|
+
* a topbar whose content wraps past `min-height` is a topbar that can be squeezed. Both consoles
|
|
95
|
+
* wrote these two rules independently, in app.css, before the fifth screen. */
|
|
96
|
+
.fdy-app__content{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;}
|
|
90
97
|
/* Brand is a fixed header, same height as the topbar so they align, and only
|
|
91
98
|
* the nav below it scrolls. Bare text (no children) renders as one bold
|
|
92
99
|
* display line via the rules on .fdy-app__brand itself. For a mark + two-line
|
|
@@ -174,7 +181,7 @@ a { color: var(--color-primary); }
|
|
|
174
181
|
also apply — it would fight the transform for the same panel. */
|
|
175
182
|
.fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
|
|
176
183
|
}
|
|
177
|
-
.fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
184
|
+
.fdy-app__topbar{position:sticky;top:0;z-index:20;flex:none;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
178
185
|
.fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
179
186
|
.fdy-app__main{flex:1;padding:var(--space-8);}
|
|
180
187
|
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
|
|
@@ -591,10 +598,26 @@ a { color: var(--color-primary); }
|
|
|
591
598
|
|
|
592
599
|
/* Search bar, single leading-icon field; Enter/typing filters the results. */
|
|
593
600
|
.fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
601
|
+
/* The search group spans the bar. Four files wrote this as `style="max-width:none"` — both typed
|
|
602
|
+
adapters and both docs examples — which is the same missing vocabulary as #055 §1, one class over. */
|
|
603
|
+
.fdy-cfl__search .fdy-input-group{max-width:none;}
|
|
594
604
|
|
|
595
|
-
/*
|
|
596
|
-
|
|
605
|
+
/* Field-less host (#054). With `dialogOnly` the typed wrappers render the dialog and no field, for a
|
|
606
|
+
caller whose trigger is its own — a chip, a table cell, a menu item — which is what the raw path
|
|
607
|
+
has always had in `[data-fdy-cfl-open]`. `display:contents` so the host generates no box at all:
|
|
608
|
+
a zero-size div would still be a flex item or a grid cell in whatever laid the trigger out. */
|
|
609
|
+
.fdy-cfl__host{display:contents;}
|
|
610
|
+
|
|
611
|
+
/* Results, dense rows, sticky header, its own scroll region.
|
|
612
|
+
*
|
|
613
|
+
* `container-type: inline-size` makes this box the query container for the stacked layout below.
|
|
614
|
+
* Safe here and nowhere near free in general: a size container stops contributing intrinsic inline
|
|
615
|
+
* size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
|
|
616
|
+
* takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
|
|
617
|
+
* its own content, so it never sized to the table to begin with. */
|
|
618
|
+
.fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
|
|
597
619
|
.fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
|
|
620
|
+
|
|
598
621
|
.fdy-cfl__row{cursor:pointer;}
|
|
599
622
|
.fdy-cfl__row:hover{background:var(--color-surface-2);}
|
|
600
623
|
.fdy-cfl__row:focus-visible{outline:none;background:var(--color-primary-soft);box-shadow:inset 2px 0 0 var(--color-primary);}
|
|
@@ -612,6 +635,54 @@ a { color: var(--color-primary); }
|
|
|
612
635
|
.fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
|
|
613
636
|
.fdy-cfl__actions{display:flex;gap:var(--space-2);}
|
|
614
637
|
|
|
638
|
+
/* Narrow: the same rows as list items, not table cells (#053).
|
|
639
|
+
*
|
|
640
|
+
* A 420px add-on panel gives this dialog ~372px. A three- or four-column master-data table in that
|
|
641
|
+
* space either squeezes every column below legibility or scrolls on both axes, in a dialog whose
|
|
642
|
+
* whole job is "search, look, click one". So the first cell becomes the title and the rest become
|
|
643
|
+
* one detail line under it — the shape a picker takes when it is narrow.
|
|
644
|
+
*
|
|
645
|
+
* A CONTAINER query, not a media query: the dialog is not the viewport. The same component is wide
|
|
646
|
+
* on a desktop page and narrow inside a docked panel, and only the container knows which.
|
|
647
|
+
*
|
|
648
|
+
* No ARIA roles come with this, and that is a MEASURED decision rather than an omission. The
|
|
649
|
+
* received wisdom from the responsive-table era is that `display:block` on a table box drops its
|
|
650
|
+
* implicit role; explicit `role="table"/"row"/"cell"` were written into all four stacks here on
|
|
651
|
+
* that belief and then taken out again, because Chrome's own accessibility tree says otherwise:
|
|
652
|
+
* with the rows at `display:flex`, stripping every `role` attribute leaves
|
|
653
|
+
* `table → row → cell` intact (Chromium 133; only `rowgroup`, which associates nothing, is lost).
|
|
654
|
+
* Redundant ARIA is what the kit's own rule calls worse than none, so the guard asserts the
|
|
655
|
+
* OUTCOME — that a stacked row is still a row in the a11y tree — and stays silent about how.
|
|
656
|
+
* `browser/cfl-narrow.mjs` is where a future engine that does dissolve them shows up as red.
|
|
657
|
+
*
|
|
658
|
+
* Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
|
|
659
|
+
* row that costs a third of the width buys nothing when each row is labelled by its own title. */
|
|
660
|
+
@container fdy-cfl (max-width: 30rem){
|
|
661
|
+
.fdy-cfl__results .fdy-table,
|
|
662
|
+
.fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
|
|
663
|
+
.fdy-cfl__results .fdy-table thead{display:none;}
|
|
664
|
+
.fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
665
|
+
.fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
|
|
666
|
+
.fdy-cfl__row > td{display:block;padding:0;border:0;}
|
|
667
|
+
/* The title takes the whole first line, which is what pushes every remaining cell onto the second
|
|
668
|
+
one — no selector has to know how many columns there are, or which of them is last. */
|
|
669
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
|
|
670
|
+
.fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
|
|
671
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
|
|
672
|
+
/* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
|
|
673
|
+
.fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
|
|
674
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
|
|
675
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
676
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
|
|
677
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
|
|
678
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
|
|
679
|
+
.fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
|
|
680
|
+
.fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
|
|
681
|
+
.fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
|
|
682
|
+
/* The checkbox keeps its own line position beside the title rather than above it. */
|
|
683
|
+
.fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
|
|
684
|
+
}
|
|
685
|
+
|
|
615
686
|
/* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
|
|
616
687
|
* Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
|
|
617
688
|
|
|
@@ -1101,6 +1172,16 @@ a { color: var(--color-primary); }
|
|
|
1101
1172
|
|
|
1102
1173
|
/* Freeday. Field & Input */
|
|
1103
1174
|
.fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
|
|
1175
|
+
/* A field marked --full fills whatever contains it (#055 §1). The cap above is for a field standing
|
|
1176
|
+
on its own in a row — a filter bar, a toolbar — and it is right there. But every real form is a
|
|
1177
|
+
single-column flex stack (a settings pane, a login card, a dialog at 420px), and the only release
|
|
1178
|
+
the kit shipped was `.fdy-form-grid > .fdy-field`, which a flex column can never reach:
|
|
1179
|
+
.fdy-form-grid is `repeat(auto-fit,minmax(14rem,1fr))`, which pairs fields up the moment there is
|
|
1180
|
+
room. So a stack had no vocabulary at all and said it on the container instead — 23 times across
|
|
1181
|
+
two consoles, and four times in this kit's OWN reference page as `style="max-width:none"`.
|
|
1182
|
+
Inside .fdy-form-grid this changes nothing (that rule already says it); outside one the modifier
|
|
1183
|
+
finally means what its name says, and the grid keeps `grid-column` as its own concern. */
|
|
1184
|
+
.fdy-field--full{max-width:none;}
|
|
1104
1185
|
/* Inside a labelled field, a control fills the field (#051). The 22rem cap those controls carry is
|
|
1105
1186
|
for one standing on its own, with no field to take a width from; #017 already released the date
|
|
1106
1187
|
and time pickers here for exactly this reason, and its comment claims a picker then fills its
|
|
@@ -1550,9 +1631,24 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1550
1631
|
.fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
|
|
1551
1632
|
|
|
1552
1633
|
/* Freeday. Tabs (WAI-ARIA APG) */
|
|
1553
|
-
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
1554
|
-
|
|
1555
|
-
|
|
1634
|
+
/* position:relative, containing block for out-of-flow content in the tabs (see base.css).
|
|
1635
|
+
*
|
|
1636
|
+
* The strip's own line is an INSET SHADOW, not a border, and the tab carries no negative margin
|
|
1637
|
+
* (#052). The pair those two replace — `border-bottom` here plus `margin-bottom:-1px` on the tab,
|
|
1638
|
+
* to lift the active tab's 2px underline onto the strip's 1px line so the two read as one — was
|
|
1639
|
+
* broken by the `overflow-x:auto` beside it, in two ways at once:
|
|
1640
|
+
* 1. CSS Overflow §3 gives no way to scroll one axis alone: `overflow-y` computes from `visible`
|
|
1641
|
+
* to `auto` because the other axis is not visible, so the 1px the tab hangs below the content
|
|
1642
|
+
* box became a real scrollport. Measured 47 client / 48 scroll, `scrollTop` settling at 1 —
|
|
1643
|
+
* invisible in a screenshot, and a trackpad gesture over the strip is eaten by it.
|
|
1644
|
+
* 2. A scroll container clips its descendants to the padding box, so the pixel the negative
|
|
1645
|
+
* margin pushed out was clipped away and the strip's own line painted in its place: the
|
|
1646
|
+
* active underline rendered ONE pixel of primary, never the two it asks for.
|
|
1647
|
+
* An inset shadow paints on the box, not on the scrolled content, so the line stays put while the
|
|
1648
|
+
* tabs scroll sideways, and the tab's own border-bottom paints over it — same look, no overflow.
|
|
1649
|
+
* To hide the line, override `box-shadow`, not `border-bottom` (guarded in browser/tabs-strip.mjs). */
|
|
1650
|
+
.fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);box-shadow:inset 0 calc(var(--bw) * -1) 0 var(--color-border);overflow-x:auto;}
|
|
1651
|
+
.fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1556
1652
|
.fdy-tabs__tab:hover{color:var(--color-text);}
|
|
1557
1653
|
.fdy-tabs__tab:disabled,.fdy-tabs__tab[aria-disabled="true"]{opacity:.5;cursor:not-allowed;color:var(--color-text-muted);}
|
|
1558
1654
|
.fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
|
package/dist/freeday.tokens.css
CHANGED
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
--color-surface-raised: var(--color-surface);
|
|
323
323
|
--color-text: var(--slate-100);
|
|
324
324
|
--color-text-muted: var(--slate-400);
|
|
325
|
-
--color-text-subtle: var(--slate-
|
|
325
|
+
--color-text-subtle: var(--slate-450);
|
|
326
326
|
--color-on-brand: var(--slate-0);
|
|
327
327
|
--color-border: var(--slate-800);
|
|
328
328
|
--color-border-strong: var(--slate-700);
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
--color-surface-raised: var(--color-surface);
|
|
387
387
|
--color-text: var(--slate-100);
|
|
388
388
|
--color-text-muted: var(--slate-400);
|
|
389
|
-
--color-text-subtle: var(--slate-
|
|
389
|
+
--color-text-subtle: var(--slate-450);
|
|
390
390
|
--color-on-brand: var(--slate-0);
|
|
391
391
|
--color-border: var(--slate-800);
|
|
392
392
|
--color-border-strong: var(--slate-700);
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -132,6 +132,12 @@ Recent additions most likely to replace something an app hand-rolled (all detail
|
|
|
132
132
|
| `selectable` + `selectedKeys` on `FdyTable` | a checkbox column bolted beside a controlled table, with its own bulk bar |
|
|
133
133
|
| `fdy-step-before-change` | a wizard whose Next cannot be stopped while the server is still deciding |
|
|
134
134
|
| `.fdy-table--striped` | a `:nth-child` rule of your own that turns out to be the same colour as hover |
|
|
135
|
+
| `FdyCfl` in a narrow surface | a hand-rolled card picker, because the kit's was a table — it stacks itself under a 30rem container, no prop to pass |
|
|
136
|
+
| `size="cfl"` on `FdyModal` | writing `.fdy-modal--cfl` by hand to reach the 46rem width |
|
|
137
|
+
| `dialogOnly` + `open()` on `FdyCfl` | a hand-rolled picker, because the trigger is a chip or a table cell and not a 22rem field |
|
|
138
|
+
| `.fdy-field--full` | `max-width: none` on the form container, once per form, because the fields are a flex column and not a `.fdy-form-grid` |
|
|
139
|
+
| `.fdy-nav--flat` | undoing the caret, the pointer cursor and the group divider yourself, on a sidebar whose group labels never collapse |
|
|
140
|
+
| `--fdy-app-sidebar-w` | overriding `width` on `.fdy-app__sidebar`, which is also the rule that animates the collapse |
|
|
135
141
|
|
|
136
142
|
## 3. Starting a new screen
|
|
137
143
|
|
package/docs/getting-started.md
CHANGED
|
@@ -191,7 +191,7 @@ live docs also have a copy button per component.
|
|
|
191
191
|
```bash
|
|
192
192
|
npm i @cahyo-dimas/freeday
|
|
193
193
|
```
|
|
194
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.
|
|
194
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.3.0"` (public npm package). `dist/` is
|
|
195
195
|
committed and published → no build step; `npm ci` runs without auth.
|
|
196
196
|
|
|
197
197
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/harness-contract.mjs",
|
|
95
|
+
"test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/cfl-narrow.mjs browser/harness-contract.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
|
@@ -6,8 +6,15 @@
|
|
|
6
6
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
7
7
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
8
8
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
9
|
-
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
10
|
-
|
|
9
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
10
|
+
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
11
|
+
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
12
|
+
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
13
|
+
* its content, so the scrolling child pushes this box — and the shell — open instead of scrolling.
|
|
14
|
+
* `flex:none` on the topbar is the same story one line down: it is a flex item in this column, and
|
|
15
|
+
* a topbar whose content wraps past `min-height` is a topbar that can be squeezed. Both consoles
|
|
16
|
+
* wrote these two rules independently, in app.css, before the fifth screen. */
|
|
17
|
+
.fdy-app__content{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;}
|
|
11
18
|
/* Brand is a fixed header, same height as the topbar so they align, and only
|
|
12
19
|
* the nav below it scrolls. Bare text (no children) renders as one bold
|
|
13
20
|
* display line via the rules on .fdy-app__brand itself. For a mark + two-line
|
|
@@ -95,7 +102,7 @@
|
|
|
95
102
|
also apply — it would fight the transform for the same panel. */
|
|
96
103
|
.fdy-app--nav-overlay.fdy-app--nav-collapsed .fdy-app__sidebar{width:16rem;border-right-width:var(--bw);}
|
|
97
104
|
}
|
|
98
|
-
.fdy-app__topbar{position:sticky;top:0;z-index:20;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
105
|
+
.fdy-app__topbar{position:sticky;top:0;z-index:20;flex:none;min-height:var(--space-16);display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-8);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-bottom:var(--bw) solid var(--color-border);}
|
|
99
106
|
.fdy-app__title{font-family:var(--font-display);font-size:var(--text-xl);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
100
107
|
.fdy-app__main{flex:1;padding:var(--space-8);}
|
|
101
108
|
.fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
|