@cahyo-dimas/freeday 1.53.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +657 -535
  2. package/COMPONENTS.md +153 -135
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +39 -35
  6. package/adapters/blazor/FdyAppShell.razor +50 -0
  7. package/adapters/blazor/FdyAppShell.razor.cs +114 -0
  8. package/adapters/blazor/FdyCfl.razor.cs +1 -1
  9. package/adapters/blazor/FdyChart.razor +1 -1
  10. package/adapters/blazor/FdyChart.razor.cs +3 -3
  11. package/adapters/blazor/FdyChartSeries.cs +1 -1
  12. package/adapters/blazor/FdyCombo.razor.cs +2 -2
  13. package/adapters/blazor/FdyModal.razor +1 -1
  14. package/adapters/blazor/FdyTable.razor.cs +7 -7
  15. package/adapters/blazor/FdyTableFilter.razor +1 -1
  16. package/adapters/blazor/FdyTableFooter.razor +1 -1
  17. package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
  18. package/adapters/blazor/Freeday.Blazor.csproj +1 -1
  19. package/adapters/blazor/FreedayComponentBase.cs +1 -1
  20. package/adapters/blazor/TableModel.cs +3 -3
  21. package/adapters/blazor/TableTypes.cs +3 -3
  22. package/adapters/blazor/freeday-blazor.js +5 -5
  23. package/adapters/core/app-shell.d.ts +30 -0
  24. package/adapters/core/app-shell.js +134 -0
  25. package/adapters/core/table-model.d.ts +2 -2
  26. package/adapters/core/table-model.js +8 -8
  27. package/adapters/react/components/FdyAppShell.tsx +159 -0
  28. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  29. package/adapters/react/components/FdyCascade.tsx +1 -1
  30. package/adapters/react/components/FdyCfl.tsx +11 -11
  31. package/adapters/react/components/FdyDateRange.tsx +2 -2
  32. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  33. package/adapters/react/components/FdyDrawer.tsx +1 -1
  34. package/adapters/react/components/FdyModal.tsx +2 -2
  35. package/adapters/react/components/FdyTable.tsx +7 -7
  36. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  37. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  38. package/adapters/react/index.d.ts +2 -1
  39. package/adapters/react/index.js +1 -0
  40. package/adapters/react/useFreeday.js +3 -3
  41. package/adapters/vue/components/FdyAppShell.vue +160 -0
  42. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  43. package/adapters/vue/components/FdyCascade.vue +1 -1
  44. package/adapters/vue/components/FdyCfl.vue +8 -8
  45. package/adapters/vue/components/FdyChart.vue +2 -2
  46. package/adapters/vue/components/FdyCombo.vue +1 -1
  47. package/adapters/vue/components/FdyDateRange.vue +2 -2
  48. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  49. package/adapters/vue/components/FdyDrawer.vue +3 -3
  50. package/adapters/vue/components/FdyModal.vue +3 -3
  51. package/adapters/vue/components/FdyTable.vue +7 -7
  52. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  53. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  54. package/adapters/vue/index.d.ts +3 -2
  55. package/adapters/vue/index.js +1 -0
  56. package/adapters/vue/useFreeday.js +3 -3
  57. package/dist/asset.d.ts +3 -3
  58. package/dist/freeday-app-shell.js +49 -9
  59. package/dist/freeday-autocomplete.js +2 -2
  60. package/dist/freeday-breakpoint.js +1 -1
  61. package/dist/freeday-carousel.js +5 -5
  62. package/dist/freeday-cascade.js +7 -7
  63. package/dist/freeday-cfl.js +9 -9
  64. package/dist/freeday-chart.js +11 -11
  65. package/dist/freeday-chip.js +3 -3
  66. package/dist/freeday-datepicker.js +11 -7
  67. package/dist/freeday-datetime.js +5 -5
  68. package/dist/freeday-drawer.js +1 -1
  69. package/dist/freeday-form.js +14 -14
  70. package/dist/freeday-mask.js +6 -6
  71. package/dist/freeday-menu.js +1 -1
  72. package/dist/freeday-number.js +3 -3
  73. package/dist/freeday-popover.js +3 -3
  74. package/dist/freeday-rating.js +2 -2
  75. package/dist/freeday-select.js +5 -5
  76. package/dist/freeday-slider.js +2 -2
  77. package/dist/freeday-stepper.js +7 -7
  78. package/dist/freeday-table.js +15 -15
  79. package/dist/freeday-tabs.js +1 -1
  80. package/dist/freeday-timepicker.js +2 -2
  81. package/dist/freeday-toast.js +8 -8
  82. package/dist/freeday-tree.js +3 -3
  83. package/dist/freeday-upload.js +25 -25
  84. package/dist/freeday.bundle.css +176 -176
  85. package/dist/freeday.css +170 -170
  86. package/dist/freeday.d.ts +2 -2
  87. package/dist/freeday.js +202 -158
  88. package/dist/freeday.tokens.css +5 -5
  89. package/docs/agent-onboarding.md +42 -42
  90. package/docs/getting-started.md +35 -35
  91. package/docs/integrations.md +18 -18
  92. package/docs/reference-screen.html +14 -14
  93. package/package.json +5 -4
  94. package/src/base.css +6 -6
  95. package/src/components/accordion.css +4 -4
  96. package/src/components/alert.css +1 -1
  97. package/src/components/app-shell.css +10 -10
  98. package/src/components/appbar.css +3 -3
  99. package/src/components/autocomplete.css +1 -1
  100. package/src/components/avatar.css +3 -3
  101. package/src/components/badge.css +5 -5
  102. package/src/components/breadcrumb.css +1 -1
  103. package/src/components/breakpoints.css +4 -4
  104. package/src/components/button.css +13 -13
  105. package/src/components/card.css +8 -8
  106. package/src/components/carousel.css +2 -2
  107. package/src/components/cascade.css +2 -2
  108. package/src/components/cfl.css +4 -4
  109. package/src/components/chart.css +3 -3
  110. package/src/components/chip.css +2 -2
  111. package/src/components/combo.css +3 -3
  112. package/src/components/composition.css +7 -7
  113. package/src/components/datepicker.css +6 -6
  114. package/src/components/datetimepicker.css +1 -1
  115. package/src/components/description-list.css +1 -1
  116. package/src/components/divider.css +1 -1
  117. package/src/components/drawer.css +1 -1
  118. package/src/components/file-upload.css +5 -5
  119. package/src/components/filterbar.css +7 -7
  120. package/src/components/form-grid.css +2 -2
  121. package/src/components/input-group.css +6 -6
  122. package/src/components/input.css +6 -6
  123. package/src/components/kbd.css +1 -1
  124. package/src/components/list.css +6 -6
  125. package/src/components/menu.css +3 -3
  126. package/src/components/modal.css +1 -1
  127. package/src/components/pagination.css +1 -1
  128. package/src/components/progress.css +1 -1
  129. package/src/components/rating.css +2 -2
  130. package/src/components/selection.css +4 -4
  131. package/src/components/skeleton.css +1 -1
  132. package/src/components/slider.css +1 -1
  133. package/src/components/spinner.css +1 -1
  134. package/src/components/states.css +1 -1
  135. package/src/components/stepper.css +2 -2
  136. package/src/components/table.css +15 -15
  137. package/src/components/tabs.css +3 -3
  138. package/src/components/timeline.css +1 -1
  139. package/src/components/timepicker.css +1 -1
  140. package/src/components/toast.css +3 -3
  141. package/src/components/tooltip.css +1 -1
  142. package/src/components/tree.css +2 -2
  143. package/tokens/breakpoints.d.ts +2 -2
  144. package/tokens/breakpoints.mjs +3 -3
  145. package/tokens/tokens.json +1 -1
