@namroodinc/liquid-editorial 0.1.6 → 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.
- package/AGENTS.md +2 -0
- package/README.md +42 -0
- package/UPGRADING.md +68 -11
- package/dist/components/AppShell/index.cjs +2 -0
- package/dist/components/AppShell/index.cjs.map +1 -0
- package/dist/components/AppShell/index.d.cts +15 -0
- package/dist/components/AppShell/index.d.cts.map +1 -0
- package/dist/components/AppShell/index.d.ts +15 -0
- package/dist/components/AppShell/index.d.ts.map +1 -0
- package/dist/components/AppShell/index.js +93 -0
- package/dist/components/AppShell/index.js.map +1 -0
- package/dist/components/AppShell/style.css +79 -0
- package/dist/components/AssetGrid/index.cjs +1 -1
- package/dist/components/AssetGrid/index.cjs.map +1 -1
- package/dist/components/AssetGrid/index.d.cts.map +1 -1
- package/dist/components/AssetGrid/index.d.ts.map +1 -1
- package/dist/components/AssetGrid/index.js +57 -59
- package/dist/components/AssetGrid/index.js.map +1 -1
- package/dist/components/AssetGrid/style.css +8 -16
- package/dist/components/Board/index.cjs +1 -1
- package/dist/components/Board/index.cjs.map +1 -1
- package/dist/components/Board/index.d.cts.map +1 -1
- package/dist/components/Board/index.d.ts.map +1 -1
- package/dist/components/Board/index.js +88 -84
- package/dist/components/Board/index.js.map +1 -1
- package/dist/components/Board/style.css +17 -28
- package/dist/components/Button/index.cjs +1 -1
- package/dist/components/Button/index.cjs.map +1 -1
- package/dist/components/Button/index.d.cts +3 -2
- package/dist/components/Button/index.d.cts.map +1 -1
- package/dist/components/Button/index.d.ts +3 -2
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Button/style.css +21 -15
- package/dist/components/DatePicker/index.cjs +1 -1
- package/dist/components/DatePicker/index.cjs.map +1 -1
- package/dist/components/DatePicker/index.d.cts.map +1 -1
- package/dist/components/DatePicker/index.d.ts.map +1 -1
- package/dist/components/DatePicker/index.js +46 -45
- package/dist/components/DatePicker/index.js.map +1 -1
- package/dist/components/DatePicker/style.css +4 -61
- package/dist/components/Drawer/index.cjs +1 -1
- package/dist/components/Drawer/index.cjs.map +1 -1
- package/dist/components/Drawer/index.d.cts +10 -1
- package/dist/components/Drawer/index.d.cts.map +1 -1
- package/dist/components/Drawer/index.d.ts +10 -1
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +69 -47
- package/dist/components/Drawer/index.js.map +1 -1
- package/dist/components/Drawer/style.css +106 -0
- package/dist/components/EntryPicker/index.cjs +1 -1
- package/dist/components/EntryPicker/index.cjs.map +1 -1
- package/dist/components/EntryPicker/index.d.cts.map +1 -1
- package/dist/components/EntryPicker/index.d.ts.map +1 -1
- package/dist/components/EntryPicker/index.js +12 -11
- package/dist/components/EntryPicker/index.js.map +1 -1
- package/dist/components/EntryPicker/style.css +3 -30
- package/dist/components/Field/style.css +11 -3
- package/dist/components/Menu/index.cjs +1 -1
- package/dist/components/Menu/index.cjs.map +1 -1
- package/dist/components/Menu/index.d.cts.map +1 -1
- package/dist/components/Menu/index.d.ts.map +1 -1
- package/dist/components/Menu/index.js +11 -16
- package/dist/components/Menu/index.js.map +1 -1
- package/dist/components/Menu/style.css +7 -41
- package/dist/components/Modal/index.cjs +1 -1
- package/dist/components/Modal/index.cjs.map +1 -1
- package/dist/components/Modal/index.d.cts.map +1 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/Modal/index.js +12 -12
- package/dist/components/Modal/index.js.map +1 -1
- package/dist/components/NumberField/index.cjs +1 -1
- package/dist/components/NumberField/index.cjs.map +1 -1
- package/dist/components/NumberField/index.js +13 -17
- package/dist/components/NumberField/index.js.map +1 -1
- package/dist/components/NumberField/style.css +1 -41
- package/dist/components/Popover/index.cjs +1 -1
- package/dist/components/Popover/index.cjs.map +1 -1
- package/dist/components/Popover/index.js +4 -4
- package/dist/components/SearchField/index.cjs +1 -1
- package/dist/components/SearchField/index.cjs.map +1 -1
- package/dist/components/SearchField/index.d.cts.map +1 -1
- package/dist/components/SearchField/index.d.ts.map +1 -1
- package/dist/components/SearchField/index.js +26 -27
- package/dist/components/SearchField/index.js.map +1 -1
- package/dist/components/SearchField/style.css +0 -21
- package/dist/components/Select/index.cjs +1 -1
- package/dist/components/Select/index.cjs.map +1 -1
- package/dist/components/Select/index.d.cts.map +1 -1
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +82 -75
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/Select/style.css +5 -38
- package/dist/components/SideNav/index.cjs +1 -1
- package/dist/components/SideNav/index.cjs.map +1 -1
- package/dist/components/SideNav/index.d.cts.map +1 -1
- package/dist/components/SideNav/index.d.ts.map +1 -1
- package/dist/components/SideNav/index.js +52 -49
- package/dist/components/SideNav/index.js.map +1 -1
- package/dist/components/SideNav/style.css +2 -27
- package/dist/components/SortableList/index.cjs +1 -1
- package/dist/components/SortableList/index.cjs.map +1 -1
- package/dist/components/SortableList/index.d.cts.map +1 -1
- package/dist/components/SortableList/index.d.ts.map +1 -1
- package/dist/components/SortableList/index.js +42 -39
- package/dist/components/SortableList/index.js.map +1 -1
- package/dist/components/SortableList/style.css +9 -15
- package/dist/components/Stepper/index.cjs +1 -1
- package/dist/components/Stepper/index.cjs.map +1 -1
- package/dist/components/Stepper/index.d.cts.map +1 -1
- package/dist/components/Stepper/index.d.ts.map +1 -1
- package/dist/components/Stepper/index.js +48 -39
- package/dist/components/Stepper/index.js.map +1 -1
- package/dist/components/Stepper/style.css +24 -18
- package/dist/components/SubNav/index.cjs +1 -1
- package/dist/components/SubNav/index.cjs.map +1 -1
- package/dist/components/SubNav/index.d.cts.map +1 -1
- package/dist/components/SubNav/index.d.ts.map +1 -1
- package/dist/components/SubNav/index.js +27 -28
- package/dist/components/SubNav/index.js.map +1 -1
- package/dist/components/SubNav/style.css +13 -0
- package/dist/components/Switch/index.cjs +1 -1
- package/dist/components/Switch/index.cjs.map +1 -1
- package/dist/components/Switch/index.d.cts.map +1 -1
- package/dist/components/Switch/index.d.ts.map +1 -1
- package/dist/components/Switch/index.js +23 -21
- package/dist/components/Switch/index.js.map +1 -1
- package/dist/components/Switch/style.css +2 -28
- package/dist/components/TagsInput/index.cjs +1 -1
- package/dist/components/TagsInput/index.cjs.map +1 -1
- package/dist/components/TagsInput/index.js +8 -10
- package/dist/components/TagsInput/index.js.map +1 -1
- package/dist/components/TagsInput/style.css +2 -24
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -38
- package/dist/styles.css +1 -0
- 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
|
@@ -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
|
|
@@ -84,6 +94,38 @@ Links open a URL modal from the toolbar or by clicking an existing body link. Sa
|
|
|
84
94
|
|
|
85
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).
|
|
86
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
|
+
|
|
87
129
|
## Image cropping
|
|
88
130
|
|
|
89
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.
|
package/UPGRADING.md
CHANGED
|
@@ -2,9 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 0.1.
|
|
5
|
+
## 0.1.7
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
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
|
|
6
22
|
|
|
7
|
-
|
|
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
|
|
8
46
|
|
|
9
47
|
### Changes
|
|
10
48
|
|
|
@@ -27,7 +65,11 @@ To adopt pagination, import `Pagination` from the package root or `@namroodinc/l
|
|
|
27
65
|
```tsx
|
|
28
66
|
const [pagination, setPagination] = useState({ page: 1, pageSize: 25 });
|
|
29
67
|
|
|
30
|
-
<Pagination
|
|
68
|
+
<Pagination
|
|
69
|
+
totalResults={totalResults}
|
|
70
|
+
{...pagination}
|
|
71
|
+
onChange={setPagination}
|
|
72
|
+
/>;
|
|
31
73
|
```
|
|
32
74
|
|
|
33
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.
|
|
@@ -40,16 +82,28 @@ Update every `ImageCropper` call to supply a positive, finite `aspect` (width /
|
|
|
40
82
|
|
|
41
83
|
```tsx
|
|
42
84
|
import { useState } from "react";
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
/>;
|
|
48
100
|
|
|
49
101
|
// In your save handler:
|
|
50
102
|
if (selection) {
|
|
51
103
|
const blob = await cropToBlob(src, selection.crop, {
|
|
52
|
-
outputWidth: 1600,
|
|
104
|
+
outputWidth: 1600,
|
|
105
|
+
rotation: selection.state.rotation,
|
|
106
|
+
type: "image/jpeg",
|
|
53
107
|
});
|
|
54
108
|
uploadCrop(blob);
|
|
55
109
|
}
|
|
@@ -101,8 +155,11 @@ Use the shared empty message while keeping loading indicators in your applicatio
|
|
|
101
155
|
|
|
102
156
|
```tsx
|
|
103
157
|
<TableFrame>
|
|
104
|
-
<Table
|
|
105
|
-
|
|
158
|
+
<Table
|
|
159
|
+
emptyMessage="No matching articles."
|
|
160
|
+
isEmpty={!isLoading && articles.length === 0}
|
|
161
|
+
aria-busy={isLoading}
|
|
162
|
+
>
|
|
106
163
|
<TableHead>
|
|
107
164
|
<TableRow>
|
|
108
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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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.ds-appshell {
|
|
3
|
+
position: relative;
|
|
4
|
+
container: ds-appshell / inline-size;
|
|
5
|
+
block-size: 100dvh;
|
|
6
|
+
inline-size: 100%;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
background: var(--color-canvas);
|
|
9
|
+
color: var(--color-text);
|
|
10
|
+
font-family: var(--font-sans);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ds-appshell__frame {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
block-size: 100%;
|
|
17
|
+
min-block-size: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ds-appshell .ds-topnav {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
position: sticky;
|
|
23
|
+
inset-block-start: 0;
|
|
24
|
+
z-index: 10;
|
|
25
|
+
flex: none;
|
|
26
|
+
gap: var(--space-3);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ds-appshell__body {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex: 1;
|
|
32
|
+
min-block-size: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ds-appshell__desktop-nav {
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
min-block-size: 0;
|
|
38
|
+
overflow-y: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ds-appshell__main {
|
|
42
|
+
flex: 1;
|
|
43
|
+
min-inline-size: 0;
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
padding: var(--space-8);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ds-appshell__actions {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--space-3);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ds-appshell__toggle.ds-button {
|
|
55
|
+
display: none;
|
|
56
|
+
flex: none;
|
|
57
|
+
margin-inline-start: auto;
|
|
58
|
+
margin-inline-end: calc(-1 * var(--space-2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ds-appshell__mobile-nav {
|
|
62
|
+
inline-size: 100%;
|
|
63
|
+
min-block-size: 0;
|
|
64
|
+
border: 0;
|
|
65
|
+
overflow-y: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ds-appshell__mobile-nav .ds-sidenav__item {
|
|
69
|
+
min-block-size: calc(var(--space-10) + var(--space-1));
|
|
70
|
+
font-size: var(--field-font-size-md);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@container ds-appshell (width < 768px) {
|
|
74
|
+
.ds-appshell .ds-topnav { padding-inline: var(--space-4); }
|
|
75
|
+
.ds-appshell__desktop-nav, .ds-appshell__actions { display: none; }
|
|
76
|
+
.ds-appshell__toggle.ds-button { display: flex; }
|
|
77
|
+
.ds-appshell__main { padding: var(--space-6) var(--space-4); }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/Icon-CWsA1W83.cjs"),t=require("../
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/Icon-CWsA1W83.cjs"),t=require("../Button/index.cjs"),n=require("../Grid/index.cjs"),r=require("../Skeleton/index.cjs");let i=require("react"),a=require("react/jsx-runtime");function o(...e){return e.filter(Boolean).join(` `)}var s=(0,i.forwardRef)(function({isLoading:e=!1,loadingCount:t=8,className:r,children:i,...s},l){return(0,a.jsx)(n.Grid,{ref:l,columns:`auto`,gap:`md`,minColumnWidth:`var(--asset-tile-min-width)`,className:o(`ds-asset-grid`,r),"aria-busy":e||void 0,...s,children:e?Array.from({length:t},(e,t)=>(0,a.jsx)(c,{index:t},t)):i})});function c({index:e=0}){let t={animationDelay:`calc(var(--skeleton-stagger) * ${e})`};return(0,a.jsxs)(`div`,{className:`ds-asset-tile`,"data-loading":`true`,"aria-hidden":`true`,children:[(0,a.jsx)(r.Skeleton,{shape:`block`,className:`ds-asset-tile__thumb`,style:t}),(0,a.jsxs)(`div`,{className:`ds-asset-tile__text`,children:[(0,a.jsx)(r.Skeleton,{shape:`text`,width:`80%`,style:t}),(0,a.jsx)(r.Skeleton,{shape:`text`,width:`45%`,style:t})]})]})}var l=(0,i.forwardRef)(function({name:n,src:s,icon:c=`image`,meta:l,isSelected:u=!1,isPending:d=!1,onSelect:f,actions:p,className:m,...h},g){let[_,v]=(0,i.useState)(!1),[y,b]=(0,i.useState)(!1),x=!!s&&!_;return(0,a.jsxs)(`div`,{ref:g,className:o(`ds-asset-tile`,m),"data-selected":u||void 0,"data-pending":d||void 0,...h,children:[(0,a.jsxs)(`div`,{className:`ds-asset-tile__thumb`,children:[x?(0,a.jsxs)(a.Fragment,{children:[!y&&(0,a.jsx)(r.Skeleton,{shape:`block`,className:`ds-asset-tile__thumb-skeleton`}),(0,a.jsx)(`img`,{src:s,alt:``,loading:`lazy`,"data-loaded":y||void 0,onLoad:()=>b(!0),onError:()=>v(!0)})]}):(0,a.jsx)(`span`,{className:`ds-asset-tile__placeholder`,children:(0,a.jsx)(e.t,{name:_?`alert-triangle`:c,size:24})}),d&&(0,a.jsxs)(`span`,{className:`ds-asset-tile__pending`,role:`status`,children:[(0,a.jsx)(e.t,{name:`spinner`,size:20,className:`ds-asset-tile__spinner`}),(0,a.jsx)(`span`,{className:`ds-asset-tile__pending-label`,children:`Uploading`})]}),p&&(0,a.jsx)(`div`,{className:`ds-asset-tile__actions`,children:p}),f&&(0,a.jsx)(t.Button,{variant:`simple`,className:`ds-asset-tile__hit`,"aria-pressed":u,"aria-label":n,isDisabled:d,onClick:f})]}),(0,a.jsxs)(`div`,{className:`ds-asset-tile__text`,children:[(0,a.jsx)(`span`,{className:`ds-asset-tile__name`,children:n}),l&&(0,a.jsx)(`span`,{className:`ds-asset-tile__meta`,children:l})]})]})});function u({title:t,description:n,action:r}){return(0,a.jsxs)(`div`,{className:`ds-asset-empty`,children:[(0,a.jsx)(e.t,{name:`image`,size:24}),(0,a.jsx)(`span`,{className:`ds-asset-empty__title`,children:t}),n&&(0,a.jsx)(`span`,{className:`ds-asset-empty__description`,children:n}),r]})}exports.AssetGrid=s,exports.AssetGridEmpty=u,exports.AssetTile=l,exports.AssetTileSkeleton=c;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["forwardRef","Grid","Skeleton","useState","_Fragment","Icon","require_Icon","require_components_Grid_index","require_components_Skeleton_index","react","react_jsx_runtime","cx","parts","AssetGrid","rest","className","isLoading","loadingCount","AssetTileSkeleton","i","children","delay","index","AssetTile","hasFailed","setHasFailed","isLoaded","setIsLoaded","showImage","src","isSelected","isPending","icon","actions","onSelect","name","meta","AssetGridEmpty","title","description","action"],"sources":["../../../src/components/AssetGrid/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Grid } from \"../Grid\";\nimport { Icon } from \"../Icon\";\nimport type { IconName } from \"../Icon/icons\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./style.css\";\n\nfunction cx(...parts: (string | undefined | false)[]) {\n return parts.filter(Boolean).join(\" \");\n}\n\nexport interface AssetGridProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Renders placeholder tiles instead of children. The grid keeps its\n * geometry, so nothing moves when the real assets arrive.\n */\n isLoading?: boolean;\n /** How many placeholder tiles to show while loading. */\n loadingCount?: number;\n children?: ReactNode;\n}\n\n/**\n * The media library surface: an auto-filling grid of asset tiles. Sits inside\n * a `TableFrame` when it is the page's collection, bare inside a drawer.\n */\nexport const AssetGrid = forwardRef<HTMLDivElement, AssetGridProps>(function AssetGrid(\n { isLoading = false, loadingCount = 8, className, children, ...rest },\n ref,\n) {\n return (\n <Grid\n ref={ref}\n columns=\"auto\"\n gap=\"md\"\n minColumnWidth=\"var(--asset-tile-min-width)\"\n className={cx(\"ds-asset-grid\", className)}\n aria-busy={isLoading || undefined}\n {...rest}\n >\n {isLoading\n ? Array.from({ length: loadingCount }, (_, i) => <AssetTileSkeleton key={i} index={i} />)\n : children}\n </Grid>\n );\n});\n\nexport interface AssetTileSkeletonProps {\n /** Position in the grid — drives the stagger, nothing else. */\n index?: number;\n}\n\n/** One loading tile: thumbnail block, name line, meta line. */\nexport function AssetTileSkeleton({ index = 0 }: AssetTileSkeletonProps) {\n const delay = { animationDelay: `calc(var(--skeleton-stagger) * ${index})` };\n return (\n <div className=\"ds-asset-tile\" data-loading=\"true\" aria-hidden=\"true\">\n <Skeleton shape=\"block\" className=\"ds-asset-tile__thumb\" style={delay} />\n <div className=\"ds-asset-tile__text\">\n <Skeleton shape=\"text\" width=\"80%\" style={delay} />\n <Skeleton shape=\"text\" width=\"45%\" style={delay} />\n </div>\n </div>\n );\n}\n\nexport interface AssetTileProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\" | \"title\"> {\n /** File name, shown in full — asset names are how people find things again. */\n name: string;\n /** Thumbnail source. Omit for a non-image asset and pass an `icon` instead. */\n src?: string;\n /** Glyph shown when there is no thumbnail, or while one fails to load. */\n icon?: IconName;\n /** Second line: dimensions, size, date — whatever the library sorts by. */\n meta?: ReactNode;\n /** Paints the soft fill and the solid rail. Never changes the tile's size. */\n isSelected?: boolean;\n /** The asset is still uploading or processing: dimmed, with a spinner. */\n isPending?: boolean;\n /** Makes the whole tile a selection target. */\n onSelect?: () => void;\n /** Badges or a menu, pinned to the top of the thumbnail. */\n actions?: ReactNode;\n}\n\n/**\n * One asset in the library. Selection paints a fill and a rail exactly like a\n * table row, so a grid and a list read as the same collection in two shapes.\n */\nexport const AssetTile = forwardRef<HTMLDivElement, AssetTileProps>(function AssetTile(\n {\n name,\n src,\n icon = \"image\",\n meta,\n isSelected = false,\n isPending = false,\n onSelect,\n actions,\n className,\n ...rest\n },\n ref,\n) {\n const [hasFailed, setHasFailed] = useState(false);\n const [isLoaded, setIsLoaded] = useState(false);\n const showImage = Boolean(src) && !hasFailed;\n\n return (\n <div\n ref={ref}\n className={cx(\"ds-asset-tile\", className)}\n data-selected={isSelected || undefined}\n data-pending={isPending || undefined}\n {...rest}\n >\n <div className=\"ds-asset-tile__thumb\">\n {showImage ? (\n <>\n {!isLoaded && <Skeleton shape=\"block\" className=\"ds-asset-tile__thumb-skeleton\" />}\n <img\n src={src}\n alt=\"\"\n loading=\"lazy\"\n data-loaded={isLoaded || undefined}\n onLoad={() => setIsLoaded(true)}\n onError={() => setHasFailed(true)}\n />\n </>\n ) : (\n <span className=\"ds-asset-tile__placeholder\">\n <Icon name={hasFailed ? \"alert-triangle\" : icon} size={24} />\n </span>\n )}\n\n {isPending && (\n <span className=\"ds-asset-tile__pending\" role=\"status\">\n <Icon name=\"spinner\" size={20} className=\"ds-asset-tile__spinner\" />\n <span className=\"ds-asset-tile__pending-label\">Uploading</span>\n </span>\n )}\n\n {actions && <div className=\"ds-asset-tile__actions\">{actions}</div>}\n\n {onSelect && (\n <
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["forwardRef","Grid","Skeleton","useState","_Fragment","Icon","Button","require_Icon","require_components_Button_index","require_components_Grid_index","require_components_Skeleton_index","react","react_jsx_runtime","cx","parts","AssetGrid","rest","className","isLoading","loadingCount","AssetTileSkeleton","i","children","delay","index","AssetTile","hasFailed","setHasFailed","isLoaded","setIsLoaded","showImage","src","isSelected","isPending","icon","actions","onSelect","name","meta","AssetGridEmpty","title","description","action"],"sources":["../../../src/components/AssetGrid/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Grid } from \"../Grid\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport type { IconName } from \"../Icon/icons\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./style.css\";\n\nfunction cx(...parts: (string | undefined | false)[]) {\n return parts.filter(Boolean).join(\" \");\n}\n\nexport interface AssetGridProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Renders placeholder tiles instead of children. The grid keeps its\n * geometry, so nothing moves when the real assets arrive.\n */\n isLoading?: boolean;\n /** How many placeholder tiles to show while loading. */\n loadingCount?: number;\n children?: ReactNode;\n}\n\n/**\n * The media library surface: an auto-filling grid of asset tiles. Sits inside\n * a `TableFrame` when it is the page's collection, bare inside a drawer.\n */\nexport const AssetGrid = forwardRef<HTMLDivElement, AssetGridProps>(function AssetGrid(\n { isLoading = false, loadingCount = 8, className, children, ...rest },\n ref,\n) {\n return (\n <Grid\n ref={ref}\n columns=\"auto\"\n gap=\"md\"\n minColumnWidth=\"var(--asset-tile-min-width)\"\n className={cx(\"ds-asset-grid\", className)}\n aria-busy={isLoading || undefined}\n {...rest}\n >\n {isLoading\n ? Array.from({ length: loadingCount }, (_, i) => <AssetTileSkeleton key={i} index={i} />)\n : children}\n </Grid>\n );\n});\n\nexport interface AssetTileSkeletonProps {\n /** Position in the grid — drives the stagger, nothing else. */\n index?: number;\n}\n\n/** One loading tile: thumbnail block, name line, meta line. */\nexport function AssetTileSkeleton({ index = 0 }: AssetTileSkeletonProps) {\n const delay = { animationDelay: `calc(var(--skeleton-stagger) * ${index})` };\n return (\n <div className=\"ds-asset-tile\" data-loading=\"true\" aria-hidden=\"true\">\n <Skeleton shape=\"block\" className=\"ds-asset-tile__thumb\" style={delay} />\n <div className=\"ds-asset-tile__text\">\n <Skeleton shape=\"text\" width=\"80%\" style={delay} />\n <Skeleton shape=\"text\" width=\"45%\" style={delay} />\n </div>\n </div>\n );\n}\n\nexport interface AssetTileProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\" | \"title\"> {\n /** File name, shown in full — asset names are how people find things again. */\n name: string;\n /** Thumbnail source. Omit for a non-image asset and pass an `icon` instead. */\n src?: string;\n /** Glyph shown when there is no thumbnail, or while one fails to load. */\n icon?: IconName;\n /** Second line: dimensions, size, date — whatever the library sorts by. */\n meta?: ReactNode;\n /** Paints the soft fill and the solid rail. Never changes the tile's size. */\n isSelected?: boolean;\n /** The asset is still uploading or processing: dimmed, with a spinner. */\n isPending?: boolean;\n /** Makes the whole tile a selection target. */\n onSelect?: () => void;\n /** Badges or a menu, pinned to the top of the thumbnail. */\n actions?: ReactNode;\n}\n\n/**\n * One asset in the library. Selection paints a fill and a rail exactly like a\n * table row, so a grid and a list read as the same collection in two shapes.\n */\nexport const AssetTile = forwardRef<HTMLDivElement, AssetTileProps>(function AssetTile(\n {\n name,\n src,\n icon = \"image\",\n meta,\n isSelected = false,\n isPending = false,\n onSelect,\n actions,\n className,\n ...rest\n },\n ref,\n) {\n const [hasFailed, setHasFailed] = useState(false);\n const [isLoaded, setIsLoaded] = useState(false);\n const showImage = Boolean(src) && !hasFailed;\n\n return (\n <div\n ref={ref}\n className={cx(\"ds-asset-tile\", className)}\n data-selected={isSelected || undefined}\n data-pending={isPending || undefined}\n {...rest}\n >\n <div className=\"ds-asset-tile__thumb\">\n {showImage ? (\n <>\n {!isLoaded && <Skeleton shape=\"block\" className=\"ds-asset-tile__thumb-skeleton\" />}\n <img\n src={src}\n alt=\"\"\n loading=\"lazy\"\n data-loaded={isLoaded || undefined}\n onLoad={() => setIsLoaded(true)}\n onError={() => setHasFailed(true)}\n />\n </>\n ) : (\n <span className=\"ds-asset-tile__placeholder\">\n <Icon name={hasFailed ? \"alert-triangle\" : icon} size={24} />\n </span>\n )}\n\n {isPending && (\n <span className=\"ds-asset-tile__pending\" role=\"status\">\n <Icon name=\"spinner\" size={20} className=\"ds-asset-tile__spinner\" />\n <span className=\"ds-asset-tile__pending-label\">Uploading</span>\n </span>\n )}\n\n {actions && <div className=\"ds-asset-tile__actions\">{actions}</div>}\n\n {onSelect && (\n <Button\n variant=\"simple\"\n className=\"ds-asset-tile__hit\"\n aria-pressed={isSelected}\n aria-label={name}\n isDisabled={isPending}\n onClick={onSelect}\n />\n )}\n </div>\n\n <div className=\"ds-asset-tile__text\">\n <span className=\"ds-asset-tile__name\">{name}</span>\n {meta && <span className=\"ds-asset-tile__meta\">{meta}</span>}\n </div>\n </div>\n );\n});\n\nexport interface AssetGridEmptyProps {\n title: string;\n description?: ReactNode;\n action?: ReactNode;\n}\n\n/** Nothing in the library yet — one action, the one that fills it. */\nexport function AssetGridEmpty({ title, description, action }: AssetGridEmptyProps) {\n return (\n <div className=\"ds-asset-empty\">\n <Icon name=\"image\" size={24} />\n <span className=\"ds-asset-empty__title\">{title}</span>\n {description && <span className=\"ds-asset-empty__description\">{description}</span>}\n {action}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/AssetGrid/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/AssetGrid/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,aAAa,CAAC;AAMrB,MAAM,WAAW,cAAe,SAAQ,cAAc,CAAC,cAAc,CAAC;IACpE;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,2GAmBpB,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,EAAE,KAAS,EAAE,EAAE,sBAAsB,+BAWtE;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClE,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,2GAyEpB,CAAC;AAEH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,mBAAmB,+BASjF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/AssetGrid/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/AssetGrid/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,aAAa,CAAC;AAMrB,MAAM,WAAW,cAAe,SAAQ,cAAc,CAAC,cAAc,CAAC;IACpE;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,2GAmBpB,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,EAAE,KAAS,EAAE,EAAE,sBAAsB,+BAWtE;AAED,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClE,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,2GAyEpB,CAAC;AAEH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,mBAAmB,+BASjF"}
|