@namroodinc/liquid-editorial 0.1.5 → 0.1.7

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 (168) hide show
  1. package/AGENTS.md +2 -0
  2. package/README.md +149 -4
  3. package/UPGRADING.md +127 -4
  4. package/dist/components/AppShell/index.cjs +2 -0
  5. package/dist/components/AppShell/index.cjs.map +1 -0
  6. package/dist/components/AppShell/index.d.cts +15 -0
  7. package/dist/components/AppShell/index.d.cts.map +1 -0
  8. package/dist/components/AppShell/index.d.ts +15 -0
  9. package/dist/components/AppShell/index.d.ts.map +1 -0
  10. package/dist/components/AppShell/index.js +93 -0
  11. package/dist/components/AppShell/index.js.map +1 -0
  12. package/dist/components/AppShell/style.css +79 -0
  13. package/dist/components/AssetGrid/index.cjs +1 -1
  14. package/dist/components/AssetGrid/index.cjs.map +1 -1
  15. package/dist/components/AssetGrid/index.d.cts.map +1 -1
  16. package/dist/components/AssetGrid/index.d.ts.map +1 -1
  17. package/dist/components/AssetGrid/index.js +57 -59
  18. package/dist/components/AssetGrid/index.js.map +1 -1
  19. package/dist/components/AssetGrid/style.css +8 -16
  20. package/dist/components/Board/index.cjs +1 -1
  21. package/dist/components/Board/index.cjs.map +1 -1
  22. package/dist/components/Board/index.d.cts.map +1 -1
  23. package/dist/components/Board/index.d.ts.map +1 -1
  24. package/dist/components/Board/index.js +88 -84
  25. package/dist/components/Board/index.js.map +1 -1
  26. package/dist/components/Board/style.css +17 -28
  27. package/dist/components/Button/index.cjs +1 -1
  28. package/dist/components/Button/index.cjs.map +1 -1
  29. package/dist/components/Button/index.d.cts +3 -2
  30. package/dist/components/Button/index.d.cts.map +1 -1
  31. package/dist/components/Button/index.d.ts +3 -2
  32. package/dist/components/Button/index.d.ts.map +1 -1
  33. package/dist/components/Button/index.js +1 -1
  34. package/dist/components/Button/index.js.map +1 -1
  35. package/dist/components/Button/style.css +21 -15
  36. package/dist/components/DatePicker/index.cjs +1 -1
  37. package/dist/components/DatePicker/index.cjs.map +1 -1
  38. package/dist/components/DatePicker/index.d.cts.map +1 -1
  39. package/dist/components/DatePicker/index.d.ts.map +1 -1
  40. package/dist/components/DatePicker/index.js +46 -45
  41. package/dist/components/DatePicker/index.js.map +1 -1
  42. package/dist/components/DatePicker/style.css +4 -61
  43. package/dist/components/Drawer/index.cjs +1 -1
  44. package/dist/components/Drawer/index.cjs.map +1 -1
  45. package/dist/components/Drawer/index.d.cts +10 -1
  46. package/dist/components/Drawer/index.d.cts.map +1 -1
  47. package/dist/components/Drawer/index.d.ts +10 -1
  48. package/dist/components/Drawer/index.d.ts.map +1 -1
  49. package/dist/components/Drawer/index.js +69 -47
  50. package/dist/components/Drawer/index.js.map +1 -1
  51. package/dist/components/Drawer/style.css +106 -0
  52. package/dist/components/EntryPicker/index.cjs +1 -1
  53. package/dist/components/EntryPicker/index.cjs.map +1 -1
  54. package/dist/components/EntryPicker/index.d.cts.map +1 -1
  55. package/dist/components/EntryPicker/index.d.ts.map +1 -1
  56. package/dist/components/EntryPicker/index.js +12 -11
  57. package/dist/components/EntryPicker/index.js.map +1 -1
  58. package/dist/components/EntryPicker/style.css +3 -30
  59. package/dist/components/Field/style.css +11 -3
  60. package/dist/components/ImageCropper/index.cjs +1 -1
  61. package/dist/components/ImageCropper/index.cjs.map +1 -1
  62. package/dist/components/ImageCropper/index.d.cts +3 -17
  63. package/dist/components/ImageCropper/index.d.cts.map +1 -1
  64. package/dist/components/ImageCropper/index.d.ts +3 -17
  65. package/dist/components/ImageCropper/index.d.ts.map +1 -1
  66. package/dist/components/ImageCropper/index.js +143 -180
  67. package/dist/components/ImageCropper/index.js.map +1 -1
  68. package/dist/components/ImageCropper/style.css +2 -94
  69. package/dist/components/Menu/index.cjs +1 -1
  70. package/dist/components/Menu/index.cjs.map +1 -1
  71. package/dist/components/Menu/index.d.cts.map +1 -1
  72. package/dist/components/Menu/index.d.ts.map +1 -1
  73. package/dist/components/Menu/index.js +11 -16
  74. package/dist/components/Menu/index.js.map +1 -1
  75. package/dist/components/Menu/style.css +7 -41
  76. package/dist/components/Modal/index.cjs +1 -1
  77. package/dist/components/Modal/index.cjs.map +1 -1
  78. package/dist/components/Modal/index.d.cts.map +1 -1
  79. package/dist/components/Modal/index.d.ts.map +1 -1
  80. package/dist/components/Modal/index.js +12 -12
  81. package/dist/components/Modal/index.js.map +1 -1
  82. package/dist/components/NumberField/index.cjs +1 -1
  83. package/dist/components/NumberField/index.cjs.map +1 -1
  84. package/dist/components/NumberField/index.js +13 -17
  85. package/dist/components/NumberField/index.js.map +1 -1
  86. package/dist/components/NumberField/style.css +1 -41
  87. package/dist/components/Pagination/index.cjs +2 -0
  88. package/dist/components/Pagination/index.cjs.map +1 -0
  89. package/dist/components/Pagination/index.d.cts +24 -0
  90. package/dist/components/Pagination/index.d.cts.map +1 -0
  91. package/dist/components/Pagination/index.d.ts +24 -0
  92. package/dist/components/Pagination/index.d.ts.map +1 -0
  93. package/dist/components/Pagination/index.js +96 -0
  94. package/dist/components/Pagination/index.js.map +1 -0
  95. package/dist/components/Pagination/style.css +52 -0
  96. package/dist/components/Popover/index.cjs +1 -1
  97. package/dist/components/Popover/index.cjs.map +1 -1
  98. package/dist/components/Popover/index.d.cts +5 -1
  99. package/dist/components/Popover/index.d.cts.map +1 -1
  100. package/dist/components/Popover/index.d.ts +5 -1
  101. package/dist/components/Popover/index.d.ts.map +1 -1
  102. package/dist/components/Popover/index.js +27 -26
  103. package/dist/components/Popover/index.js.map +1 -1
  104. package/dist/components/SearchField/index.cjs +1 -1
  105. package/dist/components/SearchField/index.cjs.map +1 -1
  106. package/dist/components/SearchField/index.d.cts.map +1 -1
  107. package/dist/components/SearchField/index.d.ts.map +1 -1
  108. package/dist/components/SearchField/index.js +26 -27
  109. package/dist/components/SearchField/index.js.map +1 -1
  110. package/dist/components/SearchField/style.css +0 -21
  111. package/dist/components/Select/index.cjs +1 -1
  112. package/dist/components/Select/index.cjs.map +1 -1
  113. package/dist/components/Select/index.d.cts +3 -0
  114. package/dist/components/Select/index.d.cts.map +1 -1
  115. package/dist/components/Select/index.d.ts +3 -0
  116. package/dist/components/Select/index.d.ts.map +1 -1
  117. package/dist/components/Select/index.js +82 -73
  118. package/dist/components/Select/index.js.map +1 -1
  119. package/dist/components/Select/style.css +5 -38
  120. package/dist/components/SideNav/index.cjs +1 -1
  121. package/dist/components/SideNav/index.cjs.map +1 -1
  122. package/dist/components/SideNav/index.d.cts.map +1 -1
  123. package/dist/components/SideNav/index.d.ts.map +1 -1
  124. package/dist/components/SideNav/index.js +52 -49
  125. package/dist/components/SideNav/index.js.map +1 -1
  126. package/dist/components/SideNav/style.css +2 -27
  127. package/dist/components/Slider/style.css +14 -5
  128. package/dist/components/SortableList/index.cjs +1 -1
  129. package/dist/components/SortableList/index.cjs.map +1 -1
  130. package/dist/components/SortableList/index.d.cts.map +1 -1
  131. package/dist/components/SortableList/index.d.ts.map +1 -1
  132. package/dist/components/SortableList/index.js +42 -39
  133. package/dist/components/SortableList/index.js.map +1 -1
  134. package/dist/components/SortableList/style.css +9 -15
  135. package/dist/components/Stepper/index.cjs +1 -1
  136. package/dist/components/Stepper/index.cjs.map +1 -1
  137. package/dist/components/Stepper/index.d.cts.map +1 -1
  138. package/dist/components/Stepper/index.d.ts.map +1 -1
  139. package/dist/components/Stepper/index.js +48 -39
  140. package/dist/components/Stepper/index.js.map +1 -1
  141. package/dist/components/Stepper/style.css +24 -18
  142. package/dist/components/SubNav/index.cjs +1 -1
  143. package/dist/components/SubNav/index.cjs.map +1 -1
  144. package/dist/components/SubNav/index.d.cts.map +1 -1
  145. package/dist/components/SubNav/index.d.ts.map +1 -1
  146. package/dist/components/SubNav/index.js +27 -28
  147. package/dist/components/SubNav/index.js.map +1 -1
  148. package/dist/components/SubNav/style.css +13 -0
  149. package/dist/components/Switch/index.cjs +1 -1
  150. package/dist/components/Switch/index.cjs.map +1 -1
  151. package/dist/components/Switch/index.d.cts.map +1 -1
  152. package/dist/components/Switch/index.d.ts.map +1 -1
  153. package/dist/components/Switch/index.js +23 -21
  154. package/dist/components/Switch/index.js.map +1 -1
  155. package/dist/components/Switch/style.css +2 -28
  156. package/dist/components/TagsInput/index.cjs +1 -1
  157. package/dist/components/TagsInput/index.cjs.map +1 -1
  158. package/dist/components/TagsInput/index.js +8 -10
  159. package/dist/components/TagsInput/index.js.map +1 -1
  160. package/dist/components/TagsInput/style.css +2 -24
  161. package/dist/index.cjs +1 -1
  162. package/dist/index.d.cts +7 -3
  163. package/dist/index.d.cts.map +1 -1
  164. package/dist/index.d.ts +7 -3
  165. package/dist/index.d.ts.map +1 -1
  166. package/dist/index.js +36 -34
  167. package/dist/styles.css +2 -0
  168. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  Add a decision document for every change going forward.