package/USAGE.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Freeday — Usage doctrine
2
2
 
3
- Freeday ships tokens and components. This file ships the **decisions** which token to use when,
3
+ Freeday ships tokens and components. This file ships the **decisions**: which token to use when,
4
4
  how a page is assembled, what earns emphasis. A component library enforces consistent *values*; a
5
5
  design system also enforces consistent *decisions*. Skim this once before building a screen; it is
6
6
  what makes screens built by different people (or different sessions) look like one product.
7
7
 
8
- The rules are opinionated on purpose. When one conflicts with a real need, break it deliberately
8
+ The rules are opinionated on purpose. When one conflicts with a real need, break it deliberately, and
9
9
  but start here, not from a blank page.
10
10
 
11
11
  ---
12
12
 
13
- ## 1. Type roles three title levels, not eleven
13
+ ## 1. Type roles: three title levels, not eleven
14
14
 
15
15
  Do **not** reuse `.fdy-card__title` for a page title. Collapsing page/section/card into one style is
16
16
  the single biggest cause of "flat grey mush". Pick the role, not the size:
@@ -30,31 +30,31 @@ font size.
30
30
 
31
31
  ## Borders: two roles, two contrast contracts
32
32
 
33
- `--color-border` (and `-muted` / `-strong`) is **decoration** a card edge, a table rule, a
33
+ `--color-border` (and `-muted` / `-strong`) is **decoration**: a card edge, a table rule, a
34
34
  separator. Nothing in WCAG requires it to be visible, and it is deliberately faint (≈1.3:1).
35
35
 
36
- `--color-control-border` is the **boundary of a control** input, textarea, checkbox, radio,
36
+ `--color-control-border` is the **boundary of a control**: input, textarea, checkbox, radio,
37
37
  switch, dropzone. WCAG 1.4.11 requires 3:1 against every surface it can sit on, so it is
38
38
  necessarily darker than a card edge; that difference is the contract, not an inconsistency.
39
39
 
40
40
  It is tuned to clear that floor with margin (≈3.4–3.9 depending on surface) rather than to be as
41
41
  dark as it can be: a boundary that reads as loud as body text makes a form feel heavier than it is.
42
- Do not "harmonise" a control border down to `--color-border` that drops a required 3:1 boundary to
42
+ Do not "harmonise" a control border down to `--color-border`. That drops a required 3:1 boundary to
43
43
  1.3:1.
44
44
 
45
- ## 2. Spacing rhythm three gaps, always from the scale
45
+ ## 2. Spacing rhythm: three gaps, always from the scale
46
46
 
47
47
  Never a loose value; always `var(--space-N)` (a 4px scale). Three rhythms carry most layouts, and the
48
48
  composition primitives apply them for you:
49
49
 
50
- - **Between page sections:** `--space-8` `.fdy-page` sets this gap between its children.
51
- - **Within a group** (heading ↔ its body, cards in a list): `--space-4` `.fdy-page-section` sets it.
50
+ - **Between page sections:** `--space-8`, which `.fdy-page` sets between its children.
51
+ - **Within a group** (heading ↔ its body, cards in a list): `--space-4`, which `.fdy-page-section` sets.
52
52
  - **Inside a control/card** (label ↔ input, icon ↔ text): `--space-2` / `--space-3`.
53
53
 
54
54
  Reserve `--space-1` for hairline pairs and `--space-10`+ for deliberate breathing room (a hero, an
55
- empty state). Don't scatter `--space-6` everywhere a page with one gap value has no rhythm.
55
+ empty state). Don't scatter `--space-6` everywhere. A page with one gap value has no rhythm.
56
56
 
57
- ## 3. Elevation most surfaces are flat
57
+ ## 3. Elevation: most surfaces are flat
58
58
 
59
59
  Shadow is a signal, not decoration. There are two families, and the difference matters:
60
60
 
@@ -67,23 +67,23 @@ Shadow is a signal, not decoration. There are two families, and the difference m
67
67
  | Overlay | `--shadow-4` | `.fdy-drawer` |
