@thewhatmatters/wmds 0.4.7 → 0.4.8

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 (175) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONSUMING.md +3 -0
  3. package/dist/components/molecules/LinkTile/LinkTile.d.ts +45 -0
  4. package/dist/components/molecules/LinkTile/LinkTile.d.ts.map +1 -0
  5. package/dist/components/molecules/LinkTile/LinkTile.js +115 -0
  6. package/dist/components/molecules/LinkTile/linkTileStyles.d.ts +31 -0
  7. package/dist/components/molecules/LinkTile/linkTileStyles.d.ts.map +1 -0
  8. package/dist/components/molecules/LinkTile/linkTileStyles.js +8 -0
  9. package/dist/components/molecules/TileGrid/TileGrid.d.ts +42 -0
  10. package/dist/components/molecules/TileGrid/TileGrid.d.ts.map +1 -0
  11. package/dist/components/molecules/TileGrid/TileGrid.js +94 -0
  12. package/dist/components/molecules/TileGrid/tileGridMath.d.ts +8 -0
  13. package/dist/components/molecules/TileGrid/tileGridMath.d.ts.map +1 -0
  14. package/dist/components/molecules/TileGrid/tileGridMath.js +20 -0
  15. package/dist/components/molecules/TileGrid/tileGridStyles.d.ts +33 -0
  16. package/dist/components/molecules/TileGrid/tileGridStyles.d.ts.map +1 -0
  17. package/dist/components/molecules/TileGrid/tileGridStyles.js +16 -0
  18. package/dist/components/organisms/Dialog/Dialog.d.ts +12 -2
  19. package/dist/components/organisms/Dialog/Dialog.d.ts.map +1 -1
  20. package/dist/components/organisms/Dialog/Dialog.js +46 -44
  21. package/dist/components/organisms/Dialog/OverlayPanelHeader.d.ts +3 -1
  22. package/dist/components/organisms/Dialog/OverlayPanelHeader.d.ts.map +1 -1
  23. package/dist/components/organisms/Dialog/OverlayPanelHeader.js +20 -17
  24. package/dist/components/organisms/Dialog/dialogStyles.d.ts +5 -1
  25. package/dist/components/organisms/Dialog/dialogStyles.d.ts.map +1 -1
  26. package/dist/components/organisms/Dialog/dialogStyles.js +13 -11
  27. package/dist/components/organisms/FilterPanel/FilterPanel.d.ts +54 -0
  28. package/dist/components/organisms/FilterPanel/FilterPanel.d.ts.map +1 -0
  29. package/dist/components/organisms/FilterPanel/FilterPanel.js +137 -0
  30. package/dist/components/organisms/FilterPanel/filterSelection.d.ts +44 -0
  31. package/dist/components/organisms/FilterPanel/filterSelection.d.ts.map +1 -0
  32. package/dist/components/organisms/FilterPanel/filterSelection.js +35 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +73 -67
  36. package/dist/lib/compositionAudit.d.ts.map +1 -1
  37. package/dist/styles.css +1 -1
  38. package/docs/components.md +7 -4
  39. package/docs/exports.json +43 -2
  40. package/docs/patterns/components-accordion--flush.tsx +1 -1
  41. package/docs/patterns/components-avatar--dropdown-row.tsx +1 -1
  42. package/docs/patterns/components-badge--outline-tags.tsx +1 -1
  43. package/docs/patterns/components-badge--with-avatar.tsx +1 -1
  44. package/docs/patterns/components-breadcrumb--editorial.tsx +1 -1
  45. package/docs/patterns/components-breadcrumb--long-path.tsx +1 -1
  46. package/docs/patterns/components-breadcrumb--path.tsx +1 -1
  47. package/docs/patterns/components-breadcrumb--router-links.tsx +1 -1
  48. package/docs/patterns/components-button-button--copy-button.tsx +1 -1
  49. package/docs/patterns/components-button-button--destructive-action.tsx +1 -1
  50. package/docs/patterns/components-button-button--external-link.tsx +1 -1
  51. package/docs/patterns/components-button-button--ghost-action.tsx +1 -1
  52. package/docs/patterns/components-button-button--inverse-action.tsx +1 -1
  53. package/docs/patterns/components-button-button--link-render.tsx +1 -1
  54. package/docs/patterns/components-button-button--multi-state-badge.tsx +1 -1
  55. package/docs/patterns/components-button-button--outline-mono.tsx +1 -1
  56. package/docs/patterns/components-button-button--primary-action.tsx +1 -1
  57. package/docs/patterns/components-button-button--row-hug.tsx +1 -1
  58. package/docs/patterns/components-button-button--row-layout.tsx +1 -1
  59. package/docs/patterns/components-button-button--secondary-action.tsx +1 -1
  60. package/docs/patterns/components-button-button--suggestion-pills.tsx +1 -1
  61. package/docs/patterns/components-button-button--with-count.tsx +1 -1
  62. package/docs/patterns/components-button-button--with-icon.tsx +1 -1
  63. package/docs/patterns/components-button-floatingactionbutton--vertical-actions.tsx +1 -1
  64. package/docs/patterns/components-calembed--pattern-skip-in-footer.tsx +1 -1
  65. package/docs/patterns/components-card-card--body-slot-occupancy-history.tsx +1 -1
  66. package/docs/patterns/components-card-card--headerless-media.tsx +1 -1
  67. package/docs/patterns/components-card-card--outlined-layout.tsx +1 -1
  68. package/docs/patterns/components-card-card--pinned-height.tsx +1 -1
  69. package/docs/patterns/components-card-selectablecard--selectable-cards-pattern.tsx +1 -1
  70. package/docs/patterns/components-carousel--image-carousel.tsx +1 -1
  71. package/docs/patterns/components-chart--audience-activity-heatmap.tsx +1 -1
  72. package/docs/patterns/components-chart--cartesian-no-data-gaps.tsx +1 -1
  73. package/docs/patterns/components-chart--occupancy-history-in-card.tsx +1 -1
  74. package/docs/patterns/components-chart--occupancy-in-card.tsx +1 -1
  75. package/docs/patterns/components-chart--post-reach-distribution.tsx +1 -1
  76. package/docs/patterns/components-chart--ranked-audience-breakdown.tsx +1 -1
  77. package/docs/patterns/components-chart--unit-grid-composition.tsx +1 -1
  78. package/docs/patterns/components-chatdock--pattern-chat-dock.tsx +1 -1
  79. package/docs/patterns/components-chatdock--pattern-start-project-gate.tsx +1 -1
  80. package/docs/patterns/components-chatqa--pattern-qa-with-reply.tsx +1 -1
  81. package/docs/patterns/components-checkbox-checkbox--default.tsx +1 -1
  82. package/docs/patterns/components-checkbox-checkbox--with-count.tsx +1 -1
  83. package/docs/patterns/components-checkbox-checkbox--with-description.tsx +1 -1
  84. package/docs/patterns/components-checkbox-checkboxgroup--default.tsx +1 -1
  85. package/docs/patterns/components-checkbox-checkboxgroup--option-counts.tsx +1 -1
  86. package/docs/patterns/components-descriptionlist--details.tsx +1 -1
  87. package/docs/patterns/components-descriptionlist--post-metadata.tsx +1 -1
  88. package/docs/patterns/components-dialog--alert-dialog-destructive.tsx +1 -1
  89. package/docs/patterns/components-dialog--default-dialog.tsx +1 -1
  90. package/docs/patterns/components-dialog--form-dialog.tsx +1 -1
  91. package/docs/patterns/components-displaycontrols--page-utilities.tsx +1 -1
  92. package/docs/patterns/components-field--horizontal-input.tsx +1 -1
  93. package/docs/patterns/components-field--horizontal-text-area.tsx +1 -1
  94. package/docs/patterns/components-field--multi-control.tsx +1 -1
  95. package/docs/patterns/components-filterpanel--panel.tsx +55 -0
  96. package/docs/patterns/components-footerreveal--marketing-hero-pattern.tsx +1 -1
  97. package/docs/patterns/components-footerreveal--marketing-hero-ruled-pattern.tsx +1 -1
  98. package/docs/patterns/components-footerreveal--marketing-hero-with-gallery-intro.tsx +1 -1
  99. package/docs/patterns/components-footerreveal--marketing-page-pattern.tsx +1 -1
  100. package/docs/patterns/components-footerreveal--ruled-grid-footer-pattern.tsx +1 -1
  101. package/docs/patterns/components-footerreveal--sequenced-marketing-hero.tsx +1 -1
  102. package/docs/patterns/components-herointro--two-line-intro.tsx +1 -1
  103. package/docs/patterns/components-herotilestack--marketing-hero-pattern.tsx +1 -1
  104. package/docs/patterns/components-herotilestack--marketing-hero-text-sequence.tsx +1 -1
  105. package/docs/patterns/components-herotilestack--marketing-hero-with-gallery-intro.tsx +1 -1
  106. package/docs/patterns/components-indexlist--post-index.tsx +1 -1
  107. package/docs/patterns/components-intakeconfirmation--booked-pattern.tsx +1 -1
  108. package/docs/patterns/components-intakeconfirmation--emailed-pattern.tsx +1 -1
  109. package/docs/patterns/components-intakeform--about-you-pattern.tsx +1 -1
  110. package/docs/patterns/components-intakeform--short-form-pattern.tsx +1 -1
  111. package/docs/patterns/components-intakemodal--intake-modal-pattern.tsx +1 -1
  112. package/docs/patterns/components-kbd--command-row.tsx +1 -1
  113. package/docs/patterns/components-kbd--numbered-choices.tsx +1 -1
  114. package/docs/patterns/components-kbd--shortcut.tsx +1 -1
  115. package/docs/patterns/components-linktile--resource-tile.tsx +33 -0
  116. package/docs/patterns/components-linktile--router-link.tsx +25 -0
  117. package/docs/patterns/components-moremenu--action-menu.tsx +1 -1
  118. package/docs/patterns/components-moremenu--in-card-header.tsx +1 -1
  119. package/docs/patterns/components-navlist--side-nav-settings.tsx +1 -1
  120. package/docs/patterns/components-pageheader--app-header-pattern.tsx +1 -1
  121. package/docs/patterns/components-pageheader--page-section-header.tsx +1 -1
  122. package/docs/patterns/components-pageheader--toolbar-header.tsx +1 -1
  123. package/docs/patterns/components-panel--end-detail-rail.tsx +1 -1
  124. package/docs/patterns/components-pillgroup--pill-group-pattern.tsx +1 -1
  125. package/docs/patterns/components-promptbar--pattern-prompt-bar.tsx +1 -1
  126. package/docs/patterns/components-prose--article-body.tsx +1 -1
  127. package/docs/patterns/components-radio-radiogroup--default.tsx +1 -1
  128. package/docs/patterns/components-scrollhorizontal--project-gallery-pattern.tsx +1 -1
  129. package/docs/patterns/components-scrollhorizontal--with-intro.tsx +1 -1
  130. package/docs/patterns/components-sectioncaption--column-caption.tsx +1 -1
  131. package/docs/patterns/components-sectioncaption--with-action.tsx +1 -1
  132. package/docs/patterns/components-sheet--bottom-sheet.tsx +1 -1
  133. package/docs/patterns/components-sheet--end-sheet.tsx +1 -1
  134. package/docs/patterns/components-sitenav--brand-and-cta.tsx +1 -1
  135. package/docs/patterns/components-sitenav--centered-links.tsx +1 -1
  136. package/docs/patterns/components-sitenav--compact.tsx +1 -1
  137. package/docs/patterns/components-sitenav--marketing-header.tsx +1 -1
  138. package/docs/patterns/components-sitenav--mega-menu.tsx +1 -1
  139. package/docs/patterns/components-stat--insights-row.tsx +1 -1
  140. package/docs/patterns/components-stepprogress--step-progress-pattern.tsx +1 -1
  141. package/docs/patterns/components-switch--default.tsx +1 -1
  142. package/docs/patterns/components-switch--settings-row.tsx +1 -1
  143. package/docs/patterns/components-tab--settings-pages.tsx +1 -1
  144. package/docs/patterns/components-taskrows--action-details.tsx +1 -1
  145. package/docs/patterns/components-taskrows--capsules.tsx +1 -1
  146. package/docs/patterns/components-taskrows--card-with-status-rows.tsx +1 -1
  147. package/docs/patterns/components-taskrows--controlled-expand.tsx +1 -1
  148. package/docs/patterns/components-taskrows--custom-slots.tsx +1 -1
  149. package/docs/patterns/components-taskrows--status-list.tsx +1 -1
  150. package/docs/patterns/components-taskrows--tag-chips.tsx +1 -1
  151. package/docs/patterns/components-textlink--external-destination.tsx +1 -1
  152. package/docs/patterns/components-textlink--inline-prose.tsx +1 -1
  153. package/docs/patterns/components-textlink--quiet-title.tsx +1 -1
  154. package/docs/patterns/components-textlink--supporting-copy.tsx +1 -1
  155. package/docs/patterns/components-textsequence--default.tsx +1 -1
  156. package/docs/patterns/components-textsequence--reduced-motion.tsx +1 -1
  157. package/docs/patterns/components-textsequence--shapes.tsx +1 -1
  158. package/docs/patterns/components-tilegrid--masonry.tsx +44 -0
  159. package/docs/patterns/components-toast--stacked-notifications.tsx +1 -1
  160. package/docs/patterns/components-tooltip--controlled.tsx +1 -1
  161. package/docs/patterns/components-tooltip--disabled-control.tsx +1 -1
  162. package/docs/patterns/components-tooltip--icon-action.tsx +1 -1
  163. package/docs/patterns/components-tooltip--keyboard-shortcut.tsx +1 -1
  164. package/docs/patterns/components-tooltip--placement.tsx +1 -1
  165. package/docs/patterns/foundations-scroll-fade--horizontal-rail.tsx +1 -1
  166. package/docs/patterns/foundations-scroll-fade--overflowing-list.tsx +1 -1
  167. package/docs/patterns/guides-chart-explorations--comparison-gallery.tsx +1 -1
  168. package/docs/patterns/guides-filter-panel--filtered-grid.tsx +182 -0
  169. package/docs/patterns/guides-filter-panel--filtered-index.tsx +64 -153
  170. package/docs/patterns/guides-overlay-flows--notification-preferences.tsx +1 -1
  171. package/docs/patterns/guides-post-page--post-page.tsx +98 -54
  172. package/docs/patterns/guides-profile-typography--editorial-profile.tsx +1 -1
  173. package/docs/patterns/guides-resource-detail--detail.tsx +261 -0
  174. package/docs/patterns/index.json +49 -1
  175. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ Every release of `@thewhatmatters/wmds` is listed here, newest first.