6
6
 
7
+ Render button controls through the shared `Button` component, including controls inside other components and examples. Native button markup belongs only in `src/components/Button` (isolated test fixtures may use native controls). Use Button's standard variants, sizes, icons, focus, and disabled styling instead of copying them into a component stylesheet. Specialized widgets may retain their semantic layout, shared Field skin, or switch track/knob geometry.
8
+
7
9
  Every releasable change set must update the `Unreleased` section in `UPGRADING.md` with changes, adoption steps, compatibility notes, and verification steps for agents in consuming projects. Related edits share that section. Leave version increments to `.github/workflows/release.yml`: every push to `main` automatically bumps the patch version, versions the upgrade notes, validates, commits the release files, and publishes to npm. Keep the manifest and lockfile versions aligned and include this file and `UPGRADING.md` in the package.
8
10
 
9
11
  ## Using the installed package
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Liquid Editorial
2
2
 
3
- Accessible React components for editorial products. The package ships ESM, CommonJS, TypeScript declarations, and explicit CSS entry points. It does not inject styles from JavaScript.
3
+ Accessible React components for editorial products. The package ships ESM, CommonJS, TypeScript declarations, and explicit CSS entry points. Styles use those CSS entry points; the library does not inject styles through JavaScript.
4
4
 
5
5
  ## Install
6
6
 
@@ -35,6 +35,16 @@ Every family also has a direct entry point:
35
35
  import { Button } from "@namroodinc/liquid-editorial/components/Button";
36
36
  ```
37
37
 
38
+ Use `Button` for action controls, including controls rendered inside other components. Its variants, sizes, icons, focus rings, and disabled states supply the shared appearance. Use `size="xs"` for compact actions inside a field or chip, such as clear and remove. For a custom square glyph, omit `iconStart` and provide visual children plus an accessible name:
39
+
40
+ ```tsx
41
+ <Button variant="simple" isIconOnly aria-label="Account: Ada Lovelace">
42
+ <Avatar name="Ada Lovelace" size={32} aria-hidden="true" />
43
+ </Button>
44
+ ```
45
+
46
+ Pending Buttons replace custom glyphs with the shared spinner. Button-backed selects and date fields retain the shared Field skin; Switch retains its track and knob while composing Button.
47
+
38
48
  If an application only needs the design tokens, import `@namroodinc/liquid-editorial/tokens.css`. A small global normalization is available separately at `@namroodinc/liquid-editorial/reset.css`; it is never included automatically.
39
49
 
40
50
  ## Content models
@@ -43,7 +53,12 @@ If an application only needs the design tokens, import `@namroodinc/liquid-edito
43
53
 