68
68
  | **Lift** | `--shadow-lift` / `--shadow-lift-hover` | **`.fdy-card`** (and `--elevated` / `--interactive:hover`), `.fdy-modal` |
69
69
 
70
- **`.fdy-card` is a lifted surface, not a hairline one** `--shadow-lift` is a real 34px lift, ~6×
70
+ **`.fdy-card` is a lifted surface, not a hairline one.** `--shadow-lift` is a real 34px lift, ~6×
71
71
  heavier than `--shadow-1`. That is the whole point of a card, and it is also why a *stack* of them
72
72
  reads wrong: a list of ten cards is ten objects floating off the page. For rows, use the flat
73
73
  container **`.fdy-list` / `.fdy-list__row`** (hairline border, `--color-border-muted` dividers, no
74
74
  shadow). Reach for `.fdy-card` when something genuinely is one pickable object.
75
75
 
76
- If every box on the screen has the same card shadow, none of them read as special — that's the
76
+ If every box on the screen has the same card shadow, none of them read as special. That's the
77
77
  "identical card grid" failure. Prefer flat sections with **one** raised element that matters.
78
78
 
79
- ## 4. Emphasis exactly one primary per screen
79
+ ## 4. Emphasis: exactly one primary per screen
80
80
 
81
81
  `.fdy-btn` is *already* the primary action (there is no `--primary` modifier). Use it **once** per
82
- screen the one thing you want the user to do. Everything else is `--ghost` or `--text`. Two primary
82
+ screen: the one thing you want the user to do. Everything else is `--ghost` or `--text`. Two primary
83
83
  buttons on a screen means neither is. The same rule governs colour fills and `--shadow`: one focal
84
84
  point, everything around it quiet.
85
85
 
86
- ## 5. Colour semantic is reserved; categorical is `--tone`
86
+ ## 5. Colour: semantic is reserved; categorical is `--tone`
87
87
 
88
88
  - **Accent** (`--color-primary`, and `--color-accent` sparingly): interactive + brand. This is your
89
89
  one accent hue.
@@ -91,19 +91,19 @@ point, everything around it quiet.
91
91
  = caution only, `--color-danger` = destructive/error only. Never decorative. Encode state in a pill
92
92
  or chip, not just colour.
93
93
  - **Categorical** (`--tone-1` … `--tone-8`, the general alias of the validated chart palette): N
94
- visually-distinct **non-semantic** colours avatar tones, category chips, tags, legend swatches.
94
+ visually-distinct **non-semantic** colours: avatar tones, category chips, tags, legend swatches.
95
95
  Use the modifiers `.fdy-avatar--tone-N` / `.fdy-chip--tone-N` (both stay WCAG AA in light & dark),
96
96
  and hash a stable index off the full string so the same category always gets the same colour.
97
97
  - **Surfaces:** most backgrounds are `--color-surface`; `--color-surface-2`/`-3` for a recessed area;
98
98
  `--color-primary-soft` only when you want a tinted callout, not as a default panel colour.
99
99
 
100
- ## 5b. Theme global by default, per-subtree when a region is inverted
100
+ ## 5b. Theme: global by default, per-subtree when a region is inverted
101
101
 
102
102
  `data-theme="light|dark"` redefines the semantic tokens. Set it on `<html>` and it themes the app;
103
103
  that is the normal case and nothing about it has changed.
104
104
 
105
105
  **It is also per-subtree.** The two explicit selectors are bare `[data-theme="dark"]` /
106
- `[data-theme="light"]`, and semantic tokens are inheriting custom properties so a
106
+ `[data-theme="light"]`, and semantic tokens are inheriting custom properties, so a
107
107
  `<section data-theme="dark">` inverts that region and **every Freeday component inside it follows**:
108
108
  card surfaces, inputs, buttons, and text roles like `.fdy-title-page` that set
109
109
  `color: var(--color-text)` explicitly. A dark brand panel beside a light sign-in form needs no
@@ -112,40 +112,40 @@ in turn.
112
112
 
113
113
  Do **not** invert a region by hand with `--color-inverse-*` plus `color-mix`. That pair is right for
114
114
  a one-off band of your own markup, but it stops scaling the moment the region contains a real
115
- component anything that sets its own colour from a token never sees your override, and you end up
115
+ component. Anything that sets its own colour from a token never sees your override, and you end up
116
116
  restating colours per element.
117
117
 
118
118
  The **system** default (`@media (prefers-color-scheme: dark)`) stays root-scoped on purpose: that
119
119
  rule is about the document, and un-rooting it would re-darken the children of a light island.
120
120
 
121
- ## 6. Density `compact` for data-dense screens
121
+ ## 6. Density: `compact` for data-dense screens
122
122
 
123
123
  `data-density="compact"` tightens control height **and** the mid-range spacing scale
124
124
  (`--space-3`…`--space-6` step down a notch), so cards, toolbars and tables get denser. Use it on
125
125
  table-heavy back-office screens; leave `comfortable` (the default) for forms.
126
126
 
127
127
  **It is per-subtree, not only global.** The selector is a bare `[data-density="compact"]` and these
128
- are inheriting custom properties, so the attribute works on `<html>` *or* on any wrapper a route
128
+ are inheriting custom properties, so the attribute works on `<html>` *or* on any wrapper: a route
129
129
  container, a single `<section>`. An app whose two list screens are dense and whose three form screens
130
130
  are not should scope it per screen rather than densifying everything. Set it at one level per screen,
131
131
  never per component.
132
132
 
133
133
  **And it goes both ways.** `data-density="comfortable"` is a real rule, not just the absence of
134
- `compact`, so an app that is dense *at the root* can opt one region back out a header, a toolbar,
134
+ `compact`, so an app that is dense *at the root* can opt one region back out: a header, a toolbar,
135
135
  a footer shared with a sibling product that has to line up pixel for pixel. Inheritance only runs
136
136
  downhill; without that rule a compact root left every subtree compact and a `comfortable` wrapper