7
7
  - **Pasted patterns:** when a release changes a **Pattern — …** story's Show code, it is named under Consumer actions so apps re-copy it.
8
8
  - **How to add an entry** (contributors): add your change to the top entry. If that version is already on npm (`npm view @thewhatmatters/wmds versions`), start a new entry above it and bump `version` in `package.json` to match. `npm run check:changelog` enforces the format.
9
9
 
10
+ ## 0.4.8
11
+
12
+ **A filtered grid and a resource detail for a resources page.** Three new components and two page patterns: a tile that is one link, a grid that packs tiles in reading order, the filter panel as a component, and a resource's detail as a dialog over the grid or as its own page. The WhatMatters Resources page is the first use. See **ADR-0044**.
13
+
14
+ - **LinkTile** (new) — a resource tile that is one link: `media` (an image with its `width` and `height`), `title`, a `meta` line such as the domain, an optional `tag` (a **Badge** over the image) and `source` mark (**Avatar** `xsm` or a favicon, before the title). One hit area and one focus ring; nothing interactive goes inside. `external` opens a new tab the way **Button** and **TextLink** do; `render={<Link href />}` makes it a router link. The image keeps its own ratio by default; `ratio` fixes it. `loading` is a **Skeleton** placeholder. A hairline inside the frame keeps an edge on light images in light mode and dark images in dark mode. Screen readers hear the title, the meta, the tag, then "opens in a new tab".
15
+ - **TileGrid** (new) with **TileGrid.Item** — `layout` `masonry` (default) or `uniform`; `columns` as one number or `{ base, sm, md, lg, xl }` (default 1, 2 from `sm`, 3 from `lg`); `empty`; `busy`. Reading order and tab order are the order of the items in both layouts: masonry puts each next item in the shortest column, so the order runs across and then down. When the items change, the tiles that stay move to their places and the others fade; reduced motion changes at once.
16
+ - **FilterPanel** (new) with **FilterPanel.Rail**, **.Trigger**, and **.Sheet** — the filter rail, its phone button, and its bottom Sheet as one component, so a page no longer pastes them. With it: `useFilterSelection`, `matchesFilterSelection`, `countFilterOptions`, `filterSelectionCount`, and the types `FilterSelection`, `FilterFacets`, `FilterPanelGroup`, `FilterPanelOption`.
17
+ - New **Guides/Filter panel → Pattern — filtered grid** — the filtered index page with **SectionCaption** over a **TileGrid** of **LinkTile** in the main column. A tile goes to the resource's route in the app (`item.href`), not out of the site; `item.external` is there for an app that links straight out. Props: `title`, `caption`, `items`, `groups`, `layout`, `columns`, `loading`, `defaultSelection`, `selection` + `onSelectionChange`, `renderLink` (the router's link for a tile), `renderImage` (the app's image component), `overlay`.
18
+ - New **Guides/Resource detail → Pattern — resource detail** — one module with both forms. `ResourceDetailDialog` opens over the grid (dimmed, not scrolling): the media as large as the window allows beside an info column — category eyebrow, title, the curator's note, the date added, and a **DescriptionList** (Category, Tags, Source, and the app's `rows`) — with the position, previous and next, and a close in the header, and a primary **Button** `external` to visit the resource pinned in the footer. Left and Right move between resources, Escape closes, and focus returns to the tile. On phones it is the whole screen, media first. `ResourceDetailPage` is the same content on the page grid under a **Breadcrumb**. Media is one image or one `video` with controls. The item is `{ id, title, image, video?: { src, poster, captions? }, category, tags?, meta?, note, added, addedLabel, visitHref, rows? }`.
19
+ - **Dialog.Content** `size="full"` (new) — the window less a margin, up to 90rem × 60rem, and the whole screen on phones. `headerEnd` (new) — actions before the close. `aria-labelledby` (new) — names the dialog from a heading in its body when there is no `title`.
20
+ - **Guides/Filter panel → Pattern — filtered index** now uses **FilterPanel** and the helpers. It looks and behaves the same and is about 90 lines shorter. `FilterGroupDef` is now `FilterPanelGroup` from the package; `FilterSelection` is imported from the package too.
21
+ - **Guides/Post page → Pattern — post page**: `metadataStack` (new, `"before"` | `"after"`, **default `"after"`**) — where the metadata panel sits below `lg`, where the page is one column. The article now comes first, with the date and the reading time in a mono line under the title (hidden from `lg`, where the panel is beside the article). It is also the panel's place in the markup: with `metadataSide="end"` the `aside` follows the article at every width and no order utility is used. From `lg` nothing changes. `metadataStack="before"` is the old stacking. See the amendment to **ADR-0041**.
22
+ - **`overlay`** (new prop) on **Pattern — filtered index**, **Pattern — filtered grid**, and **Guides/Post page → Pattern — post page** — page-level children such as **GridOverlay**, rendered first inside the page grid. Passing a prop leaves the pasted markup unchanged, so `wmds-check` no longer reports drift for an overlay.
23
+ - **Decisions taken** (the requests left them open): **LinkTile** is its own component, not a **Card** option. The detail over the grid is a **Dialog**, not a **Sheet** or a **Panel**. The detail item adds `category`, `tags`, `addedLabel`, and `video.captions` to the shape asked for. No view counts and no save control. No sort, no search field in the rail, no paging or Load more, and no save button in this version — the grid shows every item in the order it is given. A video in the tile is out of scope; `media` is one image.
24
+ - **Known limit:** a masonry grid packs after it measures its tiles. A server-rendered page shows aligned rows first and the tiles close up once at hydration, on grids of two or more columns (phones, at one column, do not move). That move is instant — no tile slides or fades; the reflow animation is for a change in the items only. Nothing moves as images load. The space under a tile is the row gap within 3px. **`layout="uniform"` has no such move**: it is CSS alone, so the server's markup is the final layout.
25
+
26
+ ### Consumer actions
27
+
28
+ 1. **WhatMatters site, Resources page: use the new grid.** Copy **Guides/Filter panel → Pattern — filtered grid** (`node_modules/@thewhatmatters/wmds/docs/patterns/guides-filter-panel--filtered-grid.tsx`) into `components/resource-grid.tsx`, keep its header comment, and add `"use client"` above it. Each item is `{ id, title, href, image: { src, alt, width, height }, meta?, tag?, facets }` with `href: "/resources/<slug>"`; give every image its real `width` and `height`. Delete the hand-built list in `app/resources/page.tsx` and its classes.
29
+ 2. **Wire the grid in one small client file** (functions cannot cross from a server component): `components/resources.tsx` with `"use client"` renders `<FilteredGrid title="Resources" caption="Library" items={items} groups={groups} renderLink={(item) => <Link href={item.href} scroll={false} />} renderImage={(image) => <Image {...image} sizes="(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw" />} overlay={<SiteGridOverlay />} />`. `app/resources/page.tsx` loads the items and renders it. Change `sizes` if you change `columns`; allow the image hosts in `next.config.ts`. Do not edit the pasted file for any of this.
30
+ 3. **Add the detail.** Copy **Guides/Resource detail → Pattern — resource detail** (`…/docs/patterns/guides-resource-detail--detail.tsx`) into `components/resource-detail.tsx`, keep its header comment, and add `"use client"`. Each resource needs `{ id, title, image, video?: { src, poster, captions? }, category, tags?, meta?, note, added, addedLabel, visitHref, rows? }`.
31
+ 4. **Pick the form by route.**
32
+ - The page: `app/resources/[slug]/page.tsx` renders `<ResourceDetailPage item={…} breadcrumb={[{ label: "Home", href: "/" }, { label: "Resources", href: "/resources" }, { label: item.title }]} overlay={<SiteGridOverlay />} />`. Delete `app/resources/[category]`: the categories become a filter group on the grid. No redirects.
33
+ - The dialog: a parallel slot with an intercepting route (`app/resources/@detail/(.)[slug]/page.tsx`, a `default.tsx` that returns `null`, and `{detail}` in `app/resources/layout.tsx`) renders a client component with `<ResourceDetailDialog item={…} open onClose={() => router.back()} onPrevious={previous ? () => router.replace("/resources/" + previous.slug, { scroll: false }) : undefined} onNext={…} position={"3 of 9"} />`. Leave `onPrevious` out on the first resource and `onNext` out on the last.
34
+ - Opening `/resources/<slug>` directly, or from a shared link, shows the page; a tile click shows the dialog over the grid.
35
+ 5. **WhatMatters site, blog index: re-copy Guides/Filter panel → Pattern — filtered index** (`…/docs/patterns/guides-filter-panel--filtered-index.tsx`) over `components/blog-index.tsx` and re-apply only your content, data, and handlers. Then remove `<SiteGridOverlay />` from the markup and pass it as `overlay={<SiteGridOverlay />}` where the page renders `<FilteredIndex>`. If the page imports `FilterGroupDef` from that file, import `FilterPanelGroup` from `@thewhatmatters/wmds` instead (same shape).
36
+ 6. **WhatMatters site, post page: re-copy Guides/Post page → Pattern — post page** (`…/docs/patterns/guides-post-page--post-page.tsx`) over `components/post-page.tsx`, re-apply your content, and move `<SiteGridOverlay />` to `overlay={<SiteGridOverlay />}`. Keep `metadataSide="end"` and `metadataColumns={3}`: with them the panel now stacks after the article below `lg`, the date and reading time sit under the title there, and the `aside` follows the article in the markup. No other prop and no class edit is needed; `metadataStack="after"` is the default. Update any test that expects the panel before the article in the DOM or above it on a phone.
37
+ 7. **Check:** `npx wmds-check` reports no `pattern-drift` for the four pasted files. Any `data-*` attributes the site adds to pasted elements are not drift and can stay.
38
+ 8. **If the one move at hydration is not acceptable** on the Resources page, pass `layout="uniform"` to `<FilteredGrid>`: every image is cropped to 4:3 and nothing moves.
39
+ 9. **Tests:** on a server-rendered grid of two or more columns, wait for `ul[data-packed]` before asserting tile positions or taking a screenshot. The detail dialog fades in over about 0.4s; wait for it before an accessibility or visual check.
40
+ 10. Other apps: an app that pasted **Pattern — post page** and re-copies it gets the panel after the article below `lg`; pass `metadataStack="before"` to keep it above. Everything else here is new; `Dialog` `size`, `headerEnd`, and `aria-labelledby` are optional.
41
+
10
42
  ## 0.4.7