44
54
  ```tsx
45
55
  import { useState } from "react";
46
- import { EntryEditor, validateEntryValues, type ContentModel, type EntryValues } from "@namroodinc/liquid-editorial";
56
+ import {
57
+ EntryEditor,
58
+ validateEntryValues,
59
+ type ContentModel,
60
+ type EntryValues,
61
+ } from "@namroodinc/liquid-editorial";
47
62
 
48
63
  const model: ContentModel = {
49
64
  fields: [
@@ -54,8 +69,14 @@ const model: ContentModel = {
54
69
 
55
70
  export function ContentEditor() {
56
71
  const [value, setValue] = useState<EntryValues>({});
57
- return <EntryEditor model={model} value={value} onValueChange={setValue}
58
- pickerItems={{ gallery: [{ id: "image-1", title: "Cover.jpg" }] }} />;
72
+ return (
73
+ <EntryEditor
74
+ model={model}
75
+ value={value}
76
+ onValueChange={setValue}
77
+ pickerItems={{ gallery: [{ id: "image-1", title: "Cover.jpg" }] }}
78
+ />
79
+ );
59
80
  }
60
81
 
61
82
  // Before saving: validateEntryValues(model, value) returns errors keyed by field ID.
@@ -73,6 +94,130 @@ Links open a URL modal from the toolbar or by clicking an existing body link. Sa
73
94
 
74
95
  Empty `TableBody` children automatically show “No data found.” across the table; override the text with `Table.emptyMessage`. Use `isEmpty` when custom row components determine whether there are results, or set it to `false` to suppress the message while the UI loads data. Loading indicators and skeleton rows are composed by the consuming UI. Standalone `TableEmpty` uses the same default message and supports a custom `title`. See the [upgrade guide example](UPGRADING.md#013).
75
96
 
97
+ ## App shell
98
+
99
+ `AppShell` places a full-width `TopNav` above the desktop `SideNav` and a scrolling `main` landmark. Below 768px of **shell width**, a hamburger opens navigation in a right-side drawer. Its default height is `100dvh`; native `style`, `className`, div attributes, and a forwarded ref allow it to fit an embedded frame.
100
+
101
+ ```tsx
102
+ import { AppShell, Avatar, SideNavItem, SideNavSection } from "@namroodinc/liquid-editorial";
103
+
104
+ <AppShell
105
+ brand={<a href="/">Liquid</a>}
106
+ navigation={<>
107
+ <SideNavSection label="Workspace">
108
+ <SideNavItem icon="home" label="Home" href="/" />
109
+ <SideNavItem icon="file-text" label="Stories" href="/stories" isActive />
110
+ </SideNavSection>
111
+ </>}
112
+ actions={(location) => <>
113
+ <Avatar name="Ada Lovelace" size={32} />
114
+ {location === "drawer" && <span>Ada Lovelace</span>}
115
+ </>}
116
+ >
117
+ <h1>Stories</h1>
118
+ </AppShell>
119
+ ```
120
+
121
+ Pass sections/items as `navigation`, without an enclosing SideNav, header, footer, or collapse control. Applications own destinations and `isActive`; AppShell closes the drawer when a navigation link is activated. `navigationLabel` defaults to `"Main"`. Keep shared application state outside the responsive slots and use React `useId` inside custom slot components that need IDs, since the desktop and drawer presentations can coexist during transitions.
122
+
123
+ The optional `actions` render function receives `"topnav"` or `"drawer"`. Return a compact desktop control and an expanded mobile presentation; return nothing to omit the drawer footer. Keep this function pure. The component supplies no account, menu destinations, centre navigation, or space picker.
124
+
125
+ The drawer traps focus and locks body scroll. Escape, Close, the scrim, and navigation links dismiss it and return focus to the hamburger. A popup inside it, such as a Menu in the actions footer, closes first on Escape. Modifier-key clicks that open a link elsewhere leave the drawer open. Widening the shell to desktop closes it and moves focus to the active desktop link when focus was inside the drawer. Reduced motion skips the transition.
126
+
127
+ For other navigation compositions, `Drawer` supports `variant="navigation"`, a React-node `title`, `closeLabel`, and `portalContainer`. By default the drawer portals to `document.body` and covers the viewport; pass a positioned element to keep it inside that element. Supply `aria-label` for a dialog name independent of its visual brand. This variant retains an inert, accessibility-hidden panel during its closing transition; the default Drawer keeps its existing appearance and immediate unmounting.
128
+
129
+ ## Image cropping
130
+
131
+ `ImageCropper` uses the library's custom engine with a fixed crop frame, diagonal background, shared `Slider` with a `Field` label/hint, and optional quarter-turn controls. Shared `Stack` components put the rotate/Reset buttons to the right of the zoom slider and wrap them below on narrow layouts. Supply one positive, finite `aspect` (width / height): `1` for an avatar, `16 / 9` for a cover, or `4 / 5` for a portrait.
132
+
133
+ ```tsx
134
+ import { useState } from "react";
135
+ import {
136
+ Button,
137
+ ImageCropper,
138
+ cropToBlob,
139
+ type CropRect,
140
+ type CropState,
141
+ } from "@namroodinc/liquid-editorial";
142
+
143
+ function CoverCropper({
144
+ src,
145
+ onSave,
146
+ }: {
147
+ src: string;
148
+ onSave: (blob: Blob) => void;
149
+ }) {
150
+ const [selection, setSelection] = useState<{
151
+ crop: CropRect;
152
+ state: CropState;
153
+ } | null>(null);
154
+
155
+ async function save() {
156
+ if (!selection) return;
157
+ const blob = await cropToBlob(src, selection.crop, {
158
+ outputWidth: 1600,
159
+ rotation: selection.state.rotation,
160
+ type: "image/jpeg",
161
+ });
162
+ onSave(blob);
163
+ }
164
+
165
+ return (
166
+ <>
167
+ <ImageCropper
168
+ src={src}
169
+ alt="Article cover"
170
+ aspect={16 / 9}
171
+ onChange={(crop, state) => setSelection({ crop, state })}
172
+ />
173
+ <Button isDisabled={!selection} onClick={save}>
174
+ Save crop
175
+ </Button>
176
+ </>
177
+ );
178
+ }
179
+ ```
180
+
181
+ Drag the image or focus the stage and use arrow keys (Shift for a larger step). The image always covers the fixed crop window; panning, zooming out, resizing, and rotation keep the crop inside the image. Zoom defaults to a maximum of `3`; set `maxZoom` to change it. Rotation is enabled by default; set `rotatable={false}` to hide its buttons. Reset clears pan, zoom, and rotation while retaining the supplied aspect.
182
+
183
+ Changing `aspect` resets pan and zoom while preserving rotation. There is no ratio picker or implicit square default. `onChange(crop, state)` reports image-pixel coordinates plus pan, zoom, and quarter-turn rotation. `cropToBlob` supports same-origin or CORS-enabled sources and remains usable after unmounting with the saved crop/state. The forwarded ref and native HTML attributes apply to the outer container.
184
+
185
+ Cropper.js has been removed; no third-party cropper initialization or styles are needed. Keep importing the library's `styles.css`. See [UPGRADING.md](UPGRADING.md#unreleased) for migration from the temporary native Cropper.js API or the earlier shape-picker API.
186
+
187
+ ## Pagination
188
+
189
+ `Pagination` is controlled: keep the page and page size together in application state, and apply the single change callback. Import it from the package root or `@namroodinc/liquid-editorial/components/Pagination`.
190
+
191
+ ```tsx
192
+ import { useState } from "react";
193
+ import { Pagination, type PaginationState } from "@namroodinc/liquid-editorial";
194
+
195
+ export function ArticlePagination({ totalResults }: { totalResults: number }) {
196
+ const [pagination, setPagination] = useState<PaginationState>({
197
+ page: 1,
198
+ pageSize: 25,
199
+ });
200
+
201
+ return (
202
+ <Pagination
203
+ totalResults={totalResults}
204
+ {...pagination}
205
+ onChange={setPagination}
206
+ />
207
+ );
208
+ }
209
+ ```
210
+
211
+ Pages are one-based. Changing the page size keeps the previously first visible result on the new page; for example, page 3 at 25 per page becomes page 2 at 50 per page. The callback emits `{ page, pageSize }` once per actual user change. Rendering new props emits no callback, and controls wait for the application to apply the new state.
212
+
213
+ The default is a small bar with Previous/Next labels, a per-page selector with `[10, 25, 50, 100]`, and a range count. Navigation uses the standard neutral `Button` component and its shared sizing and interaction styles. Set `size="md"`, `showLabels={false}`, or `menuPlacement="top"` as needed. Controls wrap in reading order on narrow screens. All themes use the shared tokens, and the existing `styles.css` import includes pagination styles.
214
+
215
+ Supply nonnegative integer totals, a positive integer page and page size, and a nonempty list of positive integer `pageSizeOptions` containing the current size. The displayed page and navigation targets are clamped to the available pages. Applications own data fetching, slicing, loading UI, and updating their canonical page after filtering or changing the total. Compose pagination beneath `Table` or another result view; it does not paginate children automatically.
216
+
217
+ Zero results display “No results” and disable every control. `isDisabled` also disables everything. Boundary navigation buttons are disabled, and the size selector is disabled when the total is at most the smallest size option. Override the navigation's `aria-label` when rendering multiple pagination bars.
218
+
219
+ `Select.menuPlacement` and `Popover.placement` also accept `"top"` or `"bottom"`; both default to `"bottom"` and flip when space requires it. A Select with `aria-labelledby` now shares that label with its listbox.
220
+
76
221
  ## Themes and fonts
77
222
 
78
223
  Set `data-theme` on the document root to `light`, `dark`, `cherry-glaze`, or `black-hawaii`. The default font tokens use system stacks and can be overridden by the consumer without downloading bundled font files:
package/UPGRADING.md CHANGED
@@ -2,25 +2,145 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## 0.1.5
5
+ ## 0.1.7
6
6
 
7
7
  ### Changes
8
8
 
9
+ - Added `AppShell`, composing a full-width TopNav above SideNav and a scrolling main landmark. Below 768px of shell width, navigation opens in a right-side drawer with a branded header and an optional actions footer.
10
+ - Added Drawer `variant="navigation"`, React-node titles, `closeLabel`, and `portalContainer`. The navigation variant focuses Close first, slides/fades over `--duration-panel`, and retains an inert, accessibility-hidden panel until its exit completes. Explicit `aria-label` names the dialog instead of the visual title.
11
+ - Rebuilt the combined navigation example to match the App Shell handoff, with fullscreen, narrow, no-actions, and long-content stories. Standalone navigation components remain available.
12
+ - Fixed mobile navigation moving the underlying page during its slide-in. Navigation drawers clip their off-screen panel and move/restore focus without scrolling the shell.
13
+ - Audited the library and examples and replaced custom button markup with shared `Button`: shell/drawer/sidebar controls, field clear/remove/step actions, drag handles, menus, selection rows, calendar/Select options, date/select triggers, Switch, and interactive Stepper/SubNav controls. Removed duplicate action-button styling. A contributor lint rule rejects raw buttons outside Button and test fixtures.
14
+ - Icon-only Button now supports custom visual children when `iconStart` is absent, including Avatar and numeric calendar/progress glyphs. Pending state replaces those children with the shared spinner. Select/DateField retain the shared Field skin; Switch retains its track/knob mechanics.
15
+ - Added Button `size="xs"` (24px) for compact actions inside fields and chips. SearchField clear and TagsInput remove use it, so tags no longer make an md TagsInput taller and the clear button fits a small SearchField.
16
+ - Navigation Drawer covers the viewport, above page chrome, unless `portalContainer` is supplied. With a container, it stays inside that positioned element (AppShell does this). `side="start"` and right-to-left documents now slide out through the panel's own edge. Reduced motion applies to the panel, and panel content that does not scroll itself can scroll.
17
+ - Drawer lets popups inside it handle Escape first. Menu, Select, DateField, and Modal mark the Escape they handle with `preventDefault()`, so closing a menu in AppShell's drawer footer leaves the drawer open.
18
+ - AppShell renders without `ResizeObserver`. Modifier-key, non-primary-button, and `target="_blank"` link clicks no longer close its drawer.
19
+ - Fixed regressions from the Button migration. AssetTile hover and press no longer paint over the thumbnail. Interactive Stepper markers match the rail's marker size. Calendar days outside the shown month are muted again. MenuItem labels wrap, and danger shortcuts use the item's text colour. Board handles show the grab cursor with the aggregate stylesheet. SubNavButton keeps `.ds-subnav__item` and the SubNav rail. Drag overlays draw a decorative grip instead of a hidden button.
20
+
21
+ ### Adoption
22
+
23
+ Import `AppShell` from the root or `@namroodinc/liquid-editorial/components/AppShell`, retaining the existing aggregate CSS import. Move the product brand into `brand`, pass SideNavSection/SideNavItem content as `navigation`, and put page content directly inside AppShell. Do not wrap navigation in another SideNav or add a SideNavHeader/collapse toggle. The shell supplies the main landmark, so remove any enclosing/nested main from the replaced layout.
24
+
25
+ Keep routes and active links in application state. Supply `actions={(location) => ...}` to render compact `"topnav"` controls and expanded `"drawer"` content. Omit the prop, or return nothing for the drawer, to omit its footer. Use `navigationLabel` to change the default `"Main"` name. Keep render callbacks pure and shared state outside slots; use React `useId` inside custom components instead of fixed IDs in content rendered in both locations.
26
+
27
+ AppShell defaults to `100dvh`; override its height through native style/className for embedded frames. It responds to its own width with CSS container queries. The library owns drawer state and dismisses it on link activation and when the shell reaches 768px; no router dependency or prototype viewport prop is needed. The page-movement fix requires no prop changes; remove any local shell-scroll reset workaround added for the drawer animation.
28
+
29
+ The Button cleanup needs no changes to existing component props. Remove local copies of button appearance and selectors for removed `.ds-search__clear`, `.ds-tags__remove`, and `.ds-asset-tile__hit-label` markup. Use accessible names in tests. Compose custom square controls as `<Button isIconOnly aria-label="Account">...</Button>` with no `iconStart`, using `aria-label` or `aria-labelledby` for their accessible name. New library controls and examples must compose Button; the existing variants/sizes supply their appearance. Shared field skin and specialized widget layout remain supported. Use `size="xs"` for a Button placed inside a field or chip.
30
+
31
+ Use `Drawer variant="navigation"` without `portalContainer` to cover the viewport. To contain it in a layout region, pass a positioned element (for example, `position: relative`) as `portalContainer`. If a custom popup inside a Drawer handles Escape, call `event.preventDefault()` so the drawer stays open. Remove any ResizeObserver polyfill that was added only for AppShell.
32
+
33
+ ### Compatibility
34
+
35
+ The AppShell/Drawer props are additive. The Button cleanup intentionally standardizes action appearance and sizes: shell navigation controls use large 48px Buttons; clear/remove actions use extra-small 24px Buttons and drag handles use small Buttons; numeric steppers follow their field's standard Button size. Menu danger actions use the shared danger variant. SubNavButton composes Button but keeps the SubNav rail and its `.ds-subnav__item` class. Interactive Stepper markers are square Buttons at the rail's marker size, while read-only markers remain indicators. `ButtonSize` now includes `"xs"`; TypeScript maps keyed by every `ButtonSize` need an `xs` entry. Drawer handles Escape and Tab on `window`, so keyboard events dispatched on `document` or on any element still reach it. An Escape already marked with `preventDefault()` no longer closes it. Select now ignores Escape while its listbox is closed, so an enclosing Drawer can close. Select option rows are now disabled-capable button elements with `role="option"`; selection and keyboard contracts are unchanged. Custom icon-only children that were previously hidden now render when no `iconStart` is supplied. Default Drawer dismissal timing and string titles remain supported. Navigation Drawer exits remain in the DOM briefly but are immediately inert and hidden from accessibility queries; tests that require actual removal should await the transition. Closed shells render on the server without accessing browser globals. Supported React versions and package versions are unchanged; the release workflow owns version increments. There is no new runtime dependency.
36
+
37
+ ### Verification
38
+
39
+ Check 390px, 767px, 768px, and 1280px shell widths, including a narrow shell inside a wide viewport. Confirm the top bar spans the whole frame, the desktop rail has no brand/collapse header, content and navigation scroll independently, and the mobile footer appears only with actions. During opening and closing, confirm only the panel moves: the background frame and shell scroll offsets must stay fixed, including when main content is already scrolled. Test Escape, Close, scrim and link dismissal, initial Close focus, Tab/Shift+Tab trapping, focus restoration, body scroll restoration, desktop resize while open, reduced motion, and reopening during exit. Check multiple shell IDs and the consuming application's route/active-state behavior. Run the consuming project's typecheck, tests, and build after replacing its layout.
40
+
41
+ Open the navigation drawer in a narrow AppShell. Press Escape inside an actions Menu or Select and confirm only the popup closes. Cmd/Ctrl-click a navigation link and confirm the drawer stays open. Outside AppShell, open `Drawer variant="navigation"` on a scrolled page without `portalContainer`, and also with `side="start"`, `dir="rtl"`, reduced motion, and content taller than the panel. Run consuming tests that render AppShell without a ResizeObserver polyfill.
42
+
43
+ Compare the converted ordinary actions with standalone Buttons of the same size/variant, including keyboard focus and disabled state. Hover and press a selectable AssetTile and confirm the thumbnail stays visible. Check navigable Steppers at both sizes: connectors should run through marker centres, with labels level. Check calendar spill-over days, wrapped and danger MenuItems, Board grab cursors with the built aggregate CSS, SubNavs that mix links and buttons, tag chips in md TagsInput, and the clear button in a small SearchField. Check field clear/remove/step behavior, select/date validation and selection, disabled options, Switch state, menu actions, SubNav/Stepper activation, and pointer/keyboard dragging. Verify tag wrapping, calendar layout, and long selection rows at narrow widths and in every theme. Run library lint to enforce Button composition.
44
+
45
+ ## 0.1.6
46
+
47
+ ### Changes
48
+
49
+ - ImageCropper places rotate/Reset buttons to the right of the zoom slider, vertically centred on its track. The shared controls wrap onto another row when space is limited.
50
+
9
51
  - Pushes to `main`, including merged PRs, now automatically bump the patch version and publish the package to npm after validation.
10
52
  - The release workflow moves these notes under the new version and commits the version files and guide back to `main`.
53
+ - Added controlled `Pagination` with standard neutral Previous/Next `Button`s, a per-page dropdown, a live result range, and wrapping layouts for narrow screens. Pagination supplies layout styles and uses the shared controls' appearance.
54
+ - Added optional `Select.menuPlacement` and `Popover.placement` (`"top"` or `"bottom"`). Select listboxes now share a trigger's `aria-labelledby` when supplied.
55
+ - Simplified `ImageCropper` to one required `aspect` prop controlled by the application. Removed the ratio picker, `ratios`, `onAspectChange`, and the `cropRatios` / `CropRatio` exports.
56
+ - ImageCropper composes shared `Stack`, `Field`, `Slider`, `ButtonGroup`, and `Button` components. Removed duplicate layout, label, hint, value, and slider styling; its CSS now covers only the crop surface, image, frame, and guides.
57
+ - Restored ImageCropper's custom engine, fixed crop frame, diagonal background, zoom slider, rotation buttons, and crop/state export API. Image boundaries remain enforced when panning, zooming out, rotating, or resizing. Removed Cropper.js and the temporary native `onReady` API.
11
58
 
12
59
  ### Adoption
13
60
 
14
61
  Library contributors add upgrade guidance here and merge their changes to `main`. GitHub Actions handles versioning, validation, and publishing; there are no local npm release commands or manual GitHub Releases to create. Pull the generated version commit before continuing local work. Consuming applications can update the dependency as usual.
15
62
 
63
+ To adopt pagination, import `Pagination` from the package root or `@namroodinc/liquid-editorial/components/Pagination` and keep both values in application state:
64
+
65
+ ```tsx
66
+ const [pagination, setPagination] = useState({ page: 1, pageSize: 25 });
67
+
68
+ <Pagination
69
+ totalResults={totalResults}
70
+ {...pagination}
71
+ onChange={setPagination}
72
+ />;
73
+ ```
74
+
75
+ Use the emitted page and page size together for requests or local slicing. Changing size preserves the first visible result (page 3 at size 25 becomes page 2 at size 50). Keep canonical state in bounds when filtering changes the total; prop changes do not emit corrections. Supply valid counts and positive integer sizes, with a nonempty `pageSizeOptions` containing the current size. The defaults are `[10, 25, 50, 100]`. The component handles no data loading or slicing; compose it beneath a table or another result view. The existing aggregate CSS import includes its styles. Give multiple pagination bars distinct `aria-label` values.
76
+
77
+ Use `menuPlacement="top"` on Pagination or Select, or `placement="top"` on Popover, to prefer an upward menu. Space constraints can still flip the panel to the other side.
78
+
79
+ Remove any `showFirstLast` or `joinedButtons` props from local integrations of the unreleased component. Pagination provides Previous/Next navigation only; First/Last controls and button joining are no longer part of its API.
80
+
81
+ Update every `ImageCropper` call to supply a positive, finite `aspect` (width / height):
82
+
83
+ ```tsx
84
+ import { useState } from "react";
85
+ import {
86
+ cropToBlob,
87
+ type CropRect,
88
+ type CropState,
89
+ } from "@namroodinc/liquid-editorial";
90
+
91
+ const [selection, setSelection] = useState<{
92
+ crop: CropRect;
93
+ state: CropState;
94
+ } | null>(null);
95
+ <ImageCropper
96
+ src={src}
97
+ aspect={16 / 9}
98
+ onChange={(crop, state) => setSelection({ crop, state })}
99
+ />;
100
+
101
+ // In your save handler:
102
+ if (selection) {
103
+ const blob = await cropToBlob(src, selection.crop, {
104
+ outputWidth: 1600,
105
+ rotation: selection.state.rotation,
106
+ type: "image/jpeg",
107
+ });
108
+ uploadCrop(blob);
109
+ }
110
+ ```
111
+
112
+ Use `aspect={1}` to retain the former implicit square shape. Remove `ratios`, `onAspectChange`, `cropRatios`, and `CropRatio` usage. The application owns the destination shape; changing `aspect` resets pan/zoom and preserves rotation. Reset clears pan, zoom, and rotation while keeping that aspect.
113
+
114
+ If adopting local versions of the temporary Cropper.js integration, remove `onReady`, native Cropper type imports, and `$toCanvas()` usage. Use the restored `onChange(crop, state)` callback, `CropRect` / `CropState` types, and `cropToBlob(src, crop, { rotation: state.rotation })`. `CropToBlobOptions`, `maxZoom` (default `3`), and `rotatable` (default `true`) are restored. The standalone helper supports saved crops after unmounting and requires same-origin or CORS-enabled sources. Remove any direct Cropper.js dependency or styles added solely for this component.
115
+
116
+ Keep the library CSS import for the diagonal background, fixed frame, guides, shared Slider/Field, and standard Button controls. Zoom is labelled by the shared Field and displays Slider's standard value label (for example, `1×` or `1.25×`). No prop changes are needed. The instruction text now uses Field's hint and is associated with the Zoom slider through `aria-describedby`. The zoom slider and rotate/Reset buttons share a horizontal Stack, with actions to the right and the Field hint beneath the row. The actions wrap below the slider in narrow containers. Remove selectors for `.ds-cropper__controls`, `.ds-cropper__zoom`, `.ds-cropper__hint`, `.ds-cropper__slider`, `.ds-cropper__zoom-label`, and `.ds-cropper__value`; use accessible names when testing controls. The forwarded ref and native container attributes remain supported. Tests should exercise the stage's direct image and the library's controls; there are no Cropper.js elements or native movable/resizable selections.
117
+
16
118
  ### Compatibility
17
119
 
18
- The workflow publishes stable patch releases under `latest`. Creating a GitHub Release no longer triggers publishing. Component APIs are unchanged.
120
+ The ImageCropper control-row adjustment needs no adoption changes. It uses the existing Field, Slider, Stack, ButtonGroup, and Button components; control appearance, accessible associations, zoom values, and crop APIs are unchanged.
121
+
122
+ The workflow publishes stable patch releases under `latest`. Creating a GitHub Release no longer triggers publishing.
123
+
124
+ Pagination is an additive API and requires `totalResults`, `page`, `pageSize`, and `onChange({ page, pageSize })`; it has no uncontrolled mode or implicit result total. The new placement props are optional and retain the previous bottom-first behavior. Select listboxes with `aria-labelledby` now use the visible external label instead of the placeholder; update accessibility assertions that expected the placeholder in that case. The manifest and lockfile versions are unchanged; the release workflow performs the version bump.
125
+
126
+ The required single-aspect API is the remaining ImageCropper breaking change: `aspect` is required and the ratio picker, `ratios`, `onAspectChange`, `cropRatios`, and `CropRatio` are removed. Existing `CropRect`, `CropState`, `CropToBlobOptions`, `onChange`, `maxZoom`, `rotatable`, and `cropToBlob` contracts remain supported. Image-pixel coordinates are rounded and clamped inside the rotated source bounds.
127
+
128
+ The temporary Cropper.js integration is reverted. Its dependency, `onReady` callback, and native export workflow are removed. The fixed frame, rotation controls, theme-driven diagonal background, and custom engine are restored. Root/component imports, ESM/CommonJS, SSR, and React 18/19 remain supported. Styles are supplied through the library CSS entry points. The shared Slider/Field composition changes only the zoom control presentation and DOM; zoom bounds, step, crop callbacks, and the public API remain unchanged.
19
129
 
20
130
  ### Verification
21
131
 
22
132
  Check the `Publish to npm` workflow in GitHub Actions and confirm that the new version appears on the npm package page. The workflow runs the existing full validation before committing and publishing.
23
133
 
134
+ - In consuming applications, verify first/last pages, partial last pages, zero results, custom size options, disabled/loading UI, and state correction after filtering.
135
+ - Change the size while on a later page and confirm that the first visible result remains on the new page and one callback updates both values.
136
+ - Exercise keyboard selection, Escape/Tab dismissal, focus, and accessible labels, including when two pagination bars are present.
137
+ - Check narrow layouts, themes, upward menu placement, and viewport flipping. Confirm Previous/Next match standard neutral Buttons of the same size and that First/Last controls are absent. Run the consuming project's type checks, tests, and build with its existing CSS import.
138
+ - Verify ImageCropper's rotate/Reset buttons sit to the right of the slider on wide layouts and wrap below it without overflow in narrow containers, including with rotation disabled.
139
+ - Confirm ImageCropper's Zoom slider has its visible Field label, associated Field hint, and standard Slider appearance/value label. Adjust it by pointer and keyboard, then check Reset and rotation update its controlled value. Check narrow layouts and all themes, including multiple instances with distinct label associations.
140
+ - For ImageCropper, check square, landscape, and portrait aspects. Confirm the fixed frame, diagonal background, zoom slider, rotation buttons, and Reset are present, and the ratio picker is absent.
141
+ - Pan to every boundary, zoom back out, lower `maxZoom`, rotate through quarter turns, and resize the stage. Confirm the frame stays covered and emitted crop coordinates remain inside the rotated source dimensions. Change `aspect` after pan/zoom/rotation and confirm only rotation is retained.
142
+ - Exercise mouse/touch dragging, keyboard nudges, multiple instances, optional rotation, and all themes. Export crops using `cropToBlob`, including a saved crop after unmounting, and check dimensions and image pixels. Run the consuming project's type checks, tests, build, and SSR/import checks.
143
+
24
144
  ## 0.1.3
25
145
 
26
146
  ### Changes
@@ -35,8 +155,11 @@ Use the shared empty message while keeping loading indicators in your applicatio
35
155
 
36
156
  ```tsx