137
137
  changed nothing. Do not restate the five values by hand: that copy goes stale the moment the kit
138
138
  retunes a step.
139
139
 
140
- ## 6b. Navigation: one component, two orientations and never a tab role
140
+ ## 6b. Navigation: one component, two orientations, and never a tab role
141
141
 
142
142
  A navigation link is an `<a class="fdy-nav__item">` marked **`aria-current="page"`**. That does not
143
143
  change with the layout:
144
144
 
145
- - **Sidebar app** `.fdy-nav` inside `.fdy-app__sidebar` (the default).
146
- - **Top-nav app** `.fdy-nav.fdy-nav--horizontal` inside `.fdy-appbar` or `.fdy-app__topbar`, with
145
+ - **Sidebar app:** `.fdy-nav` inside `.fdy-app__sidebar` (the default).
146
+ - **Top-nav app:** `.fdy-nav.fdy-nav--horizontal` inside `.fdy-appbar` or `.fdy-app__topbar`, with
147
147
  no sidebar. Same items, same states; on `.fdy-appbar--primary` the links go on-colour for you.
148
- - **Routed sub-navigation that should look like tabs** (`/settings/profile` · `/settings/billing`)
148
+ - **Routed sub-navigation that should look like tabs** (`/settings/profile` · `/settings/billing`):
149
149
  put `.fdy-tabs__list` / `.fdy-tabs__tab` on plain `<a>`s. Those classes honour `aria-current="page"`
150
150
  as well as `aria-selected="true"` precisely for this. Do **not** add `role="tab"`/`role="tablist"`
151
151
  or `freeday-tabs.js`: those promise a roving-tabindex, arrow-key, one-panel-per-tab contract that
@@ -154,38 +154,42 @@ change with the layout:
154
154
  Reserve the full `.fdy-tabs` component (with its roles and its enhancer) for **in-page** tabs, where
155
155
  nothing navigates.
156
156
 
157
- **Toggles:** a button that is on/off carries `aria-pressed`, and the kit styles it soft primary
157
+ **Toggles:** a button that is on/off carries `aria-pressed`, and the kit styles it: soft primary
158
158
  fill on `--ghost`/`--text`, a sunken gradient on the solid button. A `.fdy-btn-group` of `--ghost`
159
159
  buttons with exactly one `aria-pressed="true"` **is** the segmented control; don't hand-tint it.
160
160
 
161
161
  ## 7. Assemble the page from the frame down
162
162
 
163
- 1. **Shell:** every application starts inside **`.fdy-app`** a flex row of `__sidebar` (with
163
+ 1. **Shell:** every application starts inside **`.fdy-app`**, a flex row of `__sidebar` (with
164
164
  `__brand` + `.fdy-nav`) and `__content` (which holds `__topbar` + `__main`, plus `__navtoggle`
165
- and `__backdrop`). The nesting is fixed; don't hand-roll a shell from flexbox the toggle and
166
- backdrop plumbing are already there. Skeleton: `COMPONENTS.md` §App shell; a working screen:
165
+ and `__backdrop`). The nesting is fixed; don't hand-roll a shell from flexbox. **Take the
166
+ behaviour too:** `<FdyAppShell>` in Vue/React/Blazor, or `data-fdy-app` plus
167
+ `freeday-app-shell.js` in plain markup. Until 1.53.0 the kit shipped the classes and left every
168
+ consumer to write the toggle, Escape, focus trap, focus restore and `inert` themselves, and the
169
+ two copies in this repo's own docs had already drifted apart; a nav panel you cannot escape from
170
+ with the keyboard is the usual result. Skeleton: `COMPONENTS.md` §App shell; a working screen:
167
171
  `docs/reference-screen.html`.
168
172
  2. **Page:** wrap the screen body in **`.fdy-page`** (vertical section rhythm), opening with a
169
173
  **`.fdy-page__header`** (eyebrow + `.fdy-title-page` + `.fdy-page__desc` on the left, the one
170
174
  primary action on the right).
171
175
  3. **Sections:** each region is a **`.fdy-page-section`** (a `.fdy-title-section` + optional
172
176
  `.fdy-toolbar`, then its body).
173
- **Toolbar or filter bar pick by whether the fields carry visible labels.** `.fdy-toolbar` is
177
+ **Toolbar or filter bar? Pick by whether the fields carry visible labels.** `.fdy-toolbar` is
174
178
  `align-items:center`, right for bare controls (buttons, a search box, chips); put a labelled
175
179
  `.fdy-field` in it and that field sits half a label-height low against its neighbours. Fields
176
180
  with visible labels belong in **`.fdy-filterbar`**, which is `align-items:flex-end` for exactly
177
181
  this reason (and has the width rhythm `--w-sm`…`--w-grow`). In a toolbar, label fields with
178
182
  `.fdy-visually-hidden` or a placeholder.
179
- 4. **KPIs:** a **`.fdy-stats`** grid of **`.fdy-stat`** tiles deliberately *not* cards, so a metric
183
+ 4. **KPIs:** a **`.fdy-stats`** grid of **`.fdy-stat`** tiles, deliberately *not* cards, so a metric
180
184
  strip doesn't become an identical-card grid. Wrap in `.fdy-stats--boxed` for one shared strip.
181
185
  5. **Content:** components (`.fdy-card`, `.fdy-datatable`, `.fdy-chart`, …) go inside sections.
182
186
 
183
187
  Freeday deliberately owns **components + tokens, not layout**. Everything above is layout in the kit's
184
188
  own language; for the rest (grids, one-off spacing), pair a utility framework run **utilities-only,
185
- preflight-off** and build its theme on `var(--space-N)` so the two systems agree. See
189
+ preflight-off**, and build its theme on `var(--space-N)` so the two systems agree. See
186
190
  `docs/getting-started.md` §Core concepts.
187
191
 
188
192
  ---
189
193
 
190
194
  *Layout classes here live in `src/components/composition.css`. If a screen needs a primitive that
