@charcuterie/ui 4.18.2 → 4.19.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.
@@ -0,0 +1,51 @@
1
+ import type { CategoricalIndex } from "@charcuterie/tokens"
2
+ import type { ReactNode } from "react"
3
+ import { Card, type CardProps } from "../Card/Card.tsx"
4
+ import { CATEGORICAL_CONTENT_CLASS } from "../categoricalStyles.ts"
5
+ import { toClassName } from "../toClassName.ts"
6
+
7
+ export type MetricCardProps = Omit<CardProps, "actions"> & {
8
+ /** A stable category colour, independent of success/failure intent. */
9
+ categorical?: CategoricalIndex
10
+ /** App-supplied decoration; the visible heading names the metric. */
11
+ icon?: ReactNode
12
+ /** Already formatted by the app, including the unit when needed. */
13
+ value: ReactNode
14
+ }
15
+
16
+ /** One prominent measure, a coloured icon and edge, and app-owned supporting content. */
17
+ export const MetricCard = ({
18
+ categorical,
19
+ children,
20
+ icon,
21
+ value,
22
+ ...cardProps
23
+ }: MetricCardProps): ReactNode => (
24
+ <Card
25
+ {...cardProps}
26
+ accentEdge={
27
+ cardProps.accentEdge ??
28
+ (categorical ? { categorical } : undefined)
29
+ }
30
+ actions={
31
+ icon ? (
32
+ <span
33
+ aria-hidden="true"
34
+ className={toClassName(
35
+ "inline-flex size-7 items-center justify-center",
36
+ categorical
37
+ ? CATEGORICAL_CONTENT_CLASS[categorical]
38
+ : undefined,
39
+ )}
40
+ >
41
+ {icon}
42
+ </span>
43
+ ) : undefined
44
+ }
45
+ >
46
+ <p className="min-w-0 wrap-anywhere font-semibold text-2xl tabular-nums">
47
+ {value}
48
+ </p>
49
+ {children}
50
+ </Card>
51
+ )
@@ -95,7 +95,7 @@ export const SortableTableHeader = ({
95
95
  // uppercased and letterspaced, which strips the word shapes
96
96
  // readers use at small sizes. A column header is the label
97
97
  // for everything under it, so it is content.
98
- "flex w-full cursor-pointer items-center gap-1.5 bg-transparent px-3 py-2 text-start font-medium text-content-secondary text-sm uppercase tracking-wide transition-colors duration-(--duration-fast) ease-standard",
98
+ "flex w-full cursor-pointer items-center gap-1.5 bg-transparent px-3 py-2 text-start wrap-normal font-medium text-content-secondary text-sm uppercase tracking-wide transition-colors duration-(--duration-fast) ease-standard",
99
99
  "hover:text-content-primary",
100
100
  FOCUS_RING_CLASS,
101
101
  )}
package/src/index.ts CHANGED
@@ -158,6 +158,7 @@ export {
158
158
  export type {
159
159
  DataTableColumn,
160
160
  DataTableProps,
161
+ DataTableReflowAt,
161
162
  DataTableSelection,
162
163
  DataTableSort,
163
164
  } from "./DataTable/DataTable.tsx"
@@ -316,6 +317,8 @@ export type {
316
317
  MenuProps,
317
318
  } from "./Menu/Menu.tsx"
318
319
  export { Menu } from "./Menu/Menu.tsx"
320
+ export type { MetricCardProps } from "./MetricCard/MetricCard.tsx"
321
+ export { MetricCard } from "./MetricCard/MetricCard.tsx"
319
322
  export type { ModalProps } from "./Modal/Modal.tsx"
320
323
  export { Modal } from "./Modal/Modal.tsx"
321
324
  export type {
@@ -367,10 +367,14 @@ test.each([
367
367
  async (_case, name) => {
368
368
  const { canvas } = await mountStory(NestedLinkText)
369
369
 
370
- const link = expectAgentDrivable(canvas, {
371
- name,
372
- role: "link",
373
- })
370
+ // CodeMirror finishes its viewport decorations on a scheduled frame.
371
+ // Wait for the accessible anchor rather than assuming mount means paint.
372
+ const link = await waitFor(() =>
373
+ expectAgentDrivable(canvas, {
374
+ name,
375
+ role: "link",
376
+ }),
377
+ )
374
378
 
375
379
  await expect(link.tagName).toBe("A")
376
380
  },
@@ -621,7 +621,8 @@ test("the barrel is the only place components are re-exported", async () => {
621
621
  // ValuePairs owns compact label-over-value summaries.
622
622
  // Chart shares portable SVG geometry with Preact and image consumers.
623
623
  // TableViewport owns readable comparisons and full-window expansion.
624
- expect(componentNames.length).toBe(74)
624
+ // MetricCard shares prominent values and category-coloured icons through Card.
625
+ expect(componentNames.length).toBe(75)
625
626
 
626
627
  for (const name of componentNames) {
627
628
  expect(barrel).toContain(`export { ${name} }`)
package/src/styles.css CHANGED
@@ -260,3 +260,49 @@
260
260
  th:first-child {
261
261
  z-index: 3;
262
262
  }
263
+
264
+ /* Column width utilities describe comparison columns, never labelled list cells.
265
+ These rules are unlayered, like the component's existing structural styles,
266
+ so a consumer's cq-lg:w-32 cannot crush a list reflowing at cq-xl. */
267
+ .charcuterie-data-table[data-layout="stacked"]
268
+ > table
269
+ > :is(thead, tbody)
270
+ > tr
271
+ > :is(th, td) {
272
+ inline-size: auto;
273
+ min-inline-size: 0;
274
+ max-inline-size: none;
275
+ }
276
+ @container (width < 32rem) {
277
+ .charcuterie-data-table[data-reflow-at="md"]
278
+ > table
279
+ > :is(thead, tbody)
280
+ > tr
281
+ > :is(th, td) {
282
+ inline-size: auto;
283
+ min-inline-size: 0;
284
+ max-inline-size: none;
285
+ }
286
+ }
287
+ @container (width < 48rem) {
288
+ .charcuterie-data-table[data-reflow-at="lg"]
289
+ > table
290
+ > :is(thead, tbody)
291
+ > tr
292
+ > :is(th, td) {
293
+ inline-size: auto;
294
+ min-inline-size: 0;
295
+ max-inline-size: none;
296
+ }
297
+ }
298
+ @container (width < 64rem) {
299
+ .charcuterie-data-table[data-reflow-at="xl"]
300
+ > table
301
+ > :is(thead, tbody)
302
+ > tr
303
+ > :is(th, td) {
304
+ inline-size: auto;
305
+ min-inline-size: 0;
306
+ max-inline-size: none;
307
+ }
308
+ }