37
157
  <TableFrame>
38
- <Table emptyMessage="No matching articles."
39
- isEmpty={!isLoading && articles.length === 0} aria-busy={isLoading}>
158
+ <Table
159
+ emptyMessage="No matching articles."
160
+ isEmpty={!isLoading && articles.length === 0}
161
+ aria-busy={isLoading}
162
+ >
40
163
  <TableHead>
41
164
  <TableRow>
42
165
  <TableHeaderCell>Title</TableHeaderCell>
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../Button/index.cjs"),t=require("../Drawer/index.cjs"),n=require("../SideNav/index.cjs"),r=require("../TopNav/index.cjs");let i=require("react"),a=require("react/jsx-runtime");var o=(0,i.forwardRef)(function({brand:o,navigation:s,actions:c,navigationLabel:l=`Main`,className:u,children:d,...f},p){let[m,h]=(0,i.useState)(null),[g,_]=(0,i.useState)(!1),v=(0,i.useRef)(null),y=(0,i.useRef)(null),b=(0,i.useRef)(!1),x=(0,i.useId)(),S=(0,i.useCallback)(e=>{h(e),typeof p==`function`?p(e):p&&(p.current=e)},[p]);(0,i.useEffect)(()=>{if(!m)return;let e=e=>{e<768||(m.querySelector(`[data-state='open'] [role='dialog']`)?.contains(document.activeElement)&&(b.current=!0),_(!1))};if(e(m.getBoundingClientRect().width),typeof ResizeObserver>`u`)return;let t=new ResizeObserver(([t])=>{t&&e(t.contentRect.width)});return t.observe(m),()=>{t.disconnect()}},[m]),(0,i.useEffect)(()=>{!g&&b.current&&(b.current=!1,(v.current?.querySelector(`[aria-current='page']`)??v.current?.querySelector(`a[href]`)??y.current)?.focus())},[g]);let C=c?.(`drawer`);return(0,a.jsxs)(`div`,{...f,ref:S,className:[`ds-appshell`,u].filter(Boolean).join(` `),children:[(0,a.jsxs)(`div`,{className:`ds-appshell__frame`,"aria-hidden":g||void 0,ref:e=>{e&&(e.inert=g)},children:[(0,a.jsx)(r.TopNav,{brand:o,actions:(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(`div`,{className:`ds-appshell__actions`,children:c?.(`topnav`)}),(0,a.jsx)(e.Button,{variant:`simple`,size:`lg`,isIconOnly:!0,iconStart:`menu`,className:`ds-appshell__toggle`,"aria-label":`Open navigation`,"aria-expanded":g,"aria-controls":x,onClick:()=>_(!0)})]})}),(0,a.jsxs)(`div`,{className:`ds-appshell__body`,children:[(0,a.jsx)(n.SideNav,{ref:v,className:`ds-appshell__desktop-nav`,label:l,children:s}),(0,a.jsx)(`main`,{ref:y,className:`ds-appshell__main`,tabIndex:-1,children:d})]})]}),(0,a.jsx)(t.Drawer,{id:x,isOpen:g,onClose:()=>_(!1),variant:`navigation`,portalContainer:m,title:o,"aria-label":`Navigation`,closeLabel:`Close navigation`,footer:C?(0,a.jsx)(n.SideNavFooter,{children:C}):void 0,children:(0,a.jsx)(n.SideNav,{className:`ds-appshell__mobile-nav`,label:l,onClick:e=>{let t=e.target instanceof Element?e.target.closest(`a[href]`):null,n=e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||t?.target===`_blank`;t&&!n&&_(!1)},children:s})})]})});exports.AppShell=o;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["forwardRef","useState","useRef","useId","useCallback","TopNav","_Fragment","Button","SideNav","Drawer","SideNavFooter","require_components_Button_index","require_components_Drawer_index","require_components_SideNav_index","require_components_TopNav_index","react","react_jsx_runtime","AppShell","rest","shell","setShell","isOpen","setOpen","desktopNavRef","mainRef","restoreDesktopFocus","drawerId","shellRef","node","ref","measure","width","observer","entry","drawerActions","actions","className","navigationLabel","navigation","brand","link","event","opensElsewhere"],"sources":["../../../src/components/AppShell/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Drawer } from \"../Drawer\";\nimport { Button } from \"../Button\";\nimport { SideNav, SideNavFooter } from \"../SideNav\";\nimport { TopNav } from \"../TopNav\";\nimport \"./style.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Product brand, shared by the top bar and mobile drawer header. */\n brand: ReactNode;\n /** SideNavSection/SideNavItem content. The application owns routing and selection. */\n navigation: ReactNode;\n /** One slot with a compact top-bar and expanded drawer presentation. */\n actions?: (location: \"topnav\" | \"drawer\") => ReactNode;\n navigationLabel?: string;\n /** Rendered inside the shell's scrolling main landmark. */\n children?: ReactNode;\n}\n\n/** Full-width product bar above a desktop rail, or a mobile navigation drawer. */\nexport const AppShell = forwardRef<HTMLDivElement, AppShellProps>(function AppShell(\n { brand, navigation, actions, navigationLabel = \"Main\", className, children, ...rest },\n ref,\n) {\n const [shell, setShell] = useState<HTMLDivElement | null>(null);\n const [isOpen, setOpen] = useState(false);\n const desktopNavRef = useRef<HTMLElement | null>(null);\n const mainRef = useRef<HTMLElement | null>(null);\n const restoreDesktopFocus = useRef(false);\n const drawerId = useId();\n const shellRef = useCallback((node: HTMLDivElement | null) => {\n setShell(node);\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }, [ref]);\n\n useEffect(() => {\n if (!shell) return;\n const measure = (width: number) => {\n if (width < 768) return;\n const focusInDrawer = shell.querySelector(\"[data-state='open'] [role='dialog']\")?.contains(document.activeElement);\n if (focusInDrawer) restoreDesktopFocus.current = true;\n setOpen(false);\n };\n measure(shell.getBoundingClientRect().width);\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n if (entry) measure(entry.contentRect.width);\n });\n observer.observe(shell);\n return () => {\n observer.disconnect();\n };\n }, [shell]);\n\n useEffect(() => {\n if (isOpen || !restoreDesktopFocus.current) return;\n restoreDesktopFocus.current = false;\n const destination = desktopNavRef.current?.querySelector<HTMLElement>(\"[aria-current='page']\")\n ?? desktopNavRef.current?.querySelector<HTMLElement>(\"a[href]\");\n (destination ?? mainRef.current)?.focus();\n }, [isOpen]);\n\n const drawerActions = actions?.(\"drawer\");\n\n return (\n <div {...rest} ref={shellRef} className={[\"ds-appshell\", className].filter(Boolean).join(\" \")}>\n <div\n className=\"ds-appshell__frame\"\n aria-hidden={isOpen || undefined}\n ref={(node) => { if (node) node.inert = isOpen; }}\n >\n <TopNav\n brand={brand}\n actions={<>\n <div className=\"ds-appshell__actions\">{actions?.(\"topnav\")}</div>\n <Button\n variant=\"simple\"\n size=\"lg\"\n isIconOnly\n iconStart=\"menu\"\n className=\"ds-appshell__toggle\"\n aria-label=\"Open navigation\"\n aria-expanded={isOpen}\n aria-controls={drawerId}\n onClick={() => setOpen(true)}\n />\n </>}\n />\n <div className=\"ds-appshell__body\">\n <SideNav ref={desktopNavRef} className=\"ds-appshell__desktop-nav\" label={navigationLabel}>\n {navigation}\n </SideNav>\n <main ref={mainRef} className=\"ds-appshell__main\" tabIndex={-1}>{children}</main>\n </div>\n </div>\n <Drawer\n id={drawerId}\n isOpen={isOpen}\n onClose={() => setOpen(false)}\n variant=\"navigation\"\n portalContainer={shell}\n title={brand}\n aria-label=\"Navigation\"\n closeLabel=\"Close navigation\"\n footer={drawerActions ? <SideNavFooter>{drawerActions}</SideNavFooter> : undefined}\n >\n <SideNav\n className=\"ds-appshell__mobile-nav\"\n label={navigationLabel}\n onClick={(event) => {\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n // Opening a link elsewhere (modifier keys, another button, a new tab) leaves this page in place.\n const opensElsewhere = event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey\n || event.altKey || link?.target === \"_blank\";\n if (link && !opensElsewhere) setOpen(false);\n }}\n >\n {navigation}\n </SideNav>\n </Drawer>\n </div>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ export interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
3
+ /** Product brand, shared by the top bar and mobile drawer header. */
4
+ brand: ReactNode;
5
+ /** SideNavSection/SideNavItem content. The application owns routing and selection. */
6
+ navigation: ReactNode;
7
+ /** One slot with a compact top-bar and expanded drawer presentation. */
8
+ actions?: (location: "topnav" | "drawer") => ReactNode;
9
+ navigationLabel?: string;
10
+ /** Rendered inside the shell's scrolling main landmark. */
11
+ children?: ReactNode;
12
+ }
13
+ /** Full-width product bar above a desktop rail, or a mobile navigation drawer. */
14
+ export declare const AppShell: import("react").ForwardRefExoticComponent<AppShellProps & import("react").RefAttributes<HTMLDivElement>>;
15
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/AppShell/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,aAAc,SAAQ,cAAc,CAAC,cAAc,CAAC;IACnE,qEAAqE;IACrE,KAAK,EAAE,SAAS,CAAC;IACjB,sFAAsF;IACtF,UAAU,EAAE,SAAS,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,KAAK,SAAS,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,kFAAkF;AAClF,eAAO,MAAM,QAAQ,0GAuGnB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ export interface AppShellProps extends HTMLAttributes<HTMLDivElement> {
3
+ /** Product brand, shared by the top bar and mobile drawer header. */
4
+ brand: ReactNode;
5
+ /** SideNavSection/SideNavItem content. The application owns routing and selection. */
6
+ navigation: ReactNode;
7
+ /** One slot with a compact top-bar and expanded drawer presentation. */
8
+ actions?: (location: "topnav" | "drawer") => ReactNode;
9
+ navigationLabel?: string;
10
+ /** Rendered inside the shell's scrolling main landmark. */
11
+ children?: ReactNode;
12
+ }
13
+ /** Full-width product bar above a desktop rail, or a mobile navigation drawer. */
14
+ export declare const AppShell: import("react").ForwardRefExoticComponent<AppShellProps & import("react").RefAttributes<HTMLDivElement>>;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,aAAc,SAAQ,cAAc,CAAC,cAAc,CAAC;IACnE,qEAAqE;IACrE,KAAK,EAAE,SAAS,CAAC;IACjB,sFAAsF;IACtF,UAAU,EAAE,SAAS,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,KAAK,SAAS,CAAC;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,kFAAkF;AAClF,eAAO,MAAM,QAAQ,0GAuGnB,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { Button as e } from "../Button/index.js";
2
+ import { Drawer as t } from "../Drawer/index.js";
3
+ import { SideNav as n, SideNavFooter as r } from "../SideNav/index.js";
4
+ import { TopNav as i } from "../TopNav/index.js";
5
+ import { forwardRef as a, useCallback as o, useEffect as s, useId as c, useRef as l, useState as u } from "react";
6
+ import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
7
+ //#region src/components/AppShell/index.tsx
8
+ var m = a(function({ brand: a, navigation: m, actions: h, navigationLabel: g = "Main", className: _, children: v, ...y }, b) {
9
+ let [x, S] = u(null), [C, w] = u(!1), T = l(null), E = l(null), D = l(!1), O = c(), k = o((e) => {
10
+ S(e), typeof b == "function" ? b(e) : b && (b.current = e);
11
+ }, [b]);
12
+ s(() => {
13
+ if (!x) return;
14
+ let e = (e) => {
15
+ e < 768 || (x.querySelector("[data-state='open'] [role='dialog']")?.contains(document.activeElement) && (D.current = !0), w(!1));
16
+ };
17
+ if (e(x.getBoundingClientRect().width), typeof ResizeObserver > "u") return;
18
+ let t = new ResizeObserver(([t]) => {
19
+ t && e(t.contentRect.width);
20
+ });
21
+ return t.observe(x), () => {
22
+ t.disconnect();
23
+ };
24
+ }, [x]), s(() => {
25
+ !C && D.current && (D.current = !1, (T.current?.querySelector("[aria-current='page']") ?? T.current?.querySelector("a[href]") ?? E.current)?.focus());
26
+ }, [C]);
27
+ let A = h?.("drawer");
28
+ return /* @__PURE__ */ p("div", {
29
+ ...y,
30
+ ref: k,
31
+ className: ["ds-appshell", _].filter(Boolean).join(" "),
32
+ children: [/* @__PURE__ */ p("div", {
33
+ className: "ds-appshell__frame",
34
+ "aria-hidden": C || void 0,
35
+ ref: (e) => {
36
+ e && (e.inert = C);
37
+ },
38
+ children: [/* @__PURE__ */ f(i, {
39
+ brand: a,
40
+ actions: /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("div", {
41
+ className: "ds-appshell__actions",
42
+ children: h?.("topnav")
43
+ }), /* @__PURE__ */ f(e, {
44
+ variant: "simple",
45
+ size: "lg",
46
+ isIconOnly: !0,
47
+ iconStart: "menu",
48
+ className: "ds-appshell__toggle",
49
+ "aria-label": "Open navigation",
50
+ "aria-expanded": C,
51
+ "aria-controls": O,
52
+ onClick: () => w(!0)
53
+ })] })
54
+ }), /* @__PURE__ */ p("div", {
55
+ className: "ds-appshell__body",
56
+ children: [/* @__PURE__ */ f(n, {
57
+ ref: T,
58
+ className: "ds-appshell__desktop-nav",
59
+ label: g,
60
+ children: m
61
+ }), /* @__PURE__ */ f("main", {
62
+ ref: E,
63
+ className: "ds-appshell__main",
64
+ tabIndex: -1,
65
+ children: v
66
+ })]
67
+ })]
68
+ }), /* @__PURE__ */ f(t, {
69
+ id: O,
70
+ isOpen: C,
71
+ onClose: () => w(!1),
72
+ variant: "navigation",
73
+ portalContainer: x,
74
+ title: a,
75
+ "aria-label": "Navigation",
76
+ closeLabel: "Close navigation",
77
+ footer: A ? /* @__PURE__ */ f(r, { children: A }) : void 0,
78
+ children: /* @__PURE__ */ f(n, {
79
+ className: "ds-appshell__mobile-nav",
80
+ label: g,
81
+ onClick: (e) => {
82
+ let t = e.target instanceof Element ? e.target.closest("a[href]") : null, n = e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || t?.target === "_blank";
83
+ t && !n && w(!1);
84
+ },
85
+ children: m
86
+ })
87
+ })]
88
+ });
89
+ });
90
+ //#endregion
91
+ export { m as AppShell };
92
+
93
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_Fragment","Fragment$1","AppShell","forwardRef","rest","shell","setShell","useState","isOpen","setOpen","desktopNavRef","useRef","mainRef","restoreDesktopFocus","drawerId","useId","shellRef","useCallback","node","ref","useEffect","measure","width","observer","entry","drawerActions","actions","className","TopNav","Button","SideNav","navigationLabel","navigation","Drawer","brand","SideNavFooter","link","event","opensElsewhere"],"sources":["../../../src/components/AppShell/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Drawer } from \"../Drawer\";\nimport { Button } from \"../Button\";\nimport { SideNav, SideNavFooter } from \"../SideNav\";\nimport { TopNav } from \"../TopNav\";\nimport \"./style.css\";\n\nexport interface AppShellProps extends HTMLAttributes<HTMLDivElement> {\n /** Product brand, shared by the top bar and mobile drawer header. */\n brand: ReactNode;\n /** SideNavSection/SideNavItem content. The application owns routing and selection. */\n navigation: ReactNode;\n /** One slot with a compact top-bar and expanded drawer presentation. */\n actions?: (location: \"topnav\" | \"drawer\") => ReactNode;\n navigationLabel?: string;\n /** Rendered inside the shell's scrolling main landmark. */\n children?: ReactNode;\n}\n\n/** Full-width product bar above a desktop rail, or a mobile navigation drawer. */\nexport const AppShell = forwardRef<HTMLDivElement, AppShellProps>(function AppShell(\n { brand, navigation, actions, navigationLabel = \"Main\", className, children, ...rest },\n ref,\n) {\n const [shell, setShell] = useState<HTMLDivElement | null>(null);\n const [isOpen, setOpen] = useState(false);\n const desktopNavRef = useRef<HTMLElement | null>(null);\n const mainRef = useRef<HTMLElement | null>(null);\n const restoreDesktopFocus = useRef(false);\n const drawerId = useId();\n const shellRef = useCallback((node: HTMLDivElement | null) => {\n setShell(node);\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }, [ref]);\n\n useEffect(() => {\n if (!shell) return;\n const measure = (width: number) => {\n if (width < 768) return;\n const focusInDrawer = shell.querySelector(\"[data-state='open'] [role='dialog']\")?.contains(document.activeElement);\n if (focusInDrawer) restoreDesktopFocus.current = true;\n setOpen(false);\n };\n measure(shell.getBoundingClientRect().width);\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n if (entry) measure(entry.contentRect.width);\n });\n observer.observe(shell);\n return () => {\n observer.disconnect();\n };\n }, [shell]);\n\n useEffect(() => {\n if (isOpen || !restoreDesktopFocus.current) return;\n restoreDesktopFocus.current = false;\n const destination = desktopNavRef.current?.querySelector<HTMLElement>(\"[aria-current='page']\")\n ?? desktopNavRef.current?.querySelector<HTMLElement>(\"a[href]\");\n (destination ?? mainRef.current)?.focus();\n }, [isOpen]);\n\n const drawerActions = actions?.(\"drawer\");\n\n return (\n <div {...rest} ref={shellRef} className={[\"ds-appshell\", className].filter(Boolean).join(\" \")}>\n <div\n className=\"ds-appshell__frame\"\n aria-hidden={isOpen || undefined}\n ref={(node) => { if (node) node.inert = isOpen; }}\n >\n <TopNav\n brand={brand}\n actions={<>\n <div className=\"ds-appshell__actions\">{actions?.(\"topnav\")}</div>\n <Button\n variant=\"simple\"\n size=\"lg\"\n isIconOnly\n iconStart=\"menu\"\n className=\"ds-appshell__toggle\"\n aria-label=\"Open navigation\"\n aria-expanded={isOpen}\n aria-controls={drawerId}\n onClick={() => setOpen(true)}\n />\n </>}\n />\n <div className=\"ds-appshell__body\">\n <SideNav ref={desktopNavRef} className=\"ds-appshell__desktop-nav\" label={navigationLabel}>\n {navigation}\n </SideNav>\n <main ref={mainRef} className=\"ds-appshell__main\" tabIndex={-1}>{children}</main>\n </div>\n </div>\n <Drawer\n id={drawerId}\n isOpen={isOpen}\n onClose={() => setOpen(false)}\n variant=\"navigation\"\n portalContainer={shell}\n title={brand}\n aria-label=\"Navigation\"\n closeLabel=\"Close navigation\"\n footer={drawerActions ? <SideNavFooter>{drawerActions}</SideNavFooter> : undefined}\n >\n <SideNav\n className=\"ds-appshell__mobile-nav\"\n label={navigationLabel}\n onClick={(event) => {\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n // Opening a link elsewhere (modifier keys, another button, a new tab) leaves this page in place.\n const opensElsewhere = event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey\n || event.altKey || link?.target === \"_blank\";\n if (link && !opensElsewhere) setOpen(false);\n }}\n >\n {navigation}\n </SideNav>\n </Drawer>\n </div>\n );\n});\n"],"mappings":"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"}