191
- isn't here, it probably belongs here open an issue rather than re-inventing it per screen.*
195
+ isn't here, it probably belongs here. Open an issue rather than re-inventing it per screen.*
@@ -0,0 +1,50 @@
1
+ @namespace Freeday.Blazor
2
+ @inherits FreedayComponentBase<FdyAppShell>
3
+
4
+ @* Blazor wrapper over freeday's .fdy-app shell. Unlike FdyModal and FdyDrawer, which drive a native
5
+ <dialog> through the bridge, this one hands the whole behaviour to freeday-app-shell.js via
6
+ `data-fdy-app` and binds to it: the enhancer announces changes as a bubbling `fdy-app-nav` event
7
+ and accepts them through FreedayAppShell.setVisible. That is the bridge's own doctrine: the
8
+ enhancers stay the source of truth, and it keeps the focus trap from existing a third time. *@
9
+ <div @ref="Root" class="fdy-app" data-fdy-app>
10
+ @SkipContent
11
+
12
+ <aside class="fdy-app__sidebar">
13
+ @BrandContent
14
+ @NavContent
15
+ </aside>
16
+
17
+ <div class="fdy-app__content">
18
+ <header class="fdy-app__topbar">
19
+ <button class="fdy-app__navtoggle" type="button" aria-label="@ToggleLabel">
20
+ @if (ToggleIcon is not null)
21
+ {
22
+ @ToggleIcon
23
+ }
24
+ else
25
+ {
26
+ <text>&#9776;</text>
27
+ }
28
+ </button>
29
+
30
+ <h1 class="fdy-app__title">
31
+ @if (TitleContent is not null)
32
+ {
33
+ @TitleContent
34
+ }
35
+ else
36
+ {
37
+ @Title
38
+ }
39
+ </h1>
40
+
41
+ @TopbarContent
42
+ </header>
43
+
44
+ <main class="fdy-app__main">
45
+ @ChildContent
46
+ </main>
47
+ </div>
48
+
49
+ <div class="fdy-app__backdrop"></div>
50
+ </div>
@@ -0,0 +1,114 @@
1
+ using Microsoft.AspNetCore.Components;
2
+ using Microsoft.JSInterop;
3
+
4
+ namespace Freeday.Blazor;
5
+
6
+ public partial class FdyAppShell
7
+ {
8
+ /// <summary>
9
+ /// Whether the nav is visible to the reader, one idea, in both modes: above the nav breakpoint
10
+ /// a hidden nav is a collapsed column, below it a visible nav is an overlay. The kit owns that
11
+ /// mapping so an app never reasons about the viewport to answer a question about its own UI.
12
+ /// <para>
13
+ /// Leave it <c>null</c> (the default) and the shell keeps its own, starting from the viewport:
14
+ /// a column on a wide screen, hidden on a narrow one. That default is why this is nullable,
15
+ /// a caller cannot express it as a single initial value before it knows the viewport. Bind it
16
+ /// (<c>@bind-NavOpen</c>) when the app wants to drive it from a menu or a saved preference.
17
+ /// </para>
18
+ /// </summary>
19
+ [Parameter] public bool? NavOpen { get; set; }
20
+ [Parameter] public EventCallback<bool?> NavOpenChanged { get; set; }
21
+
22
+ /// <summary>Plain-text page title; ignored when <see cref="TitleContent"/> is supplied.</summary>
23
+ [Parameter] public string Title { get; set; } = string.Empty;
24
+ [Parameter] public RenderFragment? TitleContent { get; set; }
25
+
26
+ /// <summary>The brand block at the top of the sidebar.</summary>
27
+ [Parameter] public RenderFragment? BrandContent { get; set; }
28
+
29
+ /// <summary>The navigation itself, normally a <c>.fdy-nav</c> list.</summary>
30
+ [Parameter] public RenderFragment? NavContent { get; set; }
31
+
32
+ /// <summary>Topbar actions, rendered after the title.</summary>
33
+ [Parameter] public RenderFragment? TopbarContent { get; set; }
34
+
35
+ /// <summary>The skip link, if the app ships one. Rendered as the shell's first child.</summary>
36
+ [Parameter] public RenderFragment? SkipContent { get; set; }
37
+
38
+ /// <summary>The page itself.</summary>
39
+ [Parameter] public RenderFragment? ChildContent { get; set; }
40
+
41
+ /// <summary>Custom glyph for the nav toggle; a hamburger is used when null.</summary>
42
+ [Parameter] public RenderFragment? ToggleIcon { get; set; }
43
+
44
+ [Parameter] public string ToggleLabel { get; set; } = "Toggle navigation";
45
+
46
+ private int _navToken;
47
+ private bool? _lastNavOpen;
48
+
49
+ protected override async ValueTask HydrateAsync()
50
+ {
51
+ await JS.InvokeVoidAsync("FreedayBlazor.initAll", Root);
52
+ _navToken = await JS.InvokeAsync<int>(
53
+ "FreedayBlazor.on", Root, "fdy-app-nav", Self, nameof(OnNavChanged));
54
+
55
+ if (NavOpen is bool wanted)
56
+ {
57
+ // A caller that bound a value is authoritative, push it down.
58
+ await JS.InvokeVoidAsync("FreedayAppShell.setVisible", Root, wanted);
59
+ _lastNavOpen = wanted;
60
+ }
61
+ else
62
+ {
63
+ // Nobody bound one, so adopt what the shell decided from the viewport. Without this the
64
+ // bound value would claim `false` next to a nav that is plainly a visible column.
65
+ bool visible = await JS.InvokeAsync<bool>("FreedayAppShell.isVisible", Root);
66
+ _lastNavOpen = visible;
67
+ await SetNavOpenAsync(visible);
68
+ }
69
+ }
70
+
71
+ protected override async Task OnParametersSetAsync()
72
+ {
73
+ // Only reconcile once the shell is wired, and only when the caller actually changed it,
74
+ // echoing our own event back down would fight the enhancer for the same state.
75
+ if (_navToken != 0 && NavOpen is bool wanted && wanted != _lastNavOpen)
76
+ {
77
+ _lastNavOpen = wanted;
78
+ await JS.InvokeVoidAsync("FreedayAppShell.setVisible", Root, wanted);
79
+ }
80
+ }
81
+
82
+ /// <summary>Invoked by the bridge when the shell's nav visibility changes for any reason,
83
+ /// the toggle, Esc, the backdrop, following a nav link, or the viewport crossing the
84
+ /// breakpoint.</summary>
85
+ [JSInvokable]
86
+ public async Task OnNavChanged(NavDetail detail)
87
+ {
88
+ _lastNavOpen = detail.Visible;
89
+ await SetNavOpenAsync(detail.Visible);
90
+ }
91
+
92
+ private async Task SetNavOpenAsync(bool visible)
93
+ {
94
+ if (NavOpenChanged.HasDelegate)
95
+ {
96
+ await NavOpenChanged.InvokeAsync(visible);
97
+ }
98
+ else
99
+ {
100
+ NavOpen = visible;
101
+ StateHasChanged();
102
+ }
103
+ }
104
+
105
+ protected override async ValueTask CleanupAsync()
106
+ {
107
+ if (_navToken != 0)
108
+ {
109
+ await JS.InvokeVoidAsync("FreedayBlazor.off", _navToken);
110
+ }
111
+ }
112
+
113
+ public sealed record NavDetail(bool Visible);
114
+ }
@@ -195,7 +195,7 @@ public partial class FdyCfl<TRow>
195
195
  else _picked.RemoveAt(at);
