@charcuterie/ui 2.6.0 → 2.8.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 +13 -1
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts +94 -0
- package/dist/AdaptiveGrid/AdaptiveGrid.d.ts.map +1 -0
- package/dist/AdaptiveGrid/AdaptiveGrid.js +73 -0
- package/dist/AdaptiveGrid/AdaptiveGrid.js.map +1 -0
- package/dist/AdaptiveGrid/chooseColumns.d.ts +206 -0
- package/dist/AdaptiveGrid/chooseColumns.d.ts.map +1 -0
- package/dist/AdaptiveGrid/chooseColumns.js +156 -0
- package/dist/AdaptiveGrid/chooseColumns.js.map +1 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +155 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.js +153 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -0
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -6
- package/dist/Button/Button.js.map +1 -1
- package/dist/ButtonLink/ButtonLink.d.ts +59 -0
- package/dist/ButtonLink/ButtonLink.d.ts.map +1 -0
- package/dist/ButtonLink/ButtonLink.js +69 -0
- package/dist/ButtonLink/ButtonLink.js.map +1 -0
- package/dist/Combobox/Combobox.d.ts.map +1 -1
- package/dist/Combobox/Combobox.js +1 -1
- package/dist/Combobox/Combobox.js.map +1 -1
- package/dist/Dialog/Dialog.js +1 -1
- package/dist/Dialog/Dialog.js.map +1 -1
- package/dist/LogViewer/LogViewer.d.ts.map +1 -1
- package/dist/LogViewer/LogViewer.js +33 -1
- package/dist/LogViewer/LogViewer.js.map +1 -1
- package/dist/RouterLink/AnchorLink.d.ts +17 -0
- package/dist/RouterLink/AnchorLink.d.ts.map +1 -0
- package/dist/RouterLink/AnchorLink.js +16 -0
- package/dist/RouterLink/AnchorLink.js.map +1 -0
- package/dist/RouterLink/RouterLinkProvider.d.ts +13 -0
- package/dist/RouterLink/RouterLinkProvider.d.ts.map +1 -0
- package/dist/RouterLink/RouterLinkProvider.js +47 -0
- package/dist/RouterLink/RouterLinkProvider.js.map +1 -0
- package/dist/RouterLink/routerLink.d.ts +45 -0
- package/dist/RouterLink/routerLink.d.ts.map +1 -0
- package/dist/RouterLink/routerLink.js +31 -0
- package/dist/RouterLink/routerLink.js.map +1 -0
- package/dist/TextLink/TextLink.d.ts +69 -0
- package/dist/TextLink/TextLink.d.ts.map +1 -0
- package/dist/TextLink/TextLink.js +79 -0
- package/dist/TextLink/TextLink.js.map +1 -0
- package/dist/controlStyles.d.ts +36 -1
- package/dist/controlStyles.d.ts.map +1 -1
- package/dist/controlStyles.js +30 -0
- package/dist/controlStyles.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/intentStyles.d.ts +17 -0
- package/dist/intentStyles.d.ts.map +1 -1
- package/dist/intentStyles.js +17 -0
- package/dist/intentStyles.js.map +1 -1
- package/dist/reactRouter/ReactRouterLink.d.ts +23 -0
- package/dist/reactRouter/ReactRouterLink.d.ts.map +1 -0
- package/dist/reactRouter/ReactRouterLink.js +23 -0
- package/dist/reactRouter/ReactRouterLink.js.map +1 -0
- package/dist/reactRouter/index.d.ts +29 -0
- package/dist/reactRouter/index.d.ts.map +1 -0
- package/dist/reactRouter/index.js +29 -0
- package/dist/reactRouter/index.js.map +1 -0
- package/dist/styles.css +88 -8
- package/package.json +15 -3
- package/src/AdaptiveGrid/AdaptiveGrid.mdx +169 -0
- package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +240 -0
- package/src/AdaptiveGrid/AdaptiveGrid.test.tsx +243 -0
- package/src/AdaptiveGrid/AdaptiveGrid.tsx +150 -0
- package/src/AdaptiveGrid/chooseColumns.test.ts +253 -0
- package/src/AdaptiveGrid/chooseColumns.ts +270 -0
- package/src/AdaptiveGrid/useAdaptiveColumns.ts +355 -0
- package/src/Button/Button.tsx +10 -22
- package/src/ButtonLink/ButtonLink.mdx +75 -0
- package/src/ButtonLink/ButtonLink.stories.tsx +281 -0
- package/src/ButtonLink/ButtonLink.test.tsx +203 -0
- package/src/ButtonLink/ButtonLink.tsx +149 -0
- package/src/Combobox/Combobox.tsx +4 -1
- package/src/Dialog/Dialog.tsx +1 -1
- package/src/LogViewer/LogViewer.test.tsx +47 -0
- package/src/LogViewer/LogViewer.tsx +35 -1
- package/src/RouterLink/AnchorLink.tsx +23 -0
- package/src/RouterLink/RouterLink.mdx +178 -0
- package/src/RouterLink/RouterLinkProvider.tsx +68 -0
- package/src/RouterLink/routerLink.ts +56 -0
- package/src/TextLink/TextLink.mdx +93 -0
- package/src/TextLink/TextLink.stories.tsx +260 -0
- package/src/TextLink/TextLink.test.tsx +230 -0
- package/src/TextLink/TextLink.tsx +194 -0
- package/src/controlStyles.ts +69 -1
- package/src/icons.storyHelpers.tsx +32 -0
- package/src/index.ts +41 -0
- package/src/intentStyles.ts +19 -0
- package/src/reactRouter/ReactRouterLink.tsx +30 -0
- package/src/reactRouter/index.ts +29 -0
- package/src/sourceRules.test.ts +41 -6
- package/src/styles.css +88 -8
package/src/controlStyles.ts
CHANGED
|
@@ -16,7 +16,18 @@
|
|
|
16
16
|
* `packages/tokens/src/tailwindCollisions.test.ts`.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
ControlSize,
|
|
21
|
+
IntentName,
|
|
22
|
+
} from "@charcuterie/tokens"
|
|
23
|
+
|
|
24
|
+
import type { IntentAppearance } from "./intentStyles.ts"
|
|
25
|
+
import {
|
|
26
|
+
FOCUS_RING_CLASS,
|
|
27
|
+
INTENT_APPEARANCE_CLASS,
|
|
28
|
+
INTENT_HOVER_CLASS,
|
|
29
|
+
} from "./intentStyles.ts"
|
|
30
|
+
import { toClassName } from "./toClassName.ts"
|
|
20
31
|
|
|
21
32
|
export const CONTROL_SIZE_CLASS: Record<
|
|
22
33
|
ControlSize,
|
|
@@ -58,6 +69,63 @@ export const ICON_CONTROL_SIZE_CLASS: Record<
|
|
|
58
69
|
export const MIN_TOUCH_TARGET_CLASS =
|
|
59
70
|
"min-h-(--control-min-touch-target) min-w-(--control-min-touch-target)"
|
|
60
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Everything a pressable control looks like before an intent, a size,
|
|
74
|
+
* or a state is applied to it.
|
|
75
|
+
*
|
|
76
|
+
* Pulled out of `Button` the day `ButtonLink` shipped. Those two must
|
|
77
|
+
* paint **identically** — the whole point of `ButtonLink` is that a
|
|
78
|
+
* navigation can read as a primary button — and "identically" spelled
|
|
79
|
+
* as two copies of the same string is a promise that survives exactly
|
|
80
|
+
* one edit.
|
|
81
|
+
*/
|
|
82
|
+
export const CONTROL_BASE_CLASS =
|
|
83
|
+
"inline-flex cursor-pointer items-center justify-center rounded-md border font-medium whitespace-nowrap transition-colors duration-(--duration-fast) ease-standard select-none"
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The one class list a button-shaped control wears, assembled once.
|
|
87
|
+
*
|
|
88
|
+
* `Button` and `ButtonLink` call this with the same arguments and get
|
|
89
|
+
* the same string back, so a new appearance, a changed radius, or a
|
|
90
|
+
* different hover ramp reaches both or neither. `ButtonLink.test.tsx`
|
|
91
|
+
* asserts the resulting *computed styles* match, which is the version
|
|
92
|
+
* of this claim a class-name refactor cannot quietly break.
|
|
93
|
+
*
|
|
94
|
+
* The disabled treatment is the caller's, because it is the one thing
|
|
95
|
+
* that genuinely differs: a `<button>` has a real `disabled`
|
|
96
|
+
* attribute and gets `DISABLED_CLASS`, while an `<a>` has no such
|
|
97
|
+
* concept and gets `ARIA_DISABLED_CLASS` keyed off `aria-disabled`.
|
|
98
|
+
*/
|
|
99
|
+
export const getControlClassName = ({
|
|
100
|
+
appearance,
|
|
101
|
+
className,
|
|
102
|
+
disabledClass,
|
|
103
|
+
intent,
|
|
104
|
+
isFullWidth,
|
|
105
|
+
size,
|
|
106
|
+
sizing,
|
|
107
|
+
}: {
|
|
108
|
+
appearance: IntentAppearance
|
|
109
|
+
className?: string
|
|
110
|
+
disabledClass: string
|
|
111
|
+
intent: IntentName
|
|
112
|
+
isFullWidth: boolean
|
|
113
|
+
size: ControlSize
|
|
114
|
+
sizing: "control" | "icon"
|
|
115
|
+
}): string =>
|
|
116
|
+
toClassName(
|
|
117
|
+
CONTROL_BASE_CLASS,
|
|
118
|
+
sizing === "icon"
|
|
119
|
+
? ICON_CONTROL_SIZE_CLASS[size]
|
|
120
|
+
: CONTROL_SIZE_CLASS[size],
|
|
121
|
+
INTENT_APPEARANCE_CLASS[intent][appearance],
|
|
122
|
+
INTENT_HOVER_CLASS[intent][appearance],
|
|
123
|
+
FOCUS_RING_CLASS,
|
|
124
|
+
disabledClass,
|
|
125
|
+
isFullWidth && "w-full",
|
|
126
|
+
className,
|
|
127
|
+
)
|
|
128
|
+
|
|
61
129
|
/**
|
|
62
130
|
* Non-control sizing: badges, indicator dots, spinners. These are
|
|
63
131
|
* type-relative on purpose — a badge beside `text-sm` prose that
|
|
@@ -48,6 +48,38 @@ export const SettingsIcon = (): ReactNode => (
|
|
|
48
48
|
</svg>
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The back-arrow seven repos spell as a literal `←`, drawn instead —
|
|
53
|
+
* so the `TextLink` boards screenshot as an arrow rather than as the
|
|
54
|
+
* blank box that character measures in this Chromium.
|
|
55
|
+
*/
|
|
56
|
+
export const ArrowLeftIcon = (): ReactNode => (
|
|
57
|
+
<svg {...iconProps} aria-hidden="true">
|
|
58
|
+
<path d="M19 12H5" />
|
|
59
|
+
|
|
60
|
+
<path d="m11 18-6-6 6-6" />
|
|
61
|
+
</svg>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
export const ArrowRightIcon = (): ReactNode => (
|
|
65
|
+
<svg {...iconProps} aria-hidden="true">
|
|
66
|
+
<path d="M5 12h14" />
|
|
67
|
+
|
|
68
|
+
<path d="m13 6 6 6-6 6" />
|
|
69
|
+
</svg>
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
/** The `↗` an app puts after an external link, drawn. */
|
|
73
|
+
export const ExternalIcon = (): ReactNode => (
|
|
74
|
+
<svg {...iconProps} aria-hidden="true">
|
|
75
|
+
<path d="M14 5h5v5" />
|
|
76
|
+
|
|
77
|
+
<path d="M19 5 10 14" />
|
|
78
|
+
|
|
79
|
+
<path d="M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4" />
|
|
80
|
+
</svg>
|
|
81
|
+
)
|
|
82
|
+
|
|
51
83
|
export const UndoIcon = (): ReactNode => (
|
|
52
84
|
<svg {...iconProps} aria-hidden="true">
|
|
53
85
|
<path d="M9 14 4 9l5-5" />
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,27 @@ export type {
|
|
|
37
37
|
AccordionProps,
|
|
38
38
|
} from "./Accordion/Accordion.tsx"
|
|
39
39
|
export { Accordion } from "./Accordion/Accordion.tsx"
|
|
40
|
+
export type { AdaptiveGridProps } from "./AdaptiveGrid/AdaptiveGrid.tsx"
|
|
41
|
+
export { AdaptiveGrid } from "./AdaptiveGrid/AdaptiveGrid.tsx"
|
|
42
|
+
export type { ColumnChoice } from "./AdaptiveGrid/chooseColumns.ts"
|
|
43
|
+
export {
|
|
44
|
+
chooseColumns,
|
|
45
|
+
DEFAULT_MAX_AUTO_COLUMNS,
|
|
46
|
+
DEFAULT_MAX_MANUAL_COLUMNS,
|
|
47
|
+
DEFAULT_MIN_COLUMN_INLINE_SIZE_PX,
|
|
48
|
+
getColumnChoices,
|
|
49
|
+
getContentMaxInlineSize,
|
|
50
|
+
} from "./AdaptiveGrid/chooseColumns.ts"
|
|
51
|
+
export type {
|
|
52
|
+
BlockSizeResolver,
|
|
53
|
+
ColumnPersistence,
|
|
54
|
+
} from "./AdaptiveGrid/useAdaptiveColumns.ts"
|
|
55
|
+
export {
|
|
56
|
+
localStorageColumnPersistence,
|
|
57
|
+
readStoredChoice,
|
|
58
|
+
useAdaptiveColumns,
|
|
59
|
+
viewportBlockSizeResolver,
|
|
60
|
+
} from "./AdaptiveGrid/useAdaptiveColumns.ts"
|
|
40
61
|
export type {
|
|
41
62
|
AlertProps,
|
|
42
63
|
AlertSize,
|
|
@@ -46,6 +67,8 @@ export type { BadgeProps } from "./Badge/Badge.tsx"
|
|
|
46
67
|
export { Badge } from "./Badge/Badge.tsx"
|
|
47
68
|
export type { ButtonProps } from "./Button/Button.tsx"
|
|
48
69
|
export { Button } from "./Button/Button.tsx"
|
|
70
|
+
export type { ButtonLinkProps } from "./ButtonLink/ButtonLink.tsx"
|
|
71
|
+
export { ButtonLink } from "./ButtonLink/ButtonLink.tsx"
|
|
49
72
|
export type {
|
|
50
73
|
CardPadding,
|
|
51
74
|
CardProps,
|
|
@@ -66,8 +89,10 @@ export { Combobox } from "./Combobox/Combobox.tsx"
|
|
|
66
89
|
export type { BadgeSize } from "./controlStyles.ts"
|
|
67
90
|
export {
|
|
68
91
|
BADGE_SIZE_CLASS,
|
|
92
|
+
CONTROL_BASE_CLASS,
|
|
69
93
|
CONTROL_SIZE_CLASS,
|
|
70
94
|
DOT_SIZE_CLASS,
|
|
95
|
+
getControlClassName,
|
|
71
96
|
ICON_CONTROL_SIZE_CLASS,
|
|
72
97
|
MIN_TOUCH_TARGET_CLASS,
|
|
73
98
|
SPINNER_SIZE_CLASS,
|
|
@@ -92,6 +117,7 @@ export type { IconButtonProps } from "./IconButton/IconButton.tsx"
|
|
|
92
117
|
export { IconButton } from "./IconButton/IconButton.tsx"
|
|
93
118
|
export type { IntentAppearance } from "./intentStyles.ts"
|
|
94
119
|
export {
|
|
120
|
+
ARIA_DISABLED_CLASS,
|
|
95
121
|
DISABLED_CLASS,
|
|
96
122
|
FOCUS_RING_CLASS,
|
|
97
123
|
INTENT_APPEARANCE_CLASS,
|
|
@@ -148,6 +174,16 @@ export type {
|
|
|
148
174
|
RadioItem,
|
|
149
175
|
} from "./RadioGroup/RadioGroup.tsx"
|
|
150
176
|
export { RadioGroup } from "./RadioGroup/RadioGroup.tsx"
|
|
177
|
+
export { AnchorLink } from "./RouterLink/AnchorLink.tsx"
|
|
178
|
+
export {
|
|
179
|
+
RouterLinkProvider,
|
|
180
|
+
useRouterLink,
|
|
181
|
+
} from "./RouterLink/RouterLinkProvider.tsx"
|
|
182
|
+
export type {
|
|
183
|
+
RouterLinkComponent,
|
|
184
|
+
RouterLinkProps,
|
|
185
|
+
} from "./RouterLink/routerLink.ts"
|
|
186
|
+
export { getIsRoutedHref } from "./RouterLink/routerLink.ts"
|
|
151
187
|
export type {
|
|
152
188
|
SegmentedControlProps,
|
|
153
189
|
SegmentedItem,
|
|
@@ -196,6 +232,11 @@ export type {
|
|
|
196
232
|
TabsProps,
|
|
197
233
|
} from "./Tabs/Tabs.tsx"
|
|
198
234
|
export { Tabs } from "./Tabs/Tabs.tsx"
|
|
235
|
+
export type {
|
|
236
|
+
TextLinkAppearance,
|
|
237
|
+
TextLinkProps,
|
|
238
|
+
} from "./TextLink/TextLink.tsx"
|
|
239
|
+
export { TextLink } from "./TextLink/TextLink.tsx"
|
|
199
240
|
export type { ToastProps } from "./Toast/Toast.tsx"
|
|
200
241
|
export { Toast } from "./Toast/Toast.tsx"
|
|
201
242
|
export type {
|
package/src/intentStyles.ts
CHANGED
|
@@ -205,3 +205,22 @@ export const FOCUS_RING_CLASS =
|
|
|
205
205
|
*/
|
|
206
206
|
export const DISABLED_CLASS =
|
|
207
207
|
"disabled:cursor-not-allowed disabled:border-border-subtle disabled:bg-surface-sunken disabled:text-content-disabled disabled:shadow-none"
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The same treatment for something that cannot *be* disabled.
|
|
211
|
+
*
|
|
212
|
+
* **`disabled` is not a thing an `<a>` has.** There is no attribute,
|
|
213
|
+
* no `:disabled` match, and no browser behaviour to inherit — so a
|
|
214
|
+
* "disabled link" is entirely something a component has to build, and
|
|
215
|
+
* the usual build of it is a focusable `<a href>` that silently does
|
|
216
|
+
* nothing when pressed. `ButtonLink` instead drops `href` (the
|
|
217
|
+
* platform's own way to make an anchor inert and unfocusable) and
|
|
218
|
+
* says so with `aria-disabled`, which is what this paints against.
|
|
219
|
+
*
|
|
220
|
+
* `pointer-events-none` rather than `cursor-not-allowed`: without a
|
|
221
|
+
* real `:disabled`, the `hover:` classes still match — CSS `:hover`
|
|
222
|
+
* fires on disabled elements too — so the hover fill has to be taken
|
|
223
|
+
* off the table rather than merely overridden.
|
|
224
|
+
*/
|
|
225
|
+
export const ARIA_DISABLED_CLASS =
|
|
226
|
+
"aria-disabled:pointer-events-none aria-disabled:border-border-subtle aria-disabled:bg-surface-sunken aria-disabled:text-content-disabled aria-disabled:shadow-none"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { Link } from "react-router"
|
|
3
|
+
|
|
4
|
+
import type { RouterLinkProps } from "../RouterLink/routerLink.ts"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* react-router's `<Link>`, wearing the seam's `href`.
|
|
8
|
+
*
|
|
9
|
+
* The entire adapter, and that is the point: the seam's contract is
|
|
10
|
+
* "a component that takes `href`", so an adapter is a rename. wouter,
|
|
11
|
+
* TanStack Router, and a hand-rolled router each get the same four
|
|
12
|
+
* lines, written in the app rather than here.
|
|
13
|
+
*
|
|
14
|
+
* `to` versus `href` is the only mismatch worth naming. Everything
|
|
15
|
+
* else — `className`, `rel`, `target`, `aria-*`, the ref — passes
|
|
16
|
+
* through untouched, because react-router renders a real `<a>` and
|
|
17
|
+
* forwards what it does not understand.
|
|
18
|
+
*
|
|
19
|
+
* Imported from `react-router`, not `react-router-dom`. v7 and v8
|
|
20
|
+
* both export `Link` from the former, and `react-router-dom` v7 (what
|
|
21
|
+
* bambuddy has) is a re-export shim over exactly that module — so one
|
|
22
|
+
* import covers both installs, while naming `react-router-dom` would
|
|
23
|
+
* cover only one.
|
|
24
|
+
*/
|
|
25
|
+
export const ReactRouterLink = ({
|
|
26
|
+
href,
|
|
27
|
+
...linkProps
|
|
28
|
+
}: RouterLinkProps): ReactNode => (
|
|
29
|
+
<Link {...linkProps} to={href} />
|
|
30
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@charcuterie/ui/react-router` — the react-router adapter for the
|
|
3
|
+
* link seam.
|
|
4
|
+
*
|
|
5
|
+
* **A subpath export with an optional peer dependency**, and both
|
|
6
|
+
* halves of that matter. The base package must stay router-free: the
|
|
7
|
+
* fleet runs react-router v8, react-router-dom v7, wouter-preact, two
|
|
8
|
+
* hand-rolled routers, and five apps with no router at all, so a
|
|
9
|
+
* `react-router` in `@charcuterie/ui`'s dependencies would be a
|
|
10
|
+
* package eleven consumers install and six of them use. Optional
|
|
11
|
+
* peer + separate entry point means an app that never imports this
|
|
12
|
+
* path never resolves `react-router`, and `sourceRules.test.ts`
|
|
13
|
+
* asserts exactly that — the main entry is measured for what it
|
|
14
|
+
* reaches, so this cannot leak into it by accident.
|
|
15
|
+
*
|
|
16
|
+
* The wiring is one provider at the app root; `Guides/Routing` in
|
|
17
|
+
* Storybook is the copy-pasteable version.
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { RouterLinkProvider } from "@charcuterie/ui"
|
|
21
|
+
* import { ReactRouterLink } from "@charcuterie/ui/react-router"
|
|
22
|
+
*
|
|
23
|
+
* <RouterLinkProvider link={ReactRouterLink}>
|
|
24
|
+
* <RouterProvider router={router} />
|
|
25
|
+
* </RouterLinkProvider>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export { ReactRouterLink } from "./ReactRouterLink.tsx"
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -240,13 +240,22 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
240
240
|
// sibling *through* `index.ts` turns every component into a
|
|
241
241
|
// dependency of every other one, which is how a 3 KB `Spinner`
|
|
242
242
|
// starts pulling in `MediaTile`.
|
|
243
|
+
//
|
|
244
|
+
// Comments are stripped first, for the same reason the colour rule
|
|
245
|
+
// strips them: a docstring showing a *consumer* how to wire
|
|
246
|
+
// `RouterLinkProvider` has to spell `from "@charcuterie/ui"`, and a
|
|
247
|
+
// rule that cannot tell a citation from an import is a rule that
|
|
248
|
+
// gets switched off. An actual import is never inside a comment.
|
|
243
249
|
const offenders = componentFiles
|
|
244
250
|
.filter((one) => one.file !== "index.ts")
|
|
245
|
-
.filter(
|
|
246
|
-
(one)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
.filter((one) => {
|
|
252
|
+
const code = stripComments(one.contents)
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
/from "\.\.?\/index\.ts"/.test(code) ||
|
|
256
|
+
/from "@charcuterie\/ui"/.test(code)
|
|
257
|
+
)
|
|
258
|
+
})
|
|
250
259
|
.map((one) => one.file)
|
|
251
260
|
|
|
252
261
|
expect(offenders).toEqual([])
|
|
@@ -313,7 +322,27 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
313
322
|
// because both registrations are effects — and stays out of this
|
|
314
323
|
// count and the barrel by the `<Name>/<Name>.tsx` rule, exactly as
|
|
315
324
|
// `SegmentedOption` does.
|
|
316
|
-
|
|
325
|
+
//
|
|
326
|
+
// `AdaptiveGrid` is the library's first *layout* component, and the
|
|
327
|
+
// first one whose reason to exist is a rule rather than a widget:
|
|
328
|
+
// the wrapping grids in this fleet are `auto-fill, minmax()`, which
|
|
329
|
+
// takes every column the window allows and lands on seven items
|
|
330
|
+
// strung across an ultrawide. It spends height first instead, and
|
|
331
|
+
// it is a component rather than only a hook because three things
|
|
332
|
+
// about the markup are silent when they are wrong — the
|
|
333
|
+
// measured box must not be the capped box, `min-w-0` has to reach
|
|
334
|
+
// children the caller owns, and the track list must be an inline
|
|
335
|
+
// style because Tailwind cannot scan an interpolated class: +1 -> 36.
|
|
336
|
+
// The link family closes the gap that made seven repos hand-roll a
|
|
337
|
+
// back-link and made `Button` get used for navigation: `TextLink`
|
|
338
|
+
// and `ButtonLink`, both a real `<a href>`, differing in paint
|
|
339
|
+
// rather than semantics: +2 -> 38, on top of `AdaptiveGrid`'s 36.
|
|
340
|
+
// `AnchorLink`,
|
|
341
|
+
// `RouterLinkProvider`, and `ReactRouterLink` are the seam rather
|
|
342
|
+
// than components anybody stories, and live in `RouterLink/` and
|
|
343
|
+
// `reactRouter/` — outside this count by the `<Name>/<Name>.tsx`
|
|
344
|
+
// rule, exactly as `Overlay/`'s parts are.
|
|
345
|
+
expect(componentNames.length).toBe(38)
|
|
317
346
|
|
|
318
347
|
for (const name of componentNames) {
|
|
319
348
|
expect(barrel).toContain(`export { ${name} }`)
|
|
@@ -418,6 +447,12 @@ const ENTRY_POINT_RUNTIMES: Record<
|
|
|
418
447
|
"@tanstack/react-virtual",
|
|
419
448
|
"react",
|
|
420
449
|
],
|
|
450
|
+
// The router seam's one shipped adapter, and the reason it is a
|
|
451
|
+
// subpath rather than part of the barrel: `react-router` appears
|
|
452
|
+
// **only** here, as an optional peer, so the eleven consumers of
|
|
453
|
+
// the main entry never resolve it. Six of them do not have a
|
|
454
|
+
// react-router at all.
|
|
455
|
+
"./react-router": ["react", "react-router"],
|
|
421
456
|
"./testing": [],
|
|
422
457
|
"./tokens": ["@charcuterie/tokens"],
|
|
423
458
|
},
|
package/src/styles.css
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
/* `@charcuterie/ui/styles.css` — the four looping affordances
|
|
2
|
-
*
|
|
1
|
+
/* `@charcuterie/ui/styles.css` — the four looping affordances and
|
|
2
|
+
* the token-tinted scrollbar: the visuals a Tailwind utility cannot
|
|
3
|
+
* express against our tokens, and nothing else.
|
|
3
4
|
*
|
|
4
5
|
* Every other visual in this package is a Tailwind utility reading
|
|
5
6
|
* a token, which is what makes a scheme/density/variant flip a
|
|
6
|
-
* repaint with no re-render.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
7
|
+
* repaint with no re-render. Two things sit outside that reach.
|
|
8
|
+
* Animation is one: Tailwind's own `animate-spin` and `animate-pulse`
|
|
9
|
+
* hardcode 1s and 2s, ignore `prefers-reduced-motion`, and are
|
|
10
|
+
* therefore beyond the theme's reach — which is exactly why
|
|
11
|
+
* `MotionTokens` carries a separate `loopFast`/`loopSlow` ramp. The
|
|
12
|
+
* scrollbar is the other: the `::-webkit-scrollbar` pseudo-elements
|
|
13
|
+
* (and Firefox's `scrollbar-width`/`scrollbar-color`) have no
|
|
14
|
+
* `@theme`-generated utility, so `.charcuterie-scrollbar` at the foot
|
|
15
|
+
* of this file reads the same `--color-*` roles by hand and flips with
|
|
16
|
+
* `data-scheme` on the same repaint as everything else.
|
|
12
17
|
*
|
|
13
18
|
* Consumers import this once, after `@charcuterie/tokens/theme.css`.
|
|
14
19
|
*
|
|
@@ -128,3 +133,78 @@
|
|
|
128
133
|
);
|
|
129
134
|
}
|
|
130
135
|
}
|
|
136
|
+
|
|
137
|
+
/* `.charcuterie-scrollbar` — a token-tinted scrollbar for any element
|
|
138
|
+
* that scrolls. The fleet grew this twice by hand (gallery-downloader's
|
|
139
|
+
* `scrollbar-thin-token`, image-viewer's global `::-webkit-scrollbar`
|
|
140
|
+
* block); this is the one copy, promoted here so a scheme flip
|
|
141
|
+
* repaints it for free.
|
|
142
|
+
*
|
|
143
|
+
* The *designed* look is the `::-webkit-scrollbar` path: a 12px bar, a
|
|
144
|
+
* rounded thumb, a track-coloured inset, no step arrows. That is what
|
|
145
|
+
* Chromium, Edge, and Safari paint. Chromium 121+ will *prefer*
|
|
146
|
+
* `scrollbar-width` / `scrollbar-color` over these pseudo-elements
|
|
147
|
+
* whenever both are set, which collapses the bar to the thin OS chrome
|
|
148
|
+
* (arrows and all on Windows) — so the standard properties are scoped
|
|
149
|
+
* to Firefox only, where `::-webkit-scrollbar` never runs.
|
|
150
|
+
*
|
|
151
|
+
* Firefox gets the closest standard-property match (thin, token-tinted
|
|
152
|
+
* thumb, transparent track). It cannot round a corner or draw a track
|
|
153
|
+
* inset — a platform limit, not an omission here.
|
|
154
|
+
*
|
|
155
|
+
* `::-webkit-scrollbar-button` is suppressed deliberately: the
|
|
156
|
+
* affordance is the thumb on a track, not step arrows.
|
|
157
|
+
*
|
|
158
|
+
* Both engines read the same three roles — `border-strong` for the
|
|
159
|
+
* thumb, `surface-sunken` for the track, `content-muted` for the thumb
|
|
160
|
+
* on hover — so the bar is themed by `[data-scheme]` on `<html>` with
|
|
161
|
+
* nothing in React observing it. `width`/`height` rather than a
|
|
162
|
+
* logical property is not a lapse: the `::-webkit-scrollbar`
|
|
163
|
+
* pseudo-elements size only by the physical pair, and a vertical bar's
|
|
164
|
+
* thickness is its `width` regardless of writing direction. */
|
|
165
|
+
|
|
166
|
+
/* The designed bar — Chromium, Edge, Safari. */
|
|
167
|
+
.charcuterie-scrollbar::-webkit-scrollbar {
|
|
168
|
+
width: 12px;
|
|
169
|
+
height: 12px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.charcuterie-scrollbar::-webkit-scrollbar-button {
|
|
173
|
+
display: none;
|
|
174
|
+
width: 0;
|
|
175
|
+
height: 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.charcuterie-scrollbar::-webkit-scrollbar-track {
|
|
179
|
+
background-color: var(--color-surface-sunken);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.charcuterie-scrollbar::-webkit-scrollbar-thumb {
|
|
183
|
+
background-color: var(--color-border-strong);
|
|
184
|
+
border: 2px solid var(--color-surface-sunken);
|
|
185
|
+
border-radius: 6px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.charcuterie-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
189
|
+
background-color: var(--color-content-muted);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.charcuterie-scrollbar::-webkit-scrollbar-corner {
|
|
193
|
+
background-color: var(--color-surface-sunken);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Firefox only: it never paints `::-webkit-scrollbar`. `-moz-appearance`
|
|
197
|
+
* is the supports probe that Chromium and Safari reject, so this block
|
|
198
|
+
* does not reintroduce the standard properties on engines that would
|
|
199
|
+
* then drop the designed bar. */
|
|
200
|
+
@supports (-moz-appearance: none) {
|
|
201
|
+
.charcuterie-scrollbar {
|
|
202
|
+
scrollbar-width: thin;
|
|
203
|
+
scrollbar-color: color-mix(
|
|
204
|
+
in oklab,
|
|
205
|
+
var(--color-border-strong) 70%,
|
|
206
|
+
transparent
|
|
207
|
+
)
|
|
208
|
+
transparent;
|
|
209
|
+
}
|
|
210
|
+
}
|