11
43
 
12
44
  **Carousel: a row of items with a progress scrubber.** New component, built on the open `motion` package. The WhatMatters About page is the first use. See **ADR-0043**.
package/CONSUMING.md CHANGED
@@ -274,6 +274,9 @@ import {
274
274
  | `SectionCaption` | `children`, `as`, `id`, `end`, `marker`, `rule` | Components/SectionCaption — **Pattern — column caption**, **Pattern — caption with an action** |
275
275
  | `Prose` | `size` (`lg` \| `md`), `measure` (`reading` \| `none`), `as` | Components/Prose — **Pattern — article body**. Full page: **Guides/Post page → Pattern — post page** |
276
276
  | `IndexList` | `captions`, `size`, `titleAs`, `empty`, `IndexList.Item` `meta` / `title` / `href` / `preview` | Components/IndexList — **Pattern — post index**. With filters: **Guides/Filter panel → Pattern — filtered index** |
277
+ | `LinkTile` | `title`, `href` / `render`, `external`, `media`, `ratio`, `meta`, `tag`, `source`, `loading` | Components/LinkTile — **Pattern — resource tile**, **Pattern — router link** |
278
+ | `TileGrid` | `layout` (`masonry` / `uniform`), `columns`, `empty`, `busy`, `TileGrid.Item` | Components/TileGrid — **Pattern — tile grid**. With filters: **Guides/Filter panel → Pattern — filtered grid** |
279
+ | `FilterPanel` | `groups`, `selection`, `onSelectionChange`, `resultsLabel`, `labels`; `FilterPanel.Rail` / `.Trigger` / `.Sheet`; `useFilterSelection`, `matchesFilterSelection`, `countFilterOptions` | Components/FilterPanel — **Pattern — filter panel** |
277
280
  | `TaskRows` | `variant`, `status`, `meta`, `detailsLayout`, `TaskRows.Detail` | Components/TaskRows — **Pattern — status rows**, **capsules**, **action details** (`Detail variant="button"`), **tag chips** (`Chip readOnly size="sm"`), detail lines (`Detail` + `onPress` → `Button layout="row"`) |
278
281
  | `Badge` | `variant`, `size`, `emphasis` (`solid` \| `muted` \| `outline`), `mono`, `render` (tag links), `count`, `icon`, `avatar`, `iconOnly` | Components/Badge — copy a **Pattern** story. `avatar={{ src, alt }}` is a leading round **Avatar** sized to the badge (`badgeAvatarSize`: sm → 20px, md → 24px) and is mutually exclusive with `icon`. Pass `alt=""` when the label already speaks the word. |
279
282
  | `Status` | `variant`, `tone`, `label`, `besideLabel`, `pulsing`, `active`, `step` | Components/Status — `variant="ring"` or `variant="dot"` |
@@ -0,0 +1,45 @@
1
+ import { type ReactElement, type ReactNode } from "react";
2
+ export { linkTileLoadingRatio } from "./linkTileStyles";
3
+ /** Layout-only — width, margin, or grid placement. */
4
+ export type LinkTileLayoutClassName = string;
5
+ export interface LinkTileProps {
6
+ /** The resource's name. Two lines at most. Not needed while `loading`. */
7
+ title?: string;
8
+ /** Where the tile goes. Leave it out when `render` carries the destination. */
9
+ href?: string;
10
+ /**
11
+ * Compose the tile onto another element (Base UI `render`) — `render={<Link href="/work" />}` for
12
+ * a router link. The tile is that one link; never put a link or a button inside it.
13
+ */
14
+ render?: ReactElement;
15
+ /**
16
+ * Another site: opens in a new tab (`target="_blank"`, a safe `rel`), tells screen readers so,
17
+ * and shows the mark **TextLink** `external` uses after the title.
18
+ */
19
+ external?: boolean;
20
+ /**
21
+ * The preview — an `<img>` (or `next/image`) with its `width` and `height`, so the tile holds its
22
+ * space before the image loads. One image; not a video.
23
+ */
24
+ media?: ReactNode;
25
+ /**
26
+ * Fixes the frame's shape as width / height (`4 / 3`, `1`, `16 / 9`) and crops the image to it —
27
+ * for a grid of equal tiles. Left out, the image keeps its own ratio and tiles vary in height.
28
+ */
29
+ ratio?: number;
30
+ /** One line under the title — for example the domain, "linear.app". */
31
+ meta?: ReactNode;
32
+ /** A **Badge** over the image's top-start corner (at the caption's end when there is no image). Not a link. */
33
+ tag?: ReactNode;
34
+ /** A mark before the title — **Avatar** `size="xsm"`, or a 20px favicon `<img alt="">`. */
35
+ source?: ReactNode;
36
+ /** A placeholder in the tile's shape, built on **Skeleton** — not a link, and hidden from screen readers. */
37
+ loading?: boolean;
38
+ className?: LinkTileLayoutClassName;
39
+ }
40
+ /**
41
+ * A resource tile that is one link — a preview image with the resource's name and where it lives
42
+ * under it. Screen readers hear the title, the meta line, the tag, then "opens in a new tab".
43
+ */
44
+ export declare function LinkTile({ title, href, render, external, media, ratio, meta, tag, source, loading, className, }: LinkTileProps): import("react").JSX.Element;
45
+ //# sourceMappingURL=LinkTile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinkTile.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/LinkTile/LinkTile.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAqBrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,sDAAsD;AACtD,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,+GAA+G;IAC/G,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6GAA6G;IAC7G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAuBD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,MAAM,EACN,QAAgB,EAChB,KAAK,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,MAAM,EACN,OAAe,EACf,SAAS,GACV,EAAE,aAAa,+BAsFf"}
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { cn as e } from "../../../lib/cn.js";
3
+ import { Skeleton as t } from "../../atoms/Skeleton/Skeleton.js";
4
+ import { linkTileCaptionClasses as n, linkTileExternalIconClasses as r, linkTileFrameClasses as i, linkTileFrameFixedClasses as a, linkTileFrameNaturalClasses as o, linkTileLoadingRatio as s, linkTileLoadingRootClasses as c, linkTileMetaClasses as l, linkTileRootClasses as u, linkTileSourceClasses as d, linkTileTagInlineClasses as f, linkTileTagOverClasses as p, linkTileTextClasses as m, linkTileTitleClasses as h } from "./linkTileStyles.js";
5
+ import { useId as g } from "react";
6
+ import { SquareArrowOutUpRight as _ } from "lucide-react";
7
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
8
+ import { useRender as b } from "@base-ui/react/use-render";
9
+ //#region src/components/molecules/LinkTile/LinkTile.tsx
10
+ function x({ render: e, ...t }) {
11
+ return b({
12
+ render: e,
13
+ defaultTagName: "a",
14
+ props: t
15
+ });
16
+ }
17
+ function S(e) {
18
+ return e == null ? void 0 : { aspectRatio: String(e) };
19
+ }
20
+ function C({ title: s, href: b, render: C, external: w = !1, media: T, ratio: E, meta: D, tag: O, source: k, loading: A = !1, className: j }) {
21
+ let M = g(), N = `${M}-title`, P = `${M}-meta`, F = `${M}-tag`, I = `${M}-hint`;
22
+ if (A) return /* @__PURE__ */ y("div", {
23
+ className: e(c, j),
24
+ "data-link-tile": "",
25
+ "data-loading": "",
26
+ "aria-hidden": "true",
27
+ children: [/* @__PURE__ */ v("span", {
28
+ className: i,
29
+ style: S(E ?? 1.3333333333333333),
30
+ children: /* @__PURE__ */ v(t, { radius: "none" })
31
+ }), /* @__PURE__ */ y("span", {
32
+ className: m,
33
+ children: [/* @__PURE__ */ v(t, {
34
+ width: "70%",
35
+ height: 16,
36
+ radius: "inner",
37
+ index: 1
38
+ }), /* @__PURE__ */ v(t, {
39
+ width: "40%",
40
+ height: 12,
41
+ radius: "inner",
42
+ index: 2
43
+ })]
44
+ })]
45
+ });
46
+ b == null && C == null && console.warn("[WMDS LinkTile] Pass `href`, or `render` with a link that carries it.");
47
+ let L = T != null, R = [
48
+ N,
49
+ D != null && P,
50
+ O != null && F,
51
+ w && I
52
+ ].filter(Boolean).join(" ");
53
+ return /* @__PURE__ */ y(x, {
54
+ render: C,
55
+ ...b == null ? null : { href: b },
56
+ ...w ? {
57
+ target: "_blank",
58
+ rel: "noopener noreferrer"
59
+ } : null,
60
+ className: e(u, j),
61
+ "aria-labelledby": R,
62
+ "data-link-tile": "",
63
+ "data-external": w ? "" : void 0,
64
+ children: [L ? /* @__PURE__ */ y("span", {
65
+ className: e(i, E == null ? o : a),
66
+ style: S(E),
67
+ "data-link-tile-frame": "",
68
+ children: [T, O == null ? null : /* @__PURE__ */ v("span", {
69
+ id: F,
70
+ className: p,
71
+ children: O
72
+ })]
73
+ }) : null, /* @__PURE__ */ y("span", {
74
+ className: n,
75
+ children: [
76
+ k == null ? null : /* @__PURE__ */ v("span", {
77
+ className: d,
78
+ "aria-hidden": "true",
79
+ children: k
80
+ }),
81
+ /* @__PURE__ */ y("span", {
82
+ className: m,
83
+ children: [
84
+ /* @__PURE__ */ y("span", {
85
+ id: N,
86
+ className: h,
87
+ children: [s, w ? /* @__PURE__ */ v(_, {
88
+ className: r,
89
+ strokeWidth: 2,
90
+ "aria-hidden": !0
91
+ }) : null]
92
+ }),
93
+ D == null ? null : /* @__PURE__ */ v("span", {
94
+ id: P,
95
+ className: l,
96
+ children: D
97
+ }),
98
+ w ? /* @__PURE__ */ v("span", {
99
+ id: I,
100
+ className: "sr-only",
101
+ children: "(opens in a new tab)"
102
+ }) : null
103
+ ]
104
+ }),
105
+ O != null && !L ? /* @__PURE__ */ v("span", {
106
+ id: F,
107
+ className: f,
108
+ children: O
109
+ }) : null
110
+ ]
111
+ })]
112
+ });
113
+ }
114
+ //#endregion
115
+ export { C as LinkTile, s as linkTileLoadingRatio };
@@ -0,0 +1,31 @@
1
+ /** The frame's ratio (width / height) while `loading`, and for a tile with `ratio` left out there. */
2
+ export declare const linkTileLoadingRatio: number;
3
+ /**
4
+ * The whole tile is the link: one hit area and one focus ring, at the media frame's radius and
5
+ * 4px off the tile so it clears the image's edge.
6
+ */
7
+ export declare const linkTileRootClasses: string;
8
+ export declare const linkTileLoadingRootClasses = "flex w-full min-w-0 flex-col gap-3";
9
+ /**
10
+ * The media frame. The hairline is drawn over the image, inside its edge, so a light image on the
11
+ * light page and a dark image on the dark page both keep an edge; it darkens on hover and focus.
12
+ */
13
+ export declare const linkTileFrameClasses: string;
14
+ /** The image keeps its own ratio: the `width` and `height` on it reserve the space before it loads. */
15
+ export declare const linkTileFrameNaturalClasses = "[&>img]:block [&>img]:h-auto [&>img]:w-full";
16
+ /** A fixed `ratio`: the frame sets the shape and the image fills it, cropped from the center. */
17
+ export declare const linkTileFrameFixedClasses = "[&>img]:absolute [&>img]:inset-0 [&>img]:block [&>img]:size-full [&>img]:object-cover";
18
+ /** The tag over the image's top-start corner. */
19
+ export declare const linkTileTagOverClasses = "absolute start-2 top-2 z-[1] flex max-w-[calc(100%-1rem)]";
20
+ /** Under the image: the source mark, then the title over the meta line. */
21
+ export declare const linkTileCaptionClasses = "flex min-w-0 items-start gap-2";
22
+ /** Holds the source mark on the title's first line. */
23
+ export declare const linkTileSourceClasses = "flex h-5 shrink-0 items-center";
24
+ export declare const linkTileTextClasses = "flex min-w-0 flex-1 flex-col gap-0.5";
25
+ /** Two lines at most. Hover and keyboard focus underline it, as a quiet **TextLink** does. */
26
+ export declare const linkTileTitleClasses: string;
27
+ export declare const linkTileExternalIconClasses = "ml-1 inline-block size-[0.85em] align-[-0.05em] stroke-current text-muted";
28
+ export declare const linkTileMetaClasses: string;
29
+ /** A tag on a tile with no image sits at the caption's end. */
30
+ export declare const linkTileTagInlineClasses = "flex shrink-0 items-center";
31
+ //# sourceMappingURL=linkTileStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkTileStyles.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/LinkTile/linkTileStyles.ts"],"names":[],"mappings":"AAIA,sGAAsG;AACtG,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAI/B,CAAC;AAEF,eAAO,MAAM,0BAA0B,uCAAuC,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAMhC,CAAC;AAEF,uGAAuG;AACvG,eAAO,MAAM,2BAA2B,gDAAgD,CAAC;AAEzF,iGAAiG;AACjG,eAAO,MAAM,yBAAyB,0FACmD,CAAC;AAE1F,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,8DAA8D,CAAC;AAElG,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,mCAAmC,CAAC;AAEtE,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAE1E,8FAA8F;AAC9F,eAAO,MAAM,oBAAoB,QAMhC,CAAC;AAEF,eAAO,MAAM,2BAA2B,8EAA8E,CAAC;AAEvH,eAAO,MAAM,mBAAmB,QAA6C,CAAC;AAE9E,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,+BAA+B,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { cn as e } from "../../../lib/cn.js";
3
+ import { typographyClass as t } from "../../../lib/typography.js";
4
+ import { focusRingTransitionClasses as n, motionTransition as r } from "../../../lib/motion.js";
5
+ //#region src/components/molecules/LinkTile/linkTileStyles.ts
6
+ var i = 4 / 3, a = e("group/link-tile relative flex w-full min-w-0 flex-col gap-3 rounded-[var(--radius-card-body)] text-left no-underline", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-body", n), o = "flex w-full min-w-0 flex-col gap-3", s = e("relative block w-full overflow-hidden rounded-[var(--radius-card-body)] bg-muted-surface", "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-border after:content-['']", "after:transition-[border-color,background-color] after:duration-fast after:ease-standard", "group-hover/link-tile:after:border-border-emphasized group-hover/link-tile:after:bg-overlay-hover", "group-focus-visible/link-tile:after:border-border-emphasized"), c = "[&>img]:block [&>img]:h-auto [&>img]:w-full", l = "[&>img]:absolute [&>img]:inset-0 [&>img]:block [&>img]:size-full [&>img]:object-cover", u = "absolute start-2 top-2 z-[1] flex max-w-[calc(100%-1rem)]", d = "flex min-w-0 items-start gap-2", f = "flex h-5 shrink-0 items-center", p = "flex min-w-0 flex-1 flex-col gap-0.5", m = e(t("ui-label"), "line-clamp-2 text-pretty underline decoration-transparent decoration-solid underline-offset-[0.2em]", "transition-[text-decoration-color]", r("fast"), "group-hover/link-tile:decoration-current group-focus-visible/link-tile:decoration-current"), h = "ml-1 inline-block size-[0.85em] align-[-0.05em] stroke-current text-muted", g = e(t("caption"), "truncate"), _ = "flex shrink-0 items-center";
7
+ //#endregion
8
+ export { d as linkTileCaptionClasses, h as linkTileExternalIconClasses, s as linkTileFrameClasses, l as linkTileFrameFixedClasses, c as linkTileFrameNaturalClasses, i as linkTileLoadingRatio, o as linkTileLoadingRootClasses, g as linkTileMetaClasses, a as linkTileRootClasses, f as linkTileSourceClasses, _ as linkTileTagInlineClasses, u as linkTileTagOverClasses, p as linkTileTextClasses, m as linkTileTitleClasses };
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from "react";
2
+ import { type TileGridColumns, type TileGridLayout } from "./tileGridStyles";
3
+ export { tileGridBreakpoints, tileGridDefaultColumns, tileGridLayouts, type TileGridBreakpoint, type TileGridColumns, type TileGridLayout, } from "./tileGridStyles";
4
+ /** Layout-only — width, margin, or grid placement. */
5
+ export type TileGridLayoutClassName = string;
6
+ export interface TileGridProps {
7
+ /**
8
+ * `masonry` (default) — tiles keep their heights and pack upward, each next tile going to the
9
+ * shortest column. `uniform` — rows of equal height; give the tiles a fixed `ratio`.
10
+ */
11
+ layout?: TileGridLayout;
12
+ /**
13
+ * Columns — one number, or one per breakpoint (`{ base, sm, md, lg, xl }`; a breakpoint left out
14
+ * keeps the one below it). Default: `{ base: 1, sm: 2, lg: 3 }`. The gap is the grid gutter.
15
+ */
16
+ columns?: TileGridColumns;
17
+ /** Shown in place of the tiles when there are none — for example "No resources match these filters." */
18
+ empty?: ReactNode;
19
+ /** The tiles are placeholders for content on its way (`aria-busy`). */
20
+ busy?: boolean;
21
+ /** Names the list for screen readers, for example "Resources". */
22
+ "aria-label"?: string;
23
+ "aria-labelledby"?: string;
24
+ /** **TileGrid.Item** children, one per tile, each with a `key`. */
25
+ children?: ReactNode;
26
+ className?: TileGridLayoutClassName;
27
+ }
28
+ export interface TileGridItemProps {
29
+ /** One tile — a **LinkTile**, or a card. */
30
+ children?: ReactNode;
31
+ className?: TileGridLayoutClassName;
32
+ }
33
+ /**
34
+ * A grid of tiles on the page gutter. Reading order and tab order are the order of the items in
35
+ * both layouts: a masonry grid places each item in the shortest column, so the order runs across
36
+ * the top row and then down the page.
37
+ */
38
+ declare function TileGridRoot({ layout, columns, empty, busy, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, }: TileGridProps): import("react").JSX.Element;
39
+ export declare const TileGrid: typeof TileGridRoot & {
40
+ Item: import("react").ForwardRefExoticComponent<TileGridItemProps & import("react").RefAttributes<HTMLLIElement>>;
41
+ };
42
+ //# sourceMappingURL=TileGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TileGrid.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TileGrid/TileGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAE1B,sDAAsD;AACtD,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,wGAAwG;IACxG,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED;;;;GAIG;AACH,iBAAS,YAAY,CAAC,EACpB,MAAkB,EAClB,OAAO,EACP,KAAK,EACL,IAAY,EACZ,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,QAAQ,EACR,SAAS,GACV,EAAE,aAAa,+BAwFf;AA2BD,eAAO,MAAM,QAAQ;;CAEnB,CAAC"}
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { cn as e } from "../../../lib/cn.js";
3
+ import { motionTransitionProp as t } from "../../../lib/motion.js";
4
+ import { tileGridBreakpoints as n, tileGridDefaultColumns as r, tileGridEmptyClasses as i, tileGridItemClasses as a, tileGridItemInnerClasses as o, tileGridLayouts as s, tileGridListClasses as c, tileGridRootClasses as l } from "./tileGridStyles.js";
5
+ import { tileGridColumnCount as u, tileGridResolveColumns as d, tileGridRowSpan as f } from "./tileGridMath.js";
6
+ import { Children as p, forwardRef as m, isValidElement as h, useLayoutEffect as g, useMemo as _, useRef as v } from "react";
7
+ import { jsx as y } from "react/jsx-runtime";
8
+ import { AnimatePresence as b, motion as x, useReducedMotion as S } from "motion/react";
9
+ //#region src/components/molecules/TileGrid/TileGrid.tsx
10
+ function C({ layout: t = "masonry", columns: n, empty: r, busy: a = !1, "aria-label": o, "aria-labelledby": s, children: m, className: x }) {
11
+ let S = v(null), C = p.toArray(m), w = C.length > 0, T = C.map((e) => h(e) ? e.key : "").join("|"), E = d(n), D = _(() => ({
12
+ "--tile-grid-cols": E.base,
13
+ "--tile-grid-cols-sm": E.sm,
14
+ "--tile-grid-cols-md": E.md,
15
+ "--tile-grid-cols-lg": E.lg,
16
+ "--tile-grid-cols-xl": E.xl
17
+ }), [
18
+ E.base,
19
+ E.sm,
20
+ E.md,
21
+ E.lg,
22
+ E.xl
23
+ ]);
24
+ return g(() => {
25
+ let e = S.current;
26
+ if (e == null) return;
27
+ let n = () => [...e.children].filter((e) => e instanceof HTMLElement);
28
+ if (t !== "masonry" || typeof ResizeObserver > "u") {
29
+ e.removeAttribute("data-packed");
30
+ for (let e of n()) e.style.removeProperty("grid-row-end");
31
+ return;
32
+ }
33
+ let r = () => {
34
+ let t = u(getComputedStyle(e).gridTemplateColumns) > 1;
35
+ for (let e of n()) {
36
+ let n = e.firstElementChild;
37
+ if (t && n != null) {
38
+ let t = n instanceof HTMLElement ? n.offsetHeight : n.getBoundingClientRect().height;
39
+ e.style.gridRowEnd = `span ${f(t, 4)}`;
40
+ } else e.style.removeProperty("grid-row-end");
41
+ }
42
+ e.toggleAttribute("data-packed", t);
43
+ };
44
+ r();
45
+ let i = 0, a = new ResizeObserver(() => {
46
+ cancelAnimationFrame(i), i = requestAnimationFrame(r);
47
+ });
48
+ a.observe(e);
49
+ for (let e of n()) e.firstElementChild != null && a.observe(e.firstElementChild);
50
+ return () => {
51
+ cancelAnimationFrame(i), a.disconnect();
52
+ };
53
+ }, [t, T]), /* @__PURE__ */ y("div", {
54
+ className: e(l, x),
55
+ "data-tile-grid": "",
56
+ "data-layout": t,
57
+ children: w ? /* @__PURE__ */ y("ul", {
58
+ ref: S,
59
+ role: "list",
60
+ "aria-label": o,
61
+ "aria-labelledby": s,
62
+ "aria-busy": a || void 0,
63
+ className: c,
64
+ style: D,
65
+ children: /* @__PURE__ */ y(b, {
66
+ initial: !1,
67
+ mode: "popLayout",
68
+ children: m
69
+ })
70
+ }) : r == null ? null : /* @__PURE__ */ y("div", {
71
+ className: i,
72
+ "data-tile-grid-empty": "",
73
+ children: r
74
+ })
75
+ });
76
+ }
77
+ var w = m(function({ children: n, className: r }, i) {
78
+ let s = S() ?? !1;
79
+ return /* @__PURE__ */ y(x.li, {
80
+ ref: i,
81
+ layout: !s && "position",
82
+ initial: !s && { opacity: 0 },
83
+ animate: { opacity: 1 },
84
+ exit: s ? void 0 : { opacity: 0 },
85
+ transition: t("medium"),
86
+ className: e(a, r),
87
+ children: /* @__PURE__ */ y("div", {
88
+ className: o,
89
+ children: n
90
+ })
91
+ });
92
+ }), T = Object.assign(C, { Item: w });
93
+ //#endregion
94
+ export { T as TileGrid, n as tileGridBreakpoints, r as tileGridDefaultColumns, s as tileGridLayouts };
@@ -0,0 +1,8 @@
1
+ import { type TileGridBreakpoint, type TileGridColumns } from "./tileGridStyles";
2
+ /** Columns at every breakpoint — each one left out takes the one below it; at least 1, whole. */
3
+ export declare function tileGridResolveColumns(columns?: TileGridColumns): Record<TileGridBreakpoint, number>;
4
+ /** How many row tracks an item of this height takes in a packed grid. */
5
+ export declare function tileGridRowSpan(heightPx: number, unitPx: number): number;
6
+ /** The column count a computed `grid-template-columns` describes ("285px 285px 285px" → 3). */
7
+ export declare function tileGridColumnCount(template: string): number;
8
+ //# sourceMappingURL=tileGridMath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileGridMath.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TileGrid/tileGridMath.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,iGAAiG;AACjG,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,eAAwC,GAChD,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAWpC;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5D"}
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { tileGridBreakpoints as e, tileGridDefaultColumns as t } from "./tileGridStyles.js";
3
+ //#region src/components/molecules/TileGrid/tileGridMath.ts
4
+ function n(n = t) {
5
+ let r = typeof n == "number" ? { base: n } : n, i = 1, a = {};
6
+ for (let t of e) {
7
+ let e = r[t];
8
+ e != null && Number.isFinite(e) && (i = Math.max(1, Math.round(e))), a[t] = i;
9
+ }
10
+ return a;
11
+ }
12
+ function r(e, t) {
13
+ return Math.max(1, Math.ceil(e / t));
14
+ }
15
+ function i(e) {
16
+ let t = e.trim().split(/\s+/).filter((e) => e !== "" && e !== "none");
17
+ return Math.max(1, t.length);
18
+ }
19
+ //#endregion
20
+ export { i as tileGridColumnCount, n as tileGridResolveColumns, r as tileGridRowSpan };
@@ -0,0 +1,33 @@
1
+ export declare const tileGridLayouts: readonly ["masonry", "uniform"];
2
+ /**
3
+ * `masonry` (default) — tiles keep their heights and pack upward. `uniform` — rows of equal
4
+ * height; pair it with a fixed `ratio` on the tiles.
5
+ */
6
+ export type TileGridLayout = (typeof tileGridLayouts)[number];
7
+ export declare const tileGridBreakpoints: readonly ["base", "sm", "md", "lg", "xl"];
8
+ export type TileGridBreakpoint = (typeof tileGridBreakpoints)[number];
9
+ /** Columns per breakpoint. A breakpoint left out keeps the one below it. */
10
+ export type TileGridColumns = number | Partial<Record<TileGridBreakpoint, number>>;
11
+ /** One column on phones, two from `sm`, three from `lg` — for a 9-column main area beside a rail. */
12
+ export declare const tileGridDefaultColumns: {
13
+ readonly base: 1;
14
+ readonly sm: 2;
15
+ readonly lg: 3;
16
+ };
17
+ /**
18
+ * A packed grid's row track, in px (`--spacing`). A tile spans as many as its height needs, so
19
+ * the space under a tile is the row gap plus at most this much less one.
20
+ */
21
+ export declare const tileGridRowUnitPx = 4;
22
+ export declare const tileGridRootClasses = "w-full min-w-0";
23
+ /**
24
+ * Columns on the page grid's gutter. The row gap is padding under each item, not a grid gap: a
25
+ * packed grid has 4px row tracks and no gap between them. The margin takes the last row's back.
26
+ */
27
+ export declare const tileGridListClasses: string;
28
+ export declare const tileGridItemClasses = "min-w-0";
29
+ /** What is measured: the tile and the row gap under it. */
30
+ export declare const tileGridItemInnerClasses = "flex min-w-0 flex-col pb-[var(--tile-grid-row-gap)]";
31
+ /** Empty state — in the tiles' place. */
32
+ export declare const tileGridEmptyClasses = "py-8";
33
+ //# sourceMappingURL=tileGridStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileGridStyles.d.ts","sourceRoot":"","sources":["../../../../src/components/molecules/TileGrid/tileGridStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,iCAAkC,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,eAAO,MAAM,mBAAmB,2CAA4C,CAAC;AAE7E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;AAEnF,qGAAqG;AACrG,eAAO,MAAM,sBAAsB;;;;CAA+D,CAAC;AAEnG;;;GAGG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAQ/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,wDAAwD,CAAC;AAE9F,yCAAyC;AACzC,eAAO,MAAM,oBAAoB,SAAS,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { cn as e } from "../../../lib/cn.js";
3
+ //#region src/components/molecules/TileGrid/tileGridStyles.ts
4
+ var t = ["masonry", "uniform"], n = [
5
+ "base",
6
+ "sm",
7
+ "md",
8
+ "lg",
9
+ "xl"
10
+ ], r = {
11
+ base: 1,
12
+ sm: 2,
13
+ lg: 3
14
+ }, i = "w-full min-w-0", a = e("relative m-0 -mb-[var(--tile-grid-row-gap)] grid list-none items-start p-0 [--tile-grid-row-gap:2rem]", "gap-x-[var(--grid-column-gap,1.5rem)] gap-y-0", "grid-cols-[repeat(var(--tile-grid-cols),minmax(0,1fr))] sm:grid-cols-[repeat(var(--tile-grid-cols-sm),minmax(0,1fr))]", "md:grid-cols-[repeat(var(--tile-grid-cols-md),minmax(0,1fr))] lg:grid-cols-[repeat(var(--tile-grid-cols-lg),minmax(0,1fr))]", "xl:grid-cols-[repeat(var(--tile-grid-cols-xl),minmax(0,1fr))]", "data-[packed]:auto-rows-[4px]"), o = "min-w-0", s = "flex min-w-0 flex-col pb-[var(--tile-grid-row-gap)]", c = "py-8";
15
+ //#endregion
16
+ export { n as tileGridBreakpoints, r as tileGridDefaultColumns, c as tileGridEmptyClasses, o as tileGridItemClasses, s as tileGridItemInnerClasses, t as tileGridLayouts, a as tileGridListClasses, i as tileGridRootClasses };
@@ -20,11 +20,19 @@ export interface DialogContentProps {
20
20
  description?: ReactNode;
21
21
  /** Leading header slot — icon, **Badge**, **Status** (**Card.Header** `start`). */
22
22
  headerStart?: ReactNode;
23
+ /** Actions before the close — **IconButton** `size="sm"`, for example previous and next. */
24
+ headerEnd?: ReactNode;
23
25
  /** Header dismiss — default `true` for generic dialogs. */
24
26
  showClose?: boolean;
25
27
  /** Footer action cluster — compose **Button** roles. */
26
28
  footer?: ReactNode;
29
+ /**
30
+ * `sm` / `md` (default) / `lg` — a centered panel of that width. `full` — the window less a
31
+ * margin (up to 90rem × 60rem), and the whole screen on phones: media viewers and detail views.
32
+ */
27
33
  size?: DialogSize;
34
+ /** Names the dialog when there is no `title` — the id of a heading inside the body. */
35
+ "aria-labelledby"?: string;
28
36
  className?: DialogLayoutClassName;
29
37
  children?: ReactNode;
30
38
  }
@@ -33,16 +41,18 @@ export interface DialogPortalProps {
33
41
  "aria-labelledby"?: string;
34
42
  "aria-describedby"?: string;
35
43
  initialFocusSelector?: string;
44
+ /** Fill the window (the whole screen on phones) — **Dialog.Content** `size="full"`. */
45
+ fill?: boolean;
36
46
  children: ReactNode;
37
47
  }
38
48
  /** Portal + scrim + focus trap — shared by **Dialog.Content** and **AlertDialog**. */
39
- export declare function DialogPortal({ role, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, initialFocusSelector, children, }: DialogPortalProps): import("react").ReactPortal | null;
49
+ export declare function DialogPortal({ role, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, initialFocusSelector, fill, children, }: DialogPortalProps): import("react").ReactPortal | null;
40
50
  declare function DialogRoot({ open, onOpenChange, dismissOnBackdrop, dismissOnEscape, children, }: DialogProps): import("react").JSX.Element;
41
51
  declare function DialogTrigger({ children, className, }: {
42
52
  children: ReactNode;
43
53
  className?: DialogLayoutClassName;
44
54
  }): import("react").JSX.Element;
45
- declare function DialogContent({ title, description, headerStart, showClose, footer, size, className, children, }: DialogContentProps): import("react").JSX.Element;
55
+ declare function DialogContent({ title, description, headerStart, showClose, headerEnd, footer, size, "aria-labelledby": ariaLabelledBy, className, children, }: DialogContentProps): import("react").JSX.Element;
46
56
  /**
47
57
  * Modal overlay — portal scrim, focus trap, scroll lock; composes **Card** shell.
48
58
  * Composes **Card** shell + portal scrim; use **AlertDialog** for blocking confirms.
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/organisms/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAef,OAAO,EAIL,WAAW,EAMX,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,0EAA0E;AAC1E,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wCAAwC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,mFAAmF;IACnF,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,iBAAiB,EAAE,cAAc,EACjC,kBAAkB,EAAE,eAAe,EACnC,oBAAoB,EACpB,QAAQ,GACT,EAAE,iBAAiB,sCA6GnB;AAED,iBAAS,UAAU,CAAC,EAClB,IAAI,EACJ,YAAY,EACZ,iBAAwB,EACxB,eAAsB,EACtB,QAAQ,GACT,EAAE,WAAW,+BAmBb;AAED,iBAAS,aAAa,CAAC,EACrB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC,+BAmBA;AAED,iBAAS,aAAa,CAAC,EACrB,KAAK,EACL,WAAW,EACX,WAAW,EACX,SAAgB,EAChB,MAAM,EACN,IAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE,kBAAkB,+BAsDpB;AAED;;;GAGG;AACH,eAAO,MAAM,MAAM;;;CAGjB,CAAC"}
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/organisms/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAef,OAAO,EAML,WAAW,EAMX,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,0EAA0E;AAC1E,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wCAAwC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,mFAAmF;IACnF,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,iBAAiB,EAAE,cAAc,EACjC,kBAAkB,EAAE,eAAe,EACnC,oBAAoB,EACpB,IAAY,EACZ,QAAQ,GACT,EAAE,iBAAiB,sCA6GnB;AAED,iBAAS,UAAU,CAAC,EAClB,IAAI,EACJ,YAAY,EACZ,iBAAwB,EACxB,eAAsB,EACtB,QAAQ,GACT,EAAE,WAAW,+BAmBb;AAED,iBAAS,aAAa,CAAC,EACrB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC,+BAmBA;AAED,iBAAS,aAAa,CAAC,EACrB,KAAK,EACL,WAAW,EACX,WAAW,EACX,SAAgB,EAChB,SAAS,EACT,MAAM,EACN,IAAW,EACX,iBAAiB,EAAE,cAAc,EACjC,SAAS,EACT,QAAQ,GACT,EAAE,kBAAkB,+BAwDpB;AAED;;;GAGG;AACH,eAAO,MAAM,MAAM;;;CAGjB,CAAC"}