196
196
  }
197
197
 
198
- /* A click means "tick this" in multi and "this is my answer" in single the whole difference. */
198
+ /* A click means "tick this" in multi and "this is my answer" in single, the whole difference. */
199
199
  private async Task RowClickAsync(TRow row)
200
200
  {
201
201
  if (Multiple) TogglePick(row);
@@ -3,7 +3,7 @@
3
3
  @* A thin Blazor wrapper over freeday's zero-dependency chart renderer (freeday-chart.js). Charts
4
4
  are not form controls, so re-implementing the SVG maths in C# would only duplicate them. Instead
5
5
  Blazor owns the outer <div> and its data-* attributes; after every render this calls
6
- FreedayChart.update() (via the bridge) to repaint the SVG from those attributes giving reactive,
6
+ FreedayChart.update() (via the bridge) to repaint the SVG from those attributes, giving reactive,
7
7
  typed charts with no external dependency. Because the renderer paints with CSS var()/--chart-*
8
8
  tokens, charts also recolour on data-theme change for free. Requires dist/freeday.js loaded so
9
9
  window.FreedayChart exists; until then the slotted fallback content stays visible. *@
@@ -32,7 +32,7 @@ public partial class FdyChart : ComponentBase
32
32
  /// <summary>Legend visibility: <c>auto</c> | <c>always</c> | <c>none</c>.</summary>
33
33
  [Parameter] public string? Legend { get; set; }
34
34
 
35
- /// <summary>Per-series colour override semantic token names (primary/accent/success/…) or
35
+ /// <summary>Per-series colour override, semantic token names (primary/accent/success/…) or
36
36
  /// categorical slots <c>chart-1</c>..<c>chart-8</c>. Omit for the default chart palette.</summary>
37
37
  [Parameter] public IReadOnlyList<string>? Colors { get; set; }
38
38
 
@@ -72,14 +72,14 @@ public partial class FdyChart : ComponentBase
72
72
  private string? ColorsAttr => Colors is null ? null : string.Join(",", Colors);
73
73
  private string? StackedAttr => Stacked ? string.Empty : null; // presence attribute
74
74
 
75
- // Signature of every data-affecting attribute the C# analogue of the Vue/React watch list.
75
+ // Signature of every data-affecting attribute, the C# analogue of the Vue/React watch list.
76
76
  private string DataSignature => string.Join("", Type, SeriesAttr, ValuesAttr, LabelsAttr,
77
77
  Format, StackedAttr, Legend, ColorsAttr, Color, Center);
78
78
 
79
79
  private string? _lastSignature;
80
80
 
81
81
  // Repaint only when the chart's own data changed (first render, or a data-* attribute differs
82
- // since the last paint) so an unrelated parent re-render doesn't needlessly repaint the SVG.
82
+ // since the last paint), so an unrelated parent re-render doesn't needlessly repaint the SVG.
83
83
  protected override async Task OnAfterRenderAsync(bool firstRender)
84
84
  {
85
85
  string sig = DataSignature;
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
3
3
  namespace Freeday.Blazor;
4
4
 
5
5
  /// <summary>One data series in a multi-series chart (line/area/bar). Serialised to the renderer's
6
- /// <c>data-series</c> JSON shape <c>{ label, values, role? }</c> hence the camelCase names.</summary>
6
+ /// <c>data-series</c> JSON shape <c>{ label, values, role? }</c>, hence the camelCase names.</summary>
7
7
  public sealed record FdyChartSeries(
8
8
  [property: JsonPropertyName("label")] string Label,
9
9
  [property: JsonPropertyName("values")] IReadOnlyList<double> Values,
@@ -34,7 +34,7 @@ public partial class FdyCombo<TValue>
34
34
  private bool _ready;
35
35
 
36
36
  // The enhancer owns this combo's DOM after hydration (open/close, aria-selected, the button
37
- // label). Re-rendering here would fight it e.g. reconciling the listbox mid-close leaves it
37
+ // label). Re-rendering here would fight it, e.g. reconciling the listbox mid-close leaves it
38
38
  // stuck open. Render once for the initial markup, then never again; push external Value changes
39
39
  // through the enhancer via comboSetValue instead.
40
40
  protected override bool ShouldRender() => !Hydrated;
@@ -60,7 +60,7 @@ public partial class FdyCombo<TValue>
60
60
 
61
61
  protected override async Task OnParametersSetAsync()
62
62
  {
63
- // Push an externally-changed Value onto the enhancer-owned DOM (silent no fdy-change echo).
63
+ // Push an externally-changed Value onto the enhancer-owned DOM (silent, no fdy-change echo).
64
64
  if (_ready && !EqualityComparer<TValue>.Default.Equals(Value, _lastValue))
65
65
  {
66
66
  _lastValue = Value;
@@ -4,7 +4,7 @@
4
4
  @* Controlled wrapper over freeday's .fdy-modal native <dialog>. Blazor can't call
5
5
  showModal()/close() from C#, so open/close + Esc/backdrop dismissal route through the
6
6
  freeday-blazor.js bridge. Native <dialog> gives the focus trap, focus restore, top-layer
7
- stacking and inert background for free this only reconciles it with Blazor state. *@
7
+ stacking and inert background for free; this only reconciles it with Blazor state. *@
8
8
  <dialog @ref="Root" class="@ModalClass" aria-labelledby="@_titleId">
9
9
  <div class="fdy-modal__header">
10
10
  <h3 id="@_titleId" class="fdy-modal__title">
@@ -30,8 +30,8 @@ public partial class FdyTable<TRow>
30
30
  [Parameter] public int PageSize { get; set; }
31
31
 
32
32
  /// <summary>
33
- /// Controlled client-side page index (0-based). Set it with <see cref="PageSize"/>, without
34
- /// <see cref="Page"/> to own the page while the table keeps doing filter/sort/paginate. That is
33
+ /// Controlled client-side page index (0-based). Set it, with <see cref="PageSize"/>, without
34
+ /// <see cref="Page"/>, to own the page while the table keeps doing filter/sort/paginate. That is
35
35
  /// what lets an EXTERNAL pager drive the table: a responsive screen that hides the datatable below
36
36
  /// the <c>md</c> breakpoint and renders a card list from <see cref="Process"/> can render one pager
37
37
  /// for both breakpoints and bind it here. Leave null for the internal index (unchanged default).
@@ -45,14 +45,14 @@ public partial class FdyTable<TRow>
45
45
  /// Offer a rows-per-page control in the footer, beside the range and the pager. Leave null for
46
46
  /// none (unchanged default). Every back office has one, and a table that renders two thirds of
47
47
  /// its own footer forces the app to rebuild all three to add the last (#008).
48
- /// Server mode reports the pick through <see cref="PageChanged"/> same callback as a page
48
+ /// Server mode reports the pick through <see cref="PageChanged"/>, same callback as a page
49
49
  /// click, with a new size. Client mode applies it internally and also raises
50
50
  /// <see cref="PageSizeChanged"/>, so the control works with nothing wired.
51
51
  /// </summary>
52
52
  [Parameter] public IReadOnlyList<int>? PageSizes { get; set; }
53
53
 
54
54
  /// <summary>Raised in client mode when the reader picks a new rows-per-page. The table has
55
- /// already applied it this is for a caller that wants to persist the choice.</summary>
55
+ /// already applied it, this is for a caller that wants to persist the choice.</summary>
56
56
  [Parameter] public EventCallback<int> PageSizeChanged { get; set; }
57
57
 
58
58
  [Parameter] public int? PageIndex { get; set; }
@@ -62,7 +62,7 @@ public partial class FdyTable<TRow>
62
62
  [Parameter] public EventCallback<int> PageIndexChanged { get; set; }
63
63
 
64
64
  /// <summary>Raised whenever the processed page of rows (after filter/sort/paginate) or the total
65
- /// changes, in BOTH modes so the same processed set can drive a card list, a summary or an
65
+ /// changes, in BOTH modes, so the same processed set can drive a card list, a summary or an
66
66
  /// export without re-deriving the pipeline. Mirrors <c>process</c> in the Vue/React adapters.</summary>
67
67
  [Parameter] public EventCallback<FdyTableProcess<TRow>> Process { get; set; }
68
68
 
@@ -113,7 +113,7 @@ public partial class FdyTable<TRow>
113
113
  FiltersControlled ? (Filters ?? EmptyFilters) : _internalFilters;
114
114
 
115
115
  /* Client-mode rows-per-page. PageSize is a plain parameter with no callback, so a footer control
116
- * that only reported would do nothing in the app that wired nothing the table applies the pick
116
+ * that only reported would do nothing in the app that wired nothing, the table applies the pick
117
117
  * itself and reports it. An explicit change to the parameter wins back (see OnParametersSet). */
118
118
  private int? _internalPageSize;
119
119
  private int _prevPageSize;
@@ -298,7 +298,7 @@ public partial class FdyTable<TRow>
298
298
 
299
299
  // Enter/Space activate a focused row. Blazor's KeyboardEventArgs can't tell whether the row
300
300
  // itself vs an inner control is the target, so activatable rows should not embed focusable
301
- // controls (the common drill-in-row pattern) matching the Vue guard's practical effect.
301
+ // controls (the common drill-in-row pattern), matching the Vue guard's practical effect.
302
302
  private async Task OnRowKeydown(KeyboardEventArgs e, TRow row)
303
303
  {
304
304
  if (!RowActivatable) return;
@@ -3,7 +3,7 @@
3
3
  @* Internal to FdyTable: one column's header funnel button + its type-aware filter popover
4
4
  (text / enum / number / date), over freeday's .fdy-table__filterbtn + .fdy-filter* classes.
5
5
  The panel is position:fixed (top layer) so it escapes the table's overflow; light-dismiss
6
- (outside pointer / Esc) is wired through FreedayBlazor.onOutside. Purely controlled it renders
6
+ (outside pointer / Esc) is wired through FreedayBlazor.onOutside. Purely controlled: it renders
7
7
  the current Filter and raises the next one (or null to clear). *@
8
8
  <span @ref="_root" class="fdy-table__filterwrap">
9
9
  <button type="button"
@@ -1,7 +1,7 @@
1
1
  @* The band under a table: what you are looking at, how much of it you see, where you are.
2
2
 
3
3
  FdyTable renders this itself, so most screens never use it directly. It is public for the one
4
- shape that cannot use the table's own a RESPONSIVE list, where a .fdy-datatable at lg and a
4
+ shape that cannot use the table's own: a RESPONSIVE list, where a .fdy-datatable at lg and a
5
5
  .fdy-list below it are two renderings of ONE page of rows. A footer inside the table is inside the
6
6
  half that is hidden on a phone, so those screens render it once, outside both (improvement notes
7
7
  #005 and #008, from IDU_EMATE_APPL_WEB). *@
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Components;
3
3
  namespace Freeday.Blazor;
4
4
 
5
5
  /// <summary>
6
- /// The table footer range, optional rows-per-page, pager as a component of its own.
6
+ /// The table footer, range, optional rows-per-page, pager, as a component of its own.
7
7
  /// It owns nothing: <see cref="Page"/> in, <see cref="PageChanged"/> out, the same contract as
8
8
  /// <c>FdyTable</c>'s server mode.
9
9
  /// </summary>
@@ -15,7 +15,7 @@ public partial class FdyTableFooter
15
15
  [Parameter] public EventCallback<FdyPageState> PageChanged { get; set; }
16
16
 
17
17
  /// <summary>
18
- /// Offer a rows-per-page control. Leave null for none the footer is then range + pager,
18
+ /// Offer a rows-per-page control. Leave null for none; the footer is then range + pager,
19
19
  /// exactly as before. Picking a size raises <see cref="PageChanged"/> with the new size and the
20
20
  /// index that still holds the row the reader was looking at.
21
21
  /// </summary>
@@ -28,7 +28,7 @@ public partial class FdyTableFooter
28
28
  private IReadOnlyList<FdyComboOption<string>> SizeOptions =>
29
29
  Sizes.Select(size => new FdyComboOption<string>(size.ToString(), size.ToString())).ToList();
30
30
 
31
- // The combo is labelled by the visible word beside it a <label for> cannot reach inside a
31
+ // The combo is labelled by the visible word beside it, a <label for> cannot reach inside a
32
32
  // component, and an aria-label would leave that word attached to nothing.
33
33
  private readonly string SizeLabelId = $"fdy-rows-{Guid.NewGuid():N}";
34
34
 
@@ -40,7 +40,7 @@ public partial class FdyTableFooter
40
40
 
41
41
  private bool HasPager => Page.Size > 0 && TotalPages > 1;
42
42
 
43
- // One page and no size control means there is nothing here to say the table has always
43
+ // One page and no size control means there is nothing here to say, the table has always
44
44
  // withheld the whole band in that case, and this is where that decision now lives.
45
45
  private bool Visible => HasPager || Sizes.Count > 0;
46
46
 
@@ -1,4 +1,4 @@
1
- <!-- Freeday Blazor component library (Razor Class Library).
1
+ <!-- Freeday. Blazor component library (Razor Class Library).
2
2
  Typed wrappers over the vanilla `fdy-*` enhancers; the host page must load
3
3
  dist/freeday.js + adapters/blazor/freeday-blazor.js. Targets net8.0 (LTS) so it can
4
4
  be referenced from net8/9/10 apps. Consume via <ProjectReference> to this .csproj
@@ -36,7 +36,7 @@ public abstract class FreedayComponentBase<TSelf> : ComponentBase, IAsyncDisposa
36
36
  /// registered enhancer over <see cref="Root"/>; override to call a specific enhancer.</summary>
37
37
  protected virtual ValueTask HydrateAsync() => JS.InvokeVoidAsync("FreedayBlazor.initAll", Root);
38
38
 
39
- /// <summary>Runs once after the first render, right after <see cref="HydrateAsync"/> wire event
39
+ /// <summary>Runs once after the first render, right after <see cref="HydrateAsync"/>, wire event
40
40
  /// subscriptions here.</summary>
41
41
  protected virtual ValueTask OnHydratedAsync() => ValueTask.CompletedTask;
42
42
 
@@ -6,7 +6,7 @@ using System.Text.RegularExpressions;
6
6
  namespace Freeday.Blazor;
7
7
 
8
8
  /// <summary>
9
- /// The sort / filter / paginate logic for <see cref="FdyTable{TRow}"/> the C# counterpart of the
9
+ /// The sort / filter / paginate logic for <see cref="FdyTable{TRow}"/>, the C# counterpart of the
10
10
  /// framework-agnostic adapters/core/table-model.js shared by the Vue and React adapters. Kept as a
11
11
  /// separate pure-function class (no rendering, no state) so it mirrors that reference faithfully and
12
12
  /// stays unit-testable. Every method is pure: inputs are never mutated (rows are copied before sort).
@@ -40,7 +40,7 @@ public static class TableModel
40
40
  : 0;
41
41
  }
42
42
 
43
- /// <summary>ISO calendar day (yyyy-MM-dd) lexicographically comparable.</summary>
43
+ /// <summary>ISO calendar day (yyyy-MM-dd), lexicographically comparable.</summary>
44
44
  private static string DateOnly(object? v)
45
45
  {
46
46
  if (v is DateTime dt) return dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
@@ -167,7 +167,7 @@ public static class TableModel
167
167
  return rows.Skip(pageIndex * pageSize).Take(pageSize).ToList();
168
168
  }
169
169
 
170
- /// <summary>Distinct non-empty cell texts for a column, naturally sorted the enum-filter source.</summary>
170
+ /// <summary>Distinct non-empty cell texts for a column, naturally sorted, the enum-filter source.</summary>
171
171
  public static List<string> DistinctValues<TRow>(IReadOnlyList<TRow> rows, FdyTableColumn<TRow> column)
172
172
  {
173
173
  HashSet<string> seen = new();