@charcuterie/ui 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/ActionTiles/ActionTiles.d.ts +66 -8
- package/dist/ActionTiles/ActionTiles.d.ts.map +1 -1
- package/dist/ActionTiles/ActionTiles.js +88 -11
- package/dist/ActionTiles/ActionTiles.js.map +1 -1
- package/dist/PortraitTiles/PortraitAvatar.d.ts +21 -0
- package/dist/PortraitTiles/PortraitAvatar.d.ts.map +1 -0
- package/dist/PortraitTiles/PortraitAvatar.js +29 -0
- package/dist/PortraitTiles/PortraitAvatar.js.map +1 -0
- package/dist/PortraitTiles/PortraitTiles.d.ts +162 -0
- package/dist/PortraitTiles/PortraitTiles.d.ts.map +1 -0
- package/dist/PortraitTiles/PortraitTiles.js +263 -0
- package/dist/PortraitTiles/PortraitTiles.js.map +1 -0
- package/dist/PortraitTiles/portraitColour.d.ts +49 -0
- package/dist/PortraitTiles/portraitColour.d.ts.map +1 -0
- package/dist/PortraitTiles/portraitColour.js +55 -0
- package/dist/PortraitTiles/portraitColour.js.map +1 -0
- package/dist/RouterLink/UnstyledLink.d.ts +17 -0
- package/dist/RouterLink/UnstyledLink.d.ts.map +1 -0
- package/dist/RouterLink/UnstyledLink.js +24 -0
- package/dist/RouterLink/UnstyledLink.js.map +1 -0
- package/dist/categoricalStyles.d.ts +27 -0
- package/dist/categoricalStyles.d.ts.map +1 -1
- package/dist/categoricalStyles.js +49 -0
- package/dist/categoricalStyles.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tileStyles.d.ts +15 -0
- package/dist/tileStyles.d.ts.map +1 -1
- package/dist/tileStyles.js +26 -1
- package/dist/tileStyles.js.map +1 -1
- package/package.json +2 -2
- package/src/ActionTiles/ActionTiles.mdx +82 -8
- package/src/ActionTiles/ActionTiles.stories.tsx +107 -2
- package/src/ActionTiles/ActionTiles.test.tsx +205 -5
- package/src/ActionTiles/ActionTiles.tsx +274 -110
- package/src/PortraitTiles/PortraitAvatar.tsx +49 -0
- package/src/PortraitTiles/PortraitTiles.mdx +153 -0
- package/src/PortraitTiles/PortraitTiles.stories.tsx +403 -0
- package/src/PortraitTiles/PortraitTiles.test.tsx +399 -0
- package/src/PortraitTiles/PortraitTiles.tsx +577 -0
- package/src/PortraitTiles/portraitColour.test.ts +39 -0
- package/src/PortraitTiles/portraitColour.ts +67 -0
- package/src/RadioGroup/RadioGroup.stories.tsx +39 -0
- package/src/RouterLink/RouterLink.mdx +25 -4
- package/src/RouterLink/UnstyledLink.stories.tsx +57 -0
- package/src/RouterLink/UnstyledLink.test.tsx +40 -0
- package/src/RouterLink/UnstyledLink.tsx +34 -0
- package/src/categoricalStyles.ts +57 -0
- package/src/index.ts +11 -0
- package/src/sourceRules.test.ts +17 -4
- package/src/tileStyles.ts +30 -1
|
@@ -139,6 +139,45 @@ export const Tiles: Story = {
|
|
|
139
139
|
},
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The tile at all three sizes.
|
|
144
|
+
*
|
|
145
|
+
* `AllVariants` above covers the three heights of a **row**, and
|
|
146
|
+
* every tile story took the default `md` — so the largest tile had
|
|
147
|
+
* no snapshot at all, and `TILE_PADDING_CLASS.lg` could be changed
|
|
148
|
+
* without a single pixel moving in the report. It was, on
|
|
149
|
+
* 2026-09-02, when `lg` grew to a landing-page tile's padding.
|
|
150
|
+
*
|
|
151
|
+
* The padding is shared with `ActionTiles` through `tileStyles.ts`,
|
|
152
|
+
* which is the promise this board is here to photograph.
|
|
153
|
+
*/
|
|
154
|
+
export const TileSizes: Story = {
|
|
155
|
+
args: {
|
|
156
|
+
items: IMPORT_ITEMS,
|
|
157
|
+
itemShape: "tile",
|
|
158
|
+
label: "On import",
|
|
159
|
+
},
|
|
160
|
+
render: (controlProps) => (
|
|
161
|
+
<StorySection title="One box at three sizes, and the same box ActionTiles reads from.">
|
|
162
|
+
<StoryGrid columns={3}>
|
|
163
|
+
{(["sm", "md", "lg"] as const).map((size) => (
|
|
164
|
+
<StoryCell
|
|
165
|
+
align="stretch"
|
|
166
|
+
key={size}
|
|
167
|
+
label={size}
|
|
168
|
+
>
|
|
169
|
+
<RadioGroup
|
|
170
|
+
{...controlProps}
|
|
171
|
+
label={`On import at ${size}`}
|
|
172
|
+
size={size}
|
|
173
|
+
/>
|
|
174
|
+
</StoryCell>
|
|
175
|
+
))}
|
|
176
|
+
</StoryGrid>
|
|
177
|
+
</StorySection>
|
|
178
|
+
),
|
|
179
|
+
}
|
|
180
|
+
|
|
142
181
|
/**
|
|
143
182
|
* A tile may lead with an icon. The library ships none — these are
|
|
144
183
|
* story-only SVGs, exactly what an app passes — and the icon is
|
|
@@ -4,10 +4,11 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
4
4
|
|
|
5
5
|
# Routing
|
|
6
6
|
|
|
7
|
-
`TextLink` and `
|
|
8
|
-
at all — it navigates, it can be middle-clicked, ctrl-clicked, opened
|
|
9
|
-
copied — it just reloads the page. **One provider at the app root**
|
|
10
|
-
the tree to the app's own soft navigation, with no change at any
|
|
7
|
+
`TextLink`, `ButtonLink`, and `UnstyledLink` render a real `<a href>`. That is already
|
|
8
|
+
correct with no setup at all — it navigates, it can be middle-clicked, ctrl-clicked, opened
|
|
9
|
+
in a new tab and copied — it just reloads the page. **One provider at the app root**
|
|
10
|
+
upgrades every link in the tree to the app's own soft navigation, with no change at any
|
|
11
|
+
call site.
|
|
11
12
|
|
|
12
13
|
## Why the library does not just import a router
|
|
13
14
|
|
|
@@ -107,6 +108,25 @@ export const ChannelCard = ({ channel }) => (
|
|
|
107
108
|
)
|
|
108
109
|
```
|
|
109
110
|
|
|
111
|
+
`TextLink` owns the fleet's inline and standalone text-link paint. `ButtonLink` owns the
|
|
112
|
+
button-shaped paint. When an app already owns the complete appearance and must preserve it,
|
|
113
|
+
`UnstyledLink` supplies only the same routing behavior:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { UnstyledLink } from "@charcuterie/ui"
|
|
117
|
+
|
|
118
|
+
<UnstyledLink
|
|
119
|
+
className="text-xs text-content-secondary hover:text-content-primary"
|
|
120
|
+
href="/jobs"
|
|
121
|
+
>
|
|
122
|
+
Jobs
|
|
123
|
+
</UnstyledLink>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
It adds no class and changes no target, relation, or accessible name. Prefer the two painted
|
|
127
|
+
components for new UI. Use `UnstyledLink` for an established app-owned treatment whose
|
|
128
|
+
appearance is not part of the change.
|
|
129
|
+
|
|
110
130
|
## Any other router
|
|
111
131
|
|
|
112
132
|
The seam's contract is **a component that takes `href`**, so an adapter is a rename. There
|
|
@@ -208,6 +228,7 @@ what `Main.stories.tsx` does.
|
|
|
208
228
|
| `useRouterLink()` | `@charcuterie/ui` | The injected component, or `AnchorLink`. For an app building its own link-shaped component. |
|
|
209
229
|
| `RouterLinkComponent` / `RouterLinkProps` | `@charcuterie/ui` | The contract: `ComponentPropsWithRef<"a">` with `href` required. |
|
|
210
230
|
| `AnchorLink` | `@charcuterie/ui` | The plain `<a href>` default. |
|
|
231
|
+
| `UnstyledLink` | `@charcuterie/ui` | The router-aware link seam with no library paint. |
|
|
211
232
|
| `getIsRoutedHref(href)` | `@charcuterie/ui` | The destination test above. |
|
|
212
233
|
| `ScrollMemoryProvider` | `@charcuterie/ui` | The scroll seam. `entry` is `{ key, path }` for the current history entry. |
|
|
213
234
|
| `ScrollEntry` | `@charcuterie/ui` | That pair's type. |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
import { RouterLinkProvider } from "./RouterLinkProvider.tsx"
|
|
5
|
+
import type { RouterLinkProps } from "./routerLink.ts"
|
|
6
|
+
import { UnstyledLink } from "./UnstyledLink.tsx"
|
|
7
|
+
|
|
8
|
+
const SoftRouterLink = ({
|
|
9
|
+
href,
|
|
10
|
+
...linkProps
|
|
11
|
+
}: RouterLinkProps): ReactNode => (
|
|
12
|
+
<a {...linkProps} data-router="soft" href={href} />
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: "Utilities/UnstyledLink",
|
|
17
|
+
component: UnstyledLink,
|
|
18
|
+
parameters: { layout: "padded" },
|
|
19
|
+
args: {
|
|
20
|
+
children: "Jobs",
|
|
21
|
+
className:
|
|
22
|
+
"text-sm text-intent-accent-content hover:underline",
|
|
23
|
+
href: "/jobs",
|
|
24
|
+
},
|
|
25
|
+
} satisfies Meta<typeof UnstyledLink>
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
28
|
+
|
|
29
|
+
type Story = StoryObj<typeof meta>
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {}
|
|
32
|
+
|
|
33
|
+
export const Routed: Story = {
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (
|
|
36
|
+
<RouterLinkProvider link={SoftRouterLink}>
|
|
37
|
+
<Story />
|
|
38
|
+
</RouterLinkProvider>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
render: (linkProps) => (
|
|
42
|
+
<div className="flex gap-4">
|
|
43
|
+
<UnstyledLink {...linkProps}>
|
|
44
|
+
Routed jobs
|
|
45
|
+
</UnstyledLink>
|
|
46
|
+
<UnstyledLink
|
|
47
|
+
{...linkProps}
|
|
48
|
+
href="https://example.com/jobs"
|
|
49
|
+
>
|
|
50
|
+
External jobs
|
|
51
|
+
</UnstyledLink>
|
|
52
|
+
<UnstyledLink {...linkProps} href="#jobs">
|
|
53
|
+
Jobs fragment
|
|
54
|
+
</UnstyledLink>
|
|
55
|
+
</div>
|
|
56
|
+
),
|
|
57
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
6
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
7
|
+
import * as stories from "./UnstyledLink.stories.tsx"
|
|
8
|
+
|
|
9
|
+
const { Default, Routed } = composeStories(stories)
|
|
10
|
+
|
|
11
|
+
test("keeps the caller's paint and works without a router", async () => {
|
|
12
|
+
const { canvas } = await mountStory(Default)
|
|
13
|
+
const link = expectAgentDrivable(canvas, {
|
|
14
|
+
name: "Jobs",
|
|
15
|
+
role: "link",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
await expect(link).toHaveAttribute("href", "/jobs")
|
|
19
|
+
await expect(link).toHaveAttribute(
|
|
20
|
+
"class",
|
|
21
|
+
"text-sm text-intent-accent-content hover:underline",
|
|
22
|
+
)
|
|
23
|
+
await expect(link).not.toHaveAttribute("data-router")
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("routes only destinations the injected router can serve", async () => {
|
|
27
|
+
const { canvas } = await mountStory(Routed)
|
|
28
|
+
|
|
29
|
+
await expect(
|
|
30
|
+
canvas.getByRole("link", { name: "Routed jobs" }),
|
|
31
|
+
).toHaveAttribute("data-router", "soft")
|
|
32
|
+
|
|
33
|
+
await expect(
|
|
34
|
+
canvas.getByRole("link", { name: "External jobs" }),
|
|
35
|
+
).not.toHaveAttribute("data-router")
|
|
36
|
+
|
|
37
|
+
await expect(
|
|
38
|
+
canvas.getByRole("link", { name: "Jobs fragment" }),
|
|
39
|
+
).not.toHaveAttribute("data-router")
|
|
40
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
import { AnchorLink } from "./AnchorLink.tsx"
|
|
4
|
+
import { useRouterLink } from "./RouterLinkProvider.tsx"
|
|
5
|
+
import {
|
|
6
|
+
getIsRoutedHref,
|
|
7
|
+
type RouterLinkProps,
|
|
8
|
+
} from "./routerLink.ts"
|
|
9
|
+
|
|
10
|
+
export type UnstyledLinkProps = RouterLinkProps
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A router-aware link that owns no paint.
|
|
14
|
+
*
|
|
15
|
+
* Use this only when the caller already owns the complete visual
|
|
16
|
+
* treatment. `TextLink` and `ButtonLink` remain the defaults for the
|
|
17
|
+
* two link appearances the library owns.
|
|
18
|
+
*
|
|
19
|
+
* An in-app destination goes through the injected router. Another
|
|
20
|
+
* origin, another scheme, or a same-page fragment stays a platform
|
|
21
|
+
* anchor. With no provider, the injected component is `AnchorLink`,
|
|
22
|
+
* so this remains a working `<a href>` in Storybook and tests.
|
|
23
|
+
*/
|
|
24
|
+
export const UnstyledLink = ({
|
|
25
|
+
href,
|
|
26
|
+
...linkProps
|
|
27
|
+
}: UnstyledLinkProps): ReactNode => {
|
|
28
|
+
const RouterLink = useRouterLink()
|
|
29
|
+
const LinkElement = getIsRoutedHref(href)
|
|
30
|
+
? RouterLink
|
|
31
|
+
: AnchorLink
|
|
32
|
+
|
|
33
|
+
return <LinkElement {...linkProps} href={href} />
|
|
34
|
+
}
|
package/src/categoricalStyles.ts
CHANGED
|
@@ -242,3 +242,60 @@ export const CATEGORICAL_CONTENT_CLASS: Record<
|
|
|
242
242
|
9: "text-categorical-9-content",
|
|
243
243
|
10: "text-categorical-10-content",
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The hover BORDER, which `CATEGORICAL_HOVER_CLASS` deliberately
|
|
248
|
+
* does not carry.
|
|
249
|
+
*
|
|
250
|
+
* That map answers "what does this surface do under the pointer",
|
|
251
|
+
* and a `Badge` has no border state to change. A tile does: it is a
|
|
252
|
+
* bordered card, and the whole point of colouring one is that the
|
|
253
|
+
* box reacts in its own hue rather than in the neutral
|
|
254
|
+
* `border-strong` every other box uses. Kept separate rather than
|
|
255
|
+
* added as a fifth appearance key, because an appearance is a claim
|
|
256
|
+
* about the fill and this is not one.
|
|
257
|
+
*
|
|
258
|
+
* Written out in full for the reason this file opens with.
|
|
259
|
+
*/
|
|
260
|
+
export const CATEGORICAL_HOVER_BORDER_CLASS: Record<
|
|
261
|
+
CategoricalIndex,
|
|
262
|
+
string
|
|
263
|
+
> = {
|
|
264
|
+
1: "hover:border-categorical-1-border",
|
|
265
|
+
2: "hover:border-categorical-2-border",
|
|
266
|
+
3: "hover:border-categorical-3-border",
|
|
267
|
+
4: "hover:border-categorical-4-border",
|
|
268
|
+
5: "hover:border-categorical-5-border",
|
|
269
|
+
6: "hover:border-categorical-6-border",
|
|
270
|
+
7: "hover:border-categorical-7-border",
|
|
271
|
+
8: "hover:border-categorical-8-border",
|
|
272
|
+
9: "hover:border-categorical-9-border",
|
|
273
|
+
10: "hover:border-categorical-10-border",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* A halo the width of a ring, in the hue's SURFACE.
|
|
278
|
+
*
|
|
279
|
+
* `surface` and not `border`: this sits directly against the solid
|
|
280
|
+
* fill it surrounds, so it is a soft step out of the colour rather
|
|
281
|
+
* than a second line around it. `PortraitTiles` draws it around a
|
|
282
|
+
* face, where a hard outline reads as a frame and this reads as
|
|
283
|
+
* light.
|
|
284
|
+
*
|
|
285
|
+
* Written out in full for the reason this file opens with.
|
|
286
|
+
*/
|
|
287
|
+
export const CATEGORICAL_RING_CLASS: Record<
|
|
288
|
+
CategoricalIndex,
|
|
289
|
+
string
|
|
290
|
+
> = {
|
|
291
|
+
1: "ring-categorical-1-surface",
|
|
292
|
+
2: "ring-categorical-2-surface",
|
|
293
|
+
3: "ring-categorical-3-surface",
|
|
294
|
+
4: "ring-categorical-4-surface",
|
|
295
|
+
5: "ring-categorical-5-surface",
|
|
296
|
+
6: "ring-categorical-6-surface",
|
|
297
|
+
7: "ring-categorical-7-surface",
|
|
298
|
+
8: "ring-categorical-8-surface",
|
|
299
|
+
9: "ring-categorical-9-surface",
|
|
300
|
+
10: "ring-categorical-10-surface",
|
|
301
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export type {
|
|
|
39
39
|
export { Accordion } from "./Accordion/Accordion.tsx"
|
|
40
40
|
export type {
|
|
41
41
|
ActionTileItem,
|
|
42
|
+
ActionTilesAccent,
|
|
42
43
|
ActionTilesProps,
|
|
43
44
|
} from "./ActionTiles/ActionTiles.tsx"
|
|
44
45
|
export { ActionTiles } from "./ActionTiles/ActionTiles.tsx"
|
|
@@ -129,7 +130,9 @@ export { copyText } from "./CopyButton/copyText.ts"
|
|
|
129
130
|
export {
|
|
130
131
|
CATEGORICAL_APPEARANCE_CLASS,
|
|
131
132
|
CATEGORICAL_CONTENT_CLASS,
|
|
133
|
+
CATEGORICAL_HOVER_BORDER_CLASS,
|
|
132
134
|
CATEGORICAL_HOVER_CLASS,
|
|
135
|
+
CATEGORICAL_RING_CLASS,
|
|
133
136
|
CATEGORICAL_SOLID_FILL_CLASS,
|
|
134
137
|
} from "./categoricalStyles.ts"
|
|
135
138
|
export type { BadgeSize } from "./controlStyles.ts"
|
|
@@ -325,6 +328,12 @@ export type {
|
|
|
325
328
|
export { Picker } from "./Picker/Picker.tsx"
|
|
326
329
|
export type { PopoverProps } from "./Popover/Popover.tsx"
|
|
327
330
|
export { Popover } from "./Popover/Popover.tsx"
|
|
331
|
+
export type {
|
|
332
|
+
PortraitTileItem,
|
|
333
|
+
PortraitTilesLayout,
|
|
334
|
+
PortraitTilesProps,
|
|
335
|
+
} from "./PortraitTiles/PortraitTiles.tsx"
|
|
336
|
+
export { PortraitTiles } from "./PortraitTiles/PortraitTiles.tsx"
|
|
328
337
|
export type {
|
|
329
338
|
ProgressBarProps,
|
|
330
339
|
ProgressBarSize,
|
|
@@ -374,6 +383,8 @@ export type {
|
|
|
374
383
|
RouterLinkProps,
|
|
375
384
|
} from "./RouterLink/routerLink.ts"
|
|
376
385
|
export { getIsRoutedHref } from "./RouterLink/routerLink.ts"
|
|
386
|
+
export type { UnstyledLinkProps } from "./RouterLink/UnstyledLink.tsx"
|
|
387
|
+
export { UnstyledLink } from "./RouterLink/UnstyledLink.tsx"
|
|
377
388
|
export type { SearchInputProps } from "./SearchInput/SearchInput.tsx"
|
|
378
389
|
export { SearchInput } from "./SearchInput/SearchInput.tsx"
|
|
379
390
|
export type {
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -236,6 +236,14 @@ test("a container-query component is never storied in a shrink-to-fit cell", ()
|
|
|
236
236
|
"Main",
|
|
237
237
|
"MarkdownEditor",
|
|
238
238
|
"MediaTile",
|
|
239
|
+
// `PortraitTiles` declares its container on the SET rather than
|
|
240
|
+
// on a tile, and that is the whole design: the question is not
|
|
241
|
+
// "how wide is this one face" but "does this picker have room to
|
|
242
|
+
// stack". A phone shows one full-width column of rows; a wall
|
|
243
|
+
// display shows a row of tall columns. Querying a tile would
|
|
244
|
+
// answer neither, because a track is about the same width in
|
|
245
|
+
// both.
|
|
246
|
+
"PortraitTiles",
|
|
239
247
|
// `Stepper`'s container is the one that decides whether a
|
|
240
248
|
// `horizontal` sequence is actually horizontal. Below `--cq-md`
|
|
241
249
|
// it falls back to the vertical column, because four steps side
|
|
@@ -374,11 +382,10 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
374
382
|
// back-link and made `Button` get used for navigation: `TextLink`
|
|
375
383
|
// and `ButtonLink`, both a real `<a href>`, differing in paint
|
|
376
384
|
// rather than semantics: +2 -> 38, on top of `AdaptiveGrid`'s 36.
|
|
377
|
-
// `AnchorLink`,
|
|
385
|
+
// `AnchorLink`, `UnstyledLink`,
|
|
378
386
|
// `RouterLinkProvider`, `ReactRouterLink`, `ScrollMemoryProvider`
|
|
379
387
|
// and `ReactRouterAdapter` are the router seam rather
|
|
380
|
-
// than components
|
|
381
|
-
// `Main/` and
|
|
388
|
+
// than painted components, and live in `RouterLink/`, `Main/` and
|
|
382
389
|
// `reactRouter/` — outside this count by the `<Name>/<Name>.tsx`
|
|
383
390
|
// rule, exactly as `Overlay/`'s parts are.
|
|
384
391
|
// The unified app shell adds four: `Shell`, `Header`, `Rail`,
|
|
@@ -571,7 +578,13 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
571
578
|
// `isRange` is right there and wrong here for a reason that is
|
|
572
579
|
// written down — a second text field is still a text field, while a
|
|
573
580
|
// second thumb cannot share one `aria-valuenow`.
|
|
574
|
-
|
|
581
|
+
//
|
|
582
|
+
// `PortraitTiles` is +1 -> 65. It is the points-market kid picker:
|
|
583
|
+
// a face, a name and one number, which is a different question
|
|
584
|
+
// from `ActionTiles`' "what do you want to do" and would have
|
|
585
|
+
// meant one component whose icon is sometimes a 20px glyph beside
|
|
586
|
+
// a title and sometimes a 144px circle above it.
|
|
587
|
+
expect(componentNames.length).toBe(65)
|
|
575
588
|
|
|
576
589
|
for (const name of componentNames) {
|
|
577
590
|
expect(barrel).toContain(`export { ${name} }`)
|
package/src/tileStyles.ts
CHANGED
|
@@ -38,7 +38,36 @@ export const TILE_PADDING_CLASS: Record<
|
|
|
38
38
|
> = {
|
|
39
39
|
sm: "px-3 py-2.5",
|
|
40
40
|
md: "px-3.5 py-3",
|
|
41
|
-
lg
|
|
41
|
+
// `lg` is the landing-page tile — mux-magic's "Pick a tool",
|
|
42
|
+
// Gallery Downloader's two cards — where the tile is the only
|
|
43
|
+
// thing on the page and reads as a panel rather than as a row.
|
|
44
|
+
// It was `px-4 py-3.5`, which is a control's padding wearing a
|
|
45
|
+
// card's name, and the owner called the result "very boring"
|
|
46
|
+
// when it shipped in QueuePilot.
|
|
47
|
+
lg: "px-6 py-5",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What a tile with an accent edge adds to its LEADING padding.
|
|
52
|
+
*
|
|
53
|
+
* The bar is drawn by `Card`'s accent-edge pseudo-element, which is
|
|
54
|
+
* an overlay rather than a border — so it takes no space in the box
|
|
55
|
+
* model and lands on top of the text unless the tile makes room for
|
|
56
|
+
* it. `Card` has the same arrangement and the same answer.
|
|
57
|
+
*
|
|
58
|
+
* A separate map rather than a second full padding scale, because
|
|
59
|
+
* only the leading side changes and the shared box is the promise
|
|
60
|
+
* the 2026-09-01 record made. It is applied ONLY when an edge is
|
|
61
|
+
* drawn: a radio tile has no bar, and giving it this padding would
|
|
62
|
+
* be dead space on the side its own control sits on.
|
|
63
|
+
*/
|
|
64
|
+
export const TILE_ACCENT_EDGE_PADDING_CLASS: Record<
|
|
65
|
+
ControlSize,
|
|
66
|
+
string
|
|
67
|
+
> = {
|
|
68
|
+
sm: "ps-4",
|
|
69
|
+
md: "ps-4.5",
|
|
70
|
+
lg: "ps-7",
|
|
42
71
|
}
|
|
43
72
|
|
|
44
73
|
/**
|