@hackcove/ui-react 0.8.2 → 0.8.3
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 +2 -2
- package/dist/Card.d.ts +6 -0
- package/dist/Card.js +21 -9
- package/dist/CardParts.d.ts +6 -0
- package/dist/CardParts.js +5 -0
- package/dist/CodeBlock.d.ts +21 -6
- package/dist/CodeBlock.js +11 -8
- package/dist/CodeBody.d.ts +16 -0
- package/dist/CodeBody.js +15 -0
- package/dist/CodeExample.d.ts +13 -6
- package/dist/CodeExample.js +10 -8
- package/dist/CodeText.d.ts +6 -2
- package/dist/CodeText.js +43 -3
- package/dist/CopyButton.d.ts +1 -1
- package/dist/CopyButton.js +8 -2
- package/dist/Icon.d.ts +22 -10
- package/dist/Icon.js +16 -7
- package/dist/cx.d.ts +7 -0
- package/dist/cx.js +7 -0
- package/dist/icons.d.ts +16 -0
- package/dist/icons.js +48 -0
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ takes the size of the text around it. Upgrading: `MIGRATING.md`, shipped in the
|
|
|
68
68
|
|
|
69
69
|
## What is exported
|
|
70
70
|
|
|
71
|
-
- Primitives: `Card` (a bordered panel; `title` and `count` make a small heading, and `status` (a `Pill` or any node) sits right after them, where `actions` sits at the far end; `flush` drops the padding and clips the corners, for a table that runs edge to edge; `bar` moves the title, count and `actions` into a tinted strip over the body; `dashed` is an empty-state shell with centred muted text, and `dashed="filled"` the same on a surface fill with more padding; `badge` pins a `Pill` to the top-right corner over the content, on a panel or a link card, where it is part of the link's name; `attention` draws the strong warn edge (`border-amber-600`) round a card that needs a look, with or without `tone="warn"`, and does nothing on a `dashed` card; `footer` puts a tinted strip under a hairline across the bottom, for the card's own actions or a total: it takes the padding off the root and gives it to the body, so the strip runs edge to edge and stays at the foot of a card stretched in a `CardGrid`; not on a `dashed` card or a card that is a link; `as` is `section` (default), `article` or `div`; `id`, `data-*` and `aria-*` go on the root (on a link card too: the link inside takes only its own `aria-label`, `aria-labelledby` and `aria-current`); a titled panel is named by its title through `aria-labelledby`, unless you pass your own `aria-label` or `aria-labelledby`; a Card with an `id` is an anchor target, scrolled clear of the sticky header and outlined in violet while it is the `:target`), `EpicHeader`, `Pill`, `groupByEpic`, `Markdown`, `Badge` (a small red count for sitting on an icon; `count` above 99 reads `99+`, `corner` pins it to the top-right of its `relative` parent; an `IconButton` takes `badge` and does this itself).
|
|
71
|
+
- Primitives: `Card` (a bordered panel; `title` and `count` make a small heading, and `status` (a `Pill` or any node) sits right after them, where `actions` sits at the far end; `flush` drops the padding and clips the corners, for a table that runs edge to edge; `bar` moves the title, count and `actions` into a tinted strip over the body; `dashed` is an empty-state shell with centred muted text, and `dashed="filled"` the same on a surface fill with more padding; `badge` pins a `Pill` to the top-right corner over the content, on a panel or a link card, where it is part of the link's name; `icon` puts one of the `Icon` set's names above the title at `feature` size in the accent colour, decorative, on a panel or a link card but not a `bar`, `flush` or `dashed` one; `attention` draws the strong warn edge (`border-amber-600`) round a card that needs a look, with or without `tone="warn"`, and does nothing on a `dashed` card; `footer` puts a tinted strip under a hairline across the bottom, for the card's own actions or a total: it takes the padding off the root and gives it to the body, so the strip runs edge to edge and stays at the foot of a card stretched in a `CardGrid`; not on a `dashed` card or a card that is a link; `as` is `section` (default), `article` or `div`; `id`, `data-*` and `aria-*` go on the root (on a link card too: the link inside takes only its own `aria-label`, `aria-labelledby` and `aria-current`); a titled panel is named by its title through `aria-labelledby`, unless you pass your own `aria-label` or `aria-labelledby`; a Card with an `id` is an anchor target, scrolled clear of the sticky header and outlined in violet while it is the `:target`), `EpicHeader`, `Pill`, `groupByEpic`, `Markdown`, `Badge` (a small red count for sitting on an icon; `count` above 99 reads `99+`, `corner` pins it to the top-right of its `relative` parent; an `IconButton` takes `badge` and does this itself).
|
|
72
72
|
- Data: `StatTile` (one headline figure: an uppercase `label` over a large tabular `value`, already formatted, an optional `hint` under it; `tone` is `neutral` (default), `positive` or `negative`, and `accent` adds a bar across the top in the same tone), `ProgressBar` (a thin bar with a filled share of `max` (default 100); `label` names it for a screen reader, which also hears the percentage; `tone` is `accent` (default), `positive`, `warn` or `negative`), `SplitBar` (a bar split between `a` and `b` by their shares; `names` says whose is whose to a screen reader; `toneA` (default `negative`) and `toneB` (default `positive`)), `StackedBar` (any number of `segments`, each `{ label, value, tone }`, sized by its share of the sum; `legend` draws a `Legend` under it) and `Legend` (a swatch, a `label` and an optional `detail` per item, the key to a chart's or a row stripe's colours). The three bars take `size`: `sm` 6px, `md` 8px (default), `lg` 12px.
|
|
73
73
|
- Status pages: `NotFound` (404), `ErrorPage` (500 or any failure; `detail` is error text shown in a `CodeBlock`, never a stack trace, and `onRetry` adds "Try again"), `Offline` (`onRetry` too), `Maintenance` and `Forbidden` (403). Each is a whole page with default copy: `title` and `children` replace the heading and the body, `action` is the primary action, and `homeHref`, or `renderHome` for the app's own router `<Link>`, adds a "Back to home" button. With neither there is no home link.
|
|
74
74
|
- Layout (see Layout): `AppShell`, `AppShellBrand`, `appShellRootClassFor`, `Container`, `PageHeader`, `Stack`, `Section`, `Cluster`, `ClusterGrow`, and the page layouts `ListLayout`, `DetailLayout`, `FormLayout`, `ProseLayout`, `CardGrid`, `SidebarLayout`, `FullBleedLayout`, `CenteredLayout`.
|
|
@@ -80,7 +80,7 @@ takes the size of the text around it. Upgrading: `MIGRATING.md`, shipped in the
|
|
|
80
80
|
text colours as a `TableCell` tone, winning over `color`; pair the colour with a word, as in "Saved."),
|
|
81
81
|
`numeric` (tabular figures), `mono` (the monospace face) and `truncate` (one line, cut with an
|
|
82
82
|
ellipsis, the full text in a `title`, so its content is text).
|
|
83
|
-
- Controls: `Button` (`variant` is `primary` (default), `outline`, `ghost`, `link` or `dashed`, the link having no box: text with an underline on hover, for an in-line action, and the dashed one a dashed outline in muted text that fills its container, like `Card dashed`, for an "add another" row, with a leading icon as a child, ignoring `tone`; `size="sm"` is compact, `text-xs`, for table rows and toolbars, `size="lg"` is `text-base` with more padding; `tone="danger"` is red, for delete and clear, and `tone="positive"` green, for accept and approve; `disabledReason` disables it, greys it flat and makes the reason its `title`; `busy` disables it with `aria-busy` and keeps its colour, showing `busyLabel` in place of the children; `pressed` makes it a toggle with `aria-pressed`, on `outline` and `ghost` neutral when off and filled in the tone's primary colour when on, so an `outline` `size="sm"` Button with `pressed` is a filter chip; with `href`, or `render` to draw the app's own router `<Link>`, it is a link with the same look, `external` opening a new tab), `ButtonGroup` (a wrapping row of buttons; `end` is one item pushed to the far side, `label` makes it a named `role="group"`), `IconButton` (a square button holding one icon; `label` is required and becomes its `aria-label` and `title`; `variant` is `outline` (default) or `ghost`, `tone` is `default` or `danger`, `size` matches a `Button`'s height; `badge` puts a red count on its corner, not shown at 0; with `href` or `render` it is a link, `external` opening a new tab), `Icon` (one bare `<svg>` sized to fill a box: `size` `sm` 14px, `md` 16px (default) or `lg` 20px; `tone` is a text tone or `inherit` (default, the text colour around it); `aria-hidden` unless `label` names it as `role="img"`), `ColourSchemeToggle` (a light/dark switch on `<html>`; `id`, `data-*` and other button props go on the button, `store={false}` skips `localStorage`; see Light and dark), `Input` (a text input; `icon` adds a leading icon inside the field; `prefix` is short text inside the border before the value, a currency symbol, on a plain field only; `type="search"` drops the browser's search look and shows a "Clear search" button while the field has a value), `Textarea` (a multi-line text input, `rows` defaults to 3), `Select` (a native `<select>` in the control look; takes `<option>` children and keeps the browser's arrow). `Input`, `Select` and `Textarea` take `size` (`md` default, `sm`: `text-xs`, less padding, for dense forms and table rows, or `lg`: `text-base`, more padding) and `width` (`full` default, the fixed `xs` 5rem, `sm` 7rem, `md` 9rem, `lg` 12rem, `fill`: grows to the room left in a flex row and can shrink to nothing, so the buttons beside it stay on the line, or `auto`: no width class, as wide as the browser makes it); a fixed width replaces `w-full`, so there is no class to collide with. `Input` also takes `align` (`start` default, or `end` to right-align a numeric value). `size` replaces the native `size` attribute, so a multi-row `<select size={4}>` is not reachable through `Select`. `Checkbox` (a native checkbox with its label; other props go on the input; `size="sm"` is `text-xs` with a smaller box and `size="lg"` `text-base` with a larger one, `align="start"` puts the box on the first line of a label that wraps, where the default `center` centres it on all of them, and with no `label` it is a bare box for a table cell, named by `aria-label`; `description` is a muted line under the label, the box's `aria-describedby`). `RadioGroup` and `Radio` (one choice from a few options: the group has a `label`, `value`/`onChange` or `defaultValue`, `name`, `isDisabled`, `isRequired`, `size` (`sm`, `md` default, `lg`, as `Checkbox`) and an `error` shown like `Field`'s with `role="alert"`; each `Radio` has a `value`, a `label`, an optional `description` and `isDisabled`; one tab stop, the arrow keys move and choose). `Disclosure` (a summary button with a chevron and `aria-expanded` over a body that opens and closes; `summary`, `children`, `defaultOpen`; it keeps its own state and replaces `<details>`). `FileDrop` (a dashed drop zone, a `<label>` around a file input that covers it, so a click opens the dialog and a dropped file lands on the input; `children` is the prompt, `accept`, `multiple`, `name`, `disabled` and `onChange` on the input). `CopyButton` (a small text button that writes `text` to the clipboard and swaps `label` ("copy") for `copiedLabel` ("copied"); every other button prop, `disabled` included, goes on the `<button>`). `CopyLine` (one line of mono text in a box with a `CopyButton` beside it; `disabled` disables the button; other props go on the root `<div>`). `Field` (a label above a control, `htmlFor` names the control's id; a `hint` sits under it, and an `error` replaces the hint with a `role="alert"` line; `span="full"` runs it across both columns of a `FormGrid`). `FormGrid` (two columns of `Field`s from `md`, one below it), `FormRow` (an inline-edit row: `FormRowField`s side by side, an `action` after them, and one `hint` or `error` under the whole row; see Layout) and `FormRowField` (a compact label over one control, `span` 1 to 4). `Callout` (a tinted notice box; `tone` is `neutral`, `info` (default), `positive`, `warn` or `danger`; an optional `title` sits in bold above the body; `actions` puts a button or link at the end of the row; `banner` makes it a flat strip, bottom border only and no radius, for the top of a `Card flush`). `ListRow` (a full-width row that is a `<button>`, or a link with `href` or `render`, with a hover fill, a left-aligned label and `trailing` content; `divided` draws a hairline above the row, to separate rows in a `Card flush`; `expanded` makes it the header of a collapsible section: `aria-expanded` and a chevron, the caller owning the state and the section; `leading` is content before the label (an `Avatar`), and `leadingWidth="thumbnail"` gives it a fixed width so rows with and without a picture line up; `size` sets the padding (`sm` dense, `md` default, `lg`); `focused` draws the focus ring on the row the keyboard cursor is on, for a list moved with the arrow keys while focus stays on the list; `selected` marks the row in view with a violet fill and `aria-current="true"`; a link row (`href` or `render`) can hold its own link, button or control in `trailing`, which sits above the row's link and takes its own click; `static` makes it a plain `<div>`, for a row that holds its own buttons where the row would otherwise be a `<button>`, and adds `actions`, buttons after `trailing`, and `revealOnHover`, which shows them only on hover or focus inside the row and always on a touch screen; `active` marks the row the pointer or the keyboard is on, with the hover fill and the `revealOnHover` actions shown, for a list that follows both through the row's `onPointerEnter`, `onPointerLeave` and `onFocus`, which every row passes through). `Tabs` (underline tabs for switching a view in place: a row over a bottom rule, the current tab underlined in the accent; `items` are `{ value, label }` buttons, with `value` the current one's `value` and `onChange(value)` the caller's state, one tab stop and the arrow keys, Home and End moving between enabled tabs, or `{ label, href | render, active?, external? }` links in a `<nav>` with `aria-current="page"` on the current one (`active`, or the item whose `value` is `value`); `label` names the group; a button tab takes `disabled` and `controls`, the id of its `role="tabpanel"`, which is the caller's; `variant="pill"` fills the current tab, with no rule, for `PageHeader`'s `tabs` slot, an `AppShell` `nav` or a rail; only a pill row takes `size` and `orientation`, and `orientation="vertical"` stacks it in one column, the content of a `SidebarLayout` rail; `disabled` on the row disables every tab, e.g. until hydration). `SegmentedControl` (a joined pill group, one option filled; the same `items`, `value`, `onChange`, `disabled` and `label` as `Tabs`, as a `radiogroup` with `aria-checked` on each `radio`). On both an item takes `data-*` attributes (typed `data-${string}`) onto its button or link; on `Tabs` and `SegmentedControl` an item takes `count` (a small muted number after the label, part of its name: "Shots 12") and `title`; a `disabled` button option stays in the page as `aria-disabled`, so its `title` is still the hover tooltip and is announced as the reason, it is out of the tab order and the arrow keys, Home and End skip it. `Dialog` (a modal on the browser's `<dialog>`, opened on mount, so render it only while open: `title` is its name, `description` a line under it, `footer` the right-aligned actions; Esc, the backdrop or a `<form method="dialog">` call `onClose`; `size` is `sm` (320px, a list), `md` (440px, default, a form) or `lg` (640px, a wider form or a table)). `FieldDialog` (a `Dialog` with one text field and Cancel and submit buttons, every string passed in: `title`, `description`, `label`, `inputLabel`, `placeholder`, `hint`, `emptyReason`, `submit` and `busyLabel`; `onSubmit` gets the trimmed value and a resolved promise closes it, a rejection shows its message). `Drawer` (a modal slide-over on the right edge, on a native `<dialog>` opened on mount like `Dialog`, so render it only while open: `title` is its name, a close button sits beside it, `children` scroll between the header and an optional pinned `footer`; Esc, the scrim and the close button call `onClose`; `size` is `sm`, `md` (default) or `lg`; `closeLabel` names the close button). `Breadcrumbs` (a `<nav aria-label="Breadcrumb">` over an ordered list; `crumbs` are nodes, or `{ label, href | render, external? }` for one that links; the last is the current page and is never a link and gets `aria-current="page"`, and each crumb truncates). `Table` (a list of records in a rounded, bordered card around a full-width `<table>` that scrolls sideways on a narrow screen; `caption` names it for assistive tech and is not drawn; compose it from `TableHead`, `TableBody`, `TableFoot`, `TableRow`, `TableHeaderCell` (`align` `start` or `end`; `srLabel` is a visually hidden column name, in place of children, for a column of row actions: "Actions") and `TableCell` (`align`, `tone` `default`, `strong` or `muted`, or the Callout tones `neutral`, `info`, `positive`, `warn` or `danger` for an amount that means something, in the tone's text colour and with no fill, so pair the colour with a sign or a word; `numeric` for tabular figures, `mono` for a small mono face, `truncate` to cap a long value's width, cut it with an ellipsis and put the full text in a `title`, so its content is text (strings and numbers); `secondary` for a second, muted line under the content; `colSpan` passes through, so a `TableCell` with `colSpan` in a row of its own is a full-width panel, an empty state or a note); `TableFoot` (the `<tfoot>` for totals and subtotals: its cells are strong by default and each row has a firmer line above it, in a card and in a `plain` table alike); `TableGroupRow` (a full-width group header in the body: `label` spans `colSpan` columns, the table's column count, which is required; optional `trailing` content sits at the far end, and the group's rows follow it); `tone` on a `TableRow` in the body marks the row with a stripe on its leading edge over a soft fill: `neutral`, `info`, `positive`, `warn` or `danger`, as `Callout`, or `muted`, which only fades the row (a transfer, a row that is not the point); a bold subtotal is not a tone; `stripe` (a text tone) draws the leading stripe alone, with no fill, and is never combined with `tone`; `variant="plain"` on `Table` drops the card border, fill and shadow, the head strip and the row hover and keeps the row dividers, for a table inside a card of its own; every table element passes `id`, `data-*` and `aria-*` through, and none takes a `className` or `style`). `Timeline` (a named `<ol>` of events; `label` is its `aria-label`, `empty` is drawn instead when there are no items) with `TimelineItem` (a `title`, an already-formatted `time`, the `children` as a note, and `tone`, a Callout tone for the marker dot, `neutral` by default). `KeyValueList` (a `<dl>` of label/value rows; each `KeyValue` takes a `label` and its value as children, the value right-aligned in tabular figures; `align="start"` puts the values left-aligned in one column beside the labels instead, using CSS subgrid: in Chrome before 117 a row stacks its label over its value). `Avatar` (a round badge of the initials of `name`, titled with the name and `aria-hidden`; with `src` (the app's own local image path, never a remote one) it is a round, cropped `<img alt={name}>` instead; `size` is `sm`, `md` (default), `lg`, `xl` (56px) or `2xl` (96px); `ring` adds a page-colour border and a negative margin for an overlapping stack; the colour is one of seven stock background classes picked by a hash of the name, remapped by each theme, and `avatarColourClass(name)` returns it to colour something else the same way; `initialsOf(name)` is the two letters). `TextLink` (an inline text link in the accent colour; `href` is required unless `render` draws the app's own router `<Link>`, `external` adds `target="_blank"` and `rel="noreferrer"`, `stretch` covers the nearest `relative` ancestor so a whole row or card is the click target, `size` (`sm`, `md` or `lg`) pins the type size where it would otherwise follow the text around it, `mono` sets the monospace face, `tone` (`warn`, `danger`, `positive`, `info` or `muted`) gives it a meaning's colour in place of the accent, every other anchor prop passes through). `MetaList` (a wrapping row of facts or links in muted small text, joined by an `aria-hidden` `separator`: `·` (default), `•` or `none`; branch · SHA · date, or a footer's Privacy · Terms; an item is text, a `Code` or a `TextLink`; `nowrap` keeps each item on one line, for links; `mono` sets the row in the monospace face, for ids and paths). `PageFade` (the page's content area, named for the View Transitions API so a route change cross-fades it while the frame around it stays put; wrap what changes between pages, once per page, or give each a `name` (a CSS identifier) when there are two; it fades when the navigation runs in a view transition, through `startPageTransition(update)` (flushes a synchronous state change inside `document.startViewTransition`) or a router's own option such as TanStack Router `viewTransition`, and otherwise, in a browser without View Transitions, or under `prefers-reduced-motion: reduce`, it swaps with no fade; it renders a small `<style>`, with no colour in it, and the div is a stacking context, so portal an overlay that must rise above the frame). `EmptyState` (what a page or list shows when it has nothing yet: a centred `title`, the `children` as the explanation under it, and `actions` in a centred row; a named `<section>`; `level` is the title's heading level, 2 (default) to 6, and does not change its look; for one muted line inside a card, `Text as="p" color="muted"`). `Steps` (a numbered `<ol>` of `Step`s, each a `title` and the `children` as a detail line, the numbers counted by CSS in a violet disc; for a "get started" list, not for progress). `Divider` (a hairline `<hr>` in the theme's line colour; spacing comes from the layout around it). `BulletList` (a `<ul>` with discs, an indent and a small gap; children are `<li>`s). `Code` (inline code: mono, on a subtle background). `CodeBlock` (a static multi-line block of code, no client JS: the text is `children` (a string) or `lines` (one entry per line), `language` (`bash`, `css`, `tsx`, `ts` or `json`) colours it and `text`, the default, leaves it in one colour; `label` is a caption naming the language or file, long lines wrap; other props go on the root `<figure>`). `CodeExample` (one framed example: the rendered `children` over the `code` that makes them, coloured as TSX, in one bordered box, with an optional `caption` as its last band; no client JS). `Video` (a full-width, rounded `<video>` with controls that loads metadata only; `src` is required, `poster` and a WebVTT `captions` track are optional, children are the fallback). `Kbd` (a keyboard key in mono; its border takes the surrounding text colour, so it reads on a filled button too). `FilterGroup` (a `<fieldset>` with a small uppercase `legend` over a wrapped row of its children, e.g. `outline` `sm` `Button`s with `pressed`). `DateField` (see Dates).
|
|
83
|
+
- Controls: `Button` (`variant` is `primary` (default), `outline`, `ghost`, `link` or `dashed`, the link having no box: text with an underline on hover, for an in-line action, and the dashed one a dashed outline in muted text that fills its container, like `Card dashed`, for an "add another" row, with a leading icon as a child, ignoring `tone`; `size="sm"` is compact, `text-xs`, for table rows and toolbars, `size="lg"` is `text-base` with more padding; `tone="danger"` is red, for delete and clear, and `tone="positive"` green, for accept and approve; `disabledReason` disables it, greys it flat and makes the reason its `title`; `busy` disables it with `aria-busy` and keeps its colour, showing `busyLabel` in place of the children; `pressed` makes it a toggle with `aria-pressed`, on `outline` and `ghost` neutral when off and filled in the tone's primary colour when on, so an `outline` `size="sm"` Button with `pressed` is a filter chip; with `href`, or `render` to draw the app's own router `<Link>`, it is a link with the same look, `external` opening a new tab), `ButtonGroup` (a wrapping row of buttons; `end` is one item pushed to the far side, `label` makes it a named `role="group"`), `IconButton` (a square button holding one icon; `label` is required and becomes its `aria-label` and `title`; `variant` is `outline` (default) or `ghost`, `tone` is `default` or `danger`, `size` matches a `Button`'s height; `badge` puts a red count on its corner, not shown at 0; with `href` or `render` it is a link, `external` opening a new tab), `Icon` (one bare `<svg>` sized to fill a box, or `name` for one of the set's own icons (`diff`, `baseline`, `review`, `ci`, `self-host`, `privacy`, `capture`, `branch`, `theme`, `speed`: one 24 grid, a 1.5 stroke, round caps and joins, each the idea and not a product): `size` `sm` 14px, `md` 16px (default), `lg` 20px or `feature` 32px; `tone` is a text tone or `inherit` (default, the text colour around it); `aria-hidden` unless `label` names it as `role="img"`), `ColourSchemeToggle` (a light/dark switch on `<html>`; `id`, `data-*` and other button props go on the button, `store={false}` skips `localStorage`; see Light and dark), `Input` (a text input; `icon` adds a leading icon inside the field; `prefix` is short text inside the border before the value, a currency symbol, on a plain field only; `type="search"` drops the browser's search look and shows a "Clear search" button while the field has a value), `Textarea` (a multi-line text input, `rows` defaults to 3), `Select` (a native `<select>` in the control look; takes `<option>` children and keeps the browser's arrow). `Input`, `Select` and `Textarea` take `size` (`md` default, `sm`: `text-xs`, less padding, for dense forms and table rows, or `lg`: `text-base`, more padding) and `width` (`full` default, the fixed `xs` 5rem, `sm` 7rem, `md` 9rem, `lg` 12rem, `fill`: grows to the room left in a flex row and can shrink to nothing, so the buttons beside it stay on the line, or `auto`: no width class, as wide as the browser makes it); a fixed width replaces `w-full`, so there is no class to collide with. `Input` also takes `align` (`start` default, or `end` to right-align a numeric value). `size` replaces the native `size` attribute, so a multi-row `<select size={4}>` is not reachable through `Select`. `Checkbox` (a native checkbox with its label; other props go on the input; `size="sm"` is `text-xs` with a smaller box and `size="lg"` `text-base` with a larger one, `align="start"` puts the box on the first line of a label that wraps, where the default `center` centres it on all of them, and with no `label` it is a bare box for a table cell, named by `aria-label`; `description` is a muted line under the label, the box's `aria-describedby`). `RadioGroup` and `Radio` (one choice from a few options: the group has a `label`, `value`/`onChange` or `defaultValue`, `name`, `isDisabled`, `isRequired`, `size` (`sm`, `md` default, `lg`, as `Checkbox`) and an `error` shown like `Field`'s with `role="alert"`; each `Radio` has a `value`, a `label`, an optional `description` and `isDisabled`; one tab stop, the arrow keys move and choose). `Disclosure` (a summary button with a chevron and `aria-expanded` over a body that opens and closes; `summary`, `children`, `defaultOpen`; it keeps its own state and replaces `<details>`). `FileDrop` (a dashed drop zone, a `<label>` around a file input that covers it, so a click opens the dialog and a dropped file lands on the input; `children` is the prompt, `accept`, `multiple`, `name`, `disabled` and `onChange` on the input). `CopyButton` (a small text button that writes `text` to the clipboard and swaps `label` ("copy") for `copiedLabel` ("copied"); every other button prop, `disabled` included, goes on the `<button>`). `CopyLine` (one line of mono text in a box with a `CopyButton` beside it; `disabled` disables the button; other props go on the root `<div>`). `Field` (a label above a control, `htmlFor` names the control's id; a `hint` sits under it, and an `error` replaces the hint with a `role="alert"` line; `span="full"` runs it across both columns of a `FormGrid`). `FormGrid` (two columns of `Field`s from `md`, one below it), `FormRow` (an inline-edit row: `FormRowField`s side by side, an `action` after them, and one `hint` or `error` under the whole row; see Layout) and `FormRowField` (a compact label over one control, `span` 1 to 4). `Callout` (a tinted notice box; `tone` is `neutral`, `info` (default), `positive`, `warn` or `danger`; an optional `title` sits in bold above the body; `actions` puts a button or link at the end of the row; `banner` makes it a flat strip, bottom border only and no radius, for the top of a `Card flush`). `ListRow` (a full-width row that is a `<button>`, or a link with `href` or `render`, with a hover fill, a left-aligned label and `trailing` content; `divided` draws a hairline above the row, to separate rows in a `Card flush`; `expanded` makes it the header of a collapsible section: `aria-expanded` and a chevron, the caller owning the state and the section; `leading` is content before the label (an `Avatar`), and `leadingWidth="thumbnail"` gives it a fixed width so rows with and without a picture line up; `size` sets the padding (`sm` dense, `md` default, `lg`); `focused` draws the focus ring on the row the keyboard cursor is on, for a list moved with the arrow keys while focus stays on the list; `selected` marks the row in view with a violet fill and `aria-current="true"`; a link row (`href` or `render`) can hold its own link, button or control in `trailing`, which sits above the row's link and takes its own click; `static` makes it a plain `<div>`, for a row that holds its own buttons where the row would otherwise be a `<button>`, and adds `actions`, buttons after `trailing`, and `revealOnHover`, which shows them only on hover or focus inside the row and always on a touch screen; `active` marks the row the pointer or the keyboard is on, with the hover fill and the `revealOnHover` actions shown, for a list that follows both through the row's `onPointerEnter`, `onPointerLeave` and `onFocus`, which every row passes through). `Tabs` (underline tabs for switching a view in place: a row over a bottom rule, the current tab underlined in the accent; `items` are `{ value, label }` buttons, with `value` the current one's `value` and `onChange(value)` the caller's state, one tab stop and the arrow keys, Home and End moving between enabled tabs, or `{ label, href | render, active?, external? }` links in a `<nav>` with `aria-current="page"` on the current one (`active`, or the item whose `value` is `value`); `label` names the group; a button tab takes `disabled` and `controls`, the id of its `role="tabpanel"`, which is the caller's; `variant="pill"` fills the current tab, with no rule, for `PageHeader`'s `tabs` slot, an `AppShell` `nav` or a rail; only a pill row takes `size` and `orientation`, and `orientation="vertical"` stacks it in one column, the content of a `SidebarLayout` rail; `disabled` on the row disables every tab, e.g. until hydration). `SegmentedControl` (a joined pill group, one option filled; the same `items`, `value`, `onChange`, `disabled` and `label` as `Tabs`, as a `radiogroup` with `aria-checked` on each `radio`). On both an item takes `data-*` attributes (typed `data-${string}`) onto its button or link; on `Tabs` and `SegmentedControl` an item takes `count` (a small muted number after the label, part of its name: "Shots 12") and `title`; a `disabled` button option stays in the page as `aria-disabled`, so its `title` is still the hover tooltip and is announced as the reason, it is out of the tab order and the arrow keys, Home and End skip it. `Dialog` (a modal on the browser's `<dialog>`, opened on mount, so render it only while open: `title` is its name, `description` a line under it, `footer` the right-aligned actions; Esc, the backdrop or a `<form method="dialog">` call `onClose`; `size` is `sm` (320px, a list), `md` (440px, default, a form) or `lg` (640px, a wider form or a table)). `FieldDialog` (a `Dialog` with one text field and Cancel and submit buttons, every string passed in: `title`, `description`, `label`, `inputLabel`, `placeholder`, `hint`, `emptyReason`, `submit` and `busyLabel`; `onSubmit` gets the trimmed value and a resolved promise closes it, a rejection shows its message). `Drawer` (a modal slide-over on the right edge, on a native `<dialog>` opened on mount like `Dialog`, so render it only while open: `title` is its name, a close button sits beside it, `children` scroll between the header and an optional pinned `footer`; Esc, the scrim and the close button call `onClose`; `size` is `sm`, `md` (default) or `lg`; `closeLabel` names the close button). `Breadcrumbs` (a `<nav aria-label="Breadcrumb">` over an ordered list; `crumbs` are nodes, or `{ label, href | render, external? }` for one that links; the last is the current page and is never a link and gets `aria-current="page"`, and each crumb truncates). `Table` (a list of records in a rounded, bordered card around a full-width `<table>` that scrolls sideways on a narrow screen; `caption` names it for assistive tech and is not drawn; compose it from `TableHead`, `TableBody`, `TableFoot`, `TableRow`, `TableHeaderCell` (`align` `start` or `end`; `srLabel` is a visually hidden column name, in place of children, for a column of row actions: "Actions") and `TableCell` (`align`, `tone` `default`, `strong` or `muted`, or the Callout tones `neutral`, `info`, `positive`, `warn` or `danger` for an amount that means something, in the tone's text colour and with no fill, so pair the colour with a sign or a word; `numeric` for tabular figures, `mono` for a small mono face, `truncate` to cap a long value's width, cut it with an ellipsis and put the full text in a `title`, so its content is text (strings and numbers); `secondary` for a second, muted line under the content; `colSpan` passes through, so a `TableCell` with `colSpan` in a row of its own is a full-width panel, an empty state or a note); `TableFoot` (the `<tfoot>` for totals and subtotals: its cells are strong by default and each row has a firmer line above it, in a card and in a `plain` table alike); `TableGroupRow` (a full-width group header in the body: `label` spans `colSpan` columns, the table's column count, which is required; optional `trailing` content sits at the far end, and the group's rows follow it); `tone` on a `TableRow` in the body marks the row with a stripe on its leading edge over a soft fill: `neutral`, `info`, `positive`, `warn` or `danger`, as `Callout`, or `muted`, which only fades the row (a transfer, a row that is not the point); a bold subtotal is not a tone; `stripe` (a text tone) draws the leading stripe alone, with no fill, and is never combined with `tone`; `variant="plain"` on `Table` drops the card border, fill and shadow, the head strip and the row hover and keeps the row dividers, for a table inside a card of its own; every table element passes `id`, `data-*` and `aria-*` through, and none takes a `className` or `style`). `Timeline` (a named `<ol>` of events; `label` is its `aria-label`, `empty` is drawn instead when there are no items) with `TimelineItem` (a `title`, an already-formatted `time`, the `children` as a note, and `tone`, a Callout tone for the marker dot, `neutral` by default). `KeyValueList` (a `<dl>` of label/value rows; each `KeyValue` takes a `label` and its value as children, the value right-aligned in tabular figures; `align="start"` puts the values left-aligned in one column beside the labels instead, using CSS subgrid: in Chrome before 117 a row stacks its label over its value). `Avatar` (a round badge of the initials of `name`, titled with the name and `aria-hidden`; with `src` (the app's own local image path, never a remote one) it is a round, cropped `<img alt={name}>` instead; `size` is `sm`, `md` (default), `lg`, `xl` (56px) or `2xl` (96px); `ring` adds a page-colour border and a negative margin for an overlapping stack; the colour is one of seven stock background classes picked by a hash of the name, remapped by each theme, and `avatarColourClass(name)` returns it to colour something else the same way; `initialsOf(name)` is the two letters). `TextLink` (an inline text link in the accent colour; `href` is required unless `render` draws the app's own router `<Link>`, `external` adds `target="_blank"` and `rel="noreferrer"`, `stretch` covers the nearest `relative` ancestor so a whole row or card is the click target, `size` (`sm`, `md` or `lg`) pins the type size where it would otherwise follow the text around it, `mono` sets the monospace face, `tone` (`warn`, `danger`, `positive`, `info` or `muted`) gives it a meaning's colour in place of the accent, every other anchor prop passes through). `MetaList` (a wrapping row of facts or links in muted small text, joined by an `aria-hidden` `separator`: `·` (default), `•` or `none`; branch · SHA · date, or a footer's Privacy · Terms; an item is text, a `Code` or a `TextLink`; `nowrap` keeps each item on one line, for links; `mono` sets the row in the monospace face, for ids and paths). `PageFade` (the page's content area, named for the View Transitions API so a route change cross-fades it while the frame around it stays put; wrap what changes between pages, once per page, or give each a `name` (a CSS identifier) when there are two; it fades when the navigation runs in a view transition, through `startPageTransition(update)` (flushes a synchronous state change inside `document.startViewTransition`) or a router's own option such as TanStack Router `viewTransition`, and otherwise, in a browser without View Transitions, or under `prefers-reduced-motion: reduce`, it swaps with no fade; it renders a small `<style>`, with no colour in it, and the div is a stacking context, so portal an overlay that must rise above the frame). `EmptyState` (what a page or list shows when it has nothing yet: a centred `title`, the `children` as the explanation under it, and `actions` in a centred row; a named `<section>`; `level` is the title's heading level, 2 (default) to 6, and does not change its look; for one muted line inside a card, `Text as="p" color="muted"`). `Steps` (a numbered `<ol>` of `Step`s, each a `title` and the `children` as a detail line, the numbers counted by CSS in a violet disc; for a "get started" list, not for progress). `Divider` (a hairline `<hr>` in the theme's line colour; spacing comes from the layout around it). `BulletList` (a `<ul>` with discs, an indent and a small gap; children are `<li>`s). `Code` (inline code: mono, on a subtle background). `CodeBlock` (a block of code, no client JS: the text is `children` (a string) or `lines` (one entry per line), `language` (`bash`, `css`, `tsx`, `ts` or `json`) colours it and `text`, the default, leaves it in one colour; `label` is a caption naming the language or file; long lines wrap by default, so nothing is cut off and a command copies as one line, and `overflow="scroll"` keeps lines whole and scrolls them under a thin scrollbar in the theme's muted ink; `copy` adds a copy button beside the code, which copies it exactly as given and needs the page hydrated; other props go on the root `<figure>`). `CodeExample` (one framed example: the rendered `children` over the `code` that makes them, coloured as TSX, in one bordered box, with an optional `caption` as its last band; a wide preview or long code scrolls inside the frame under the same thin scrollbar; `copy` adds a button that copies the code; no client JS without `copy`). `Video` (a full-width, rounded `<video>` with controls that loads metadata only; `src` is required, `poster` and a WebVTT `captions` track are optional, children are the fallback). `Kbd` (a keyboard key in mono; its border takes the surrounding text colour, so it reads on a filled button too). `FilterGroup` (a `<fieldset>` with a small uppercase `legend` over a wrapped row of its children, e.g. `outline` `sm` `Button`s with `pressed`). `DateField` (see Dates).
|
|
84
84
|
- Dates: `toISODate`, `fromISODate`, and the `CalendarDate` type.
|
|
85
85
|
|
|
86
86
|
## Dates
|
package/dist/Card.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import { type CalloutTone } from "./Callout.js";
|
|
3
|
+
import type { IconName } from "./icons.js";
|
|
3
4
|
import { type LinkRender } from "./link.js";
|
|
4
5
|
import type { Unstyled } from "./props.js";
|
|
5
6
|
import type { Size } from "./size.js";
|
|
@@ -18,6 +19,8 @@ type CardBase = {
|
|
|
18
19
|
dashed?: boolean | "filled";
|
|
19
20
|
/** A soft fill and border in the Callout's tones, for a data panel with a status. Ignored with `bar` and `dashed`. */
|
|
20
21
|
tone?: CalloutTone;
|
|
22
|
+
/** One of the library's icons, drawn at feature size in the accent colour above the title: the mark of a feature card. Ignored with `bar`, `flush` and `dashed`. */
|
|
23
|
+
icon?: IconName;
|
|
21
24
|
/** A `Pill` (or any small node) pinned in the top-right corner, over the content and inside the radius, with no change to the layout. On a card that is a link it is part of the link's name, and a link or button in it is clickable on its own. */
|
|
22
25
|
badge?: ReactNode;
|
|
23
26
|
/** The warn tone's strong border in place of the card's own, for the one card in a set that needs a look (a run that differed). It keeps the card's fill. Ignored with `dashed`. */
|
|
@@ -78,6 +81,9 @@ type CardLinkProps = CardBase & {
|
|
|
78
81
|
* scrolls it clear of the sticky header (and a `subnav`) and outlines it in the accent colour while
|
|
79
82
|
* it is the target.
|
|
80
83
|
*
|
|
84
|
+
* `icon` is one of the library's icons at feature size in the accent colour, above the title: the
|
|
85
|
+
* mark of a feature card. It sits in the padded body, so `bar`, `flush` and `dashed` have none.
|
|
86
|
+
*
|
|
81
87
|
* `footer` is a tinted strip across the bottom, under a hairline, for the card's own actions or a
|
|
82
88
|
* total; in a `CardGrid` it stays at the foot of a card that is stretched to the row's height. It
|
|
83
89
|
* suits a panel, not a card that is a link, and `dashed` has none.
|
package/dist/Card.js
CHANGED
|
@@ -2,24 +2,33 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useId } from "react";
|
|
3
3
|
import { TONE_SURFACE } from "./Callout.js";
|
|
4
4
|
import { CardBadge } from "./CardBadge.js";
|
|
5
|
-
import { CardBar, CardFooter, CardHeader, CardHeading } from "./CardParts.js";
|
|
5
|
+
import { CardBar, CardFooter, CardHeader, CardHeading, CardIcon } from "./CardParts.js";
|
|
6
6
|
import { cx, focusRing, liftInteractive } from "./cx.js";
|
|
7
7
|
import { renderLink } from "./link.js";
|
|
8
8
|
/** A card with a `footer`: a `dashed` card stands alone and has none. */
|
|
9
9
|
function withFooter(form, footer) {
|
|
10
10
|
return { ...form, footer: !form.dashed && Boolean(footer) };
|
|
11
11
|
}
|
|
12
|
+
/** A mark needs the padded body above a heading, so it has no place in a strip, a flush card or an empty state. */
|
|
13
|
+
function paddedIcon(asked, bare) {
|
|
14
|
+
return bare || asked.flush ? undefined : asked.icon;
|
|
15
|
+
}
|
|
16
|
+
/** A flag that is on only when asked for and not overridden. */
|
|
17
|
+
function on(flag, off) {
|
|
18
|
+
return (flag ?? false) && !off;
|
|
19
|
+
}
|
|
12
20
|
/** `dashed` stands alone; `bar` needs a title or actions to fill it and has no tone. */
|
|
13
21
|
function cardForm(asked, hasHeader) {
|
|
14
22
|
const dashed = Boolean(asked.dashed);
|
|
15
|
-
const bar = (asked.bar
|
|
23
|
+
const bar = on(asked.bar, dashed) && hasHeader;
|
|
16
24
|
return {
|
|
17
|
-
flush: (asked.flush
|
|
25
|
+
flush: on(asked.flush, dashed),
|
|
18
26
|
bar,
|
|
19
27
|
dashed,
|
|
20
28
|
filled: asked.dashed === "filled",
|
|
21
29
|
tone: dashed || bar ? undefined : asked.tone,
|
|
22
|
-
attention: (asked.attention
|
|
30
|
+
attention: on(asked.attention, dashed),
|
|
31
|
+
icon: paddedIcon(asked, dashed || bar),
|
|
23
32
|
size: asked.size,
|
|
24
33
|
footer: false,
|
|
25
34
|
};
|
|
@@ -65,6 +74,9 @@ function isCardLink(props) {
|
|
|
65
74
|
* scrolls it clear of the sticky header (and a `subnav`) and outlines it in the accent colour while
|
|
66
75
|
* it is the target.
|
|
67
76
|
*
|
|
77
|
+
* `icon` is one of the library's icons at feature size in the accent colour, above the title: the
|
|
78
|
+
* mark of a feature card. It sits in the padded body, so `bar`, `flush` and `dashed` have none.
|
|
79
|
+
*
|
|
68
80
|
* `footer` is a tinted strip across the bottom, under a hairline, for the card's own actions or a
|
|
69
81
|
* total; in a `CardGrid` it stays at the foot of a card that is stretched to the row's height. It
|
|
70
82
|
* suits a panel, not a card that is a link, and `dashed` has none.
|
|
@@ -83,10 +95,10 @@ export function Card(props) {
|
|
|
83
95
|
}
|
|
84
96
|
/** A card that is not a link: `as` (a `section` by default) named by its heading. */
|
|
85
97
|
function PanelCard(props) {
|
|
86
|
-
const { title, count, status, actions, footer, bar, flush, dashed, tone, badge, attention, size = "md", children, ...panel } = props;
|
|
98
|
+
const { title, count, status, actions, footer, bar, flush, dashed, tone, badge, attention, icon, size = "md", children, ...panel } = props;
|
|
87
99
|
const { as: Tag = "section", ...rest } = panel;
|
|
88
100
|
const headingId = useId();
|
|
89
|
-
const form = withFooter(cardForm({ flush, bar, dashed, tone, attention, size }, Boolean(title || actions)), footer);
|
|
101
|
+
const form = withFooter(cardForm({ flush, bar, dashed, tone, attention, icon, size }, Boolean(title || actions)), footer);
|
|
90
102
|
// The title names the section or article; a caller's own name wins, and a bare `div` takes none.
|
|
91
103
|
const named = title &&
|
|
92
104
|
Tag !== "div" &&
|
|
@@ -108,8 +120,8 @@ function splitRootAttrs(props) {
|
|
|
108
120
|
}
|
|
109
121
|
/** A card that is a link: a `div` with the link stretched over it as an empty overlay, so the card's own links and buttons are not nested in it. */
|
|
110
122
|
function LinkCard(props) {
|
|
111
|
-
const { title, count, status, actions, bar, flush, dashed, tone, badge, attention, size = "md", children, external = false, ...rest } = props;
|
|
112
|
-
const form = cardForm({ flush, bar, dashed, tone, attention, size }, Boolean(title));
|
|
123
|
+
const { title, count, status, actions, bar, flush, dashed, tone, badge, attention, icon, size = "md", children, external = false, ...rest } = props;
|
|
124
|
+
const form = cardForm({ flush, bar, dashed, tone, attention, icon, size }, Boolean(title));
|
|
113
125
|
const generated = useId();
|
|
114
126
|
const [root, link] = splitRootAttrs(rest);
|
|
115
127
|
// The link is named by the whole card, so the root needs an id: the caller's, else a generated one.
|
|
@@ -128,7 +140,7 @@ function cardBody(form, { title, count, status, actions, children, headingId, })
|
|
|
128
140
|
if (form.bar) {
|
|
129
141
|
return (_jsxs(_Fragment, { children: [_jsx(CardBar, { heading: heading, actions: actions, size: form.size }), _jsx("div", { className: cx(grow, !form.flush && BODY_PAD[form.size]) || undefined, children: children })] }));
|
|
130
142
|
}
|
|
131
|
-
const content = (_jsxs(_Fragment, { children: [_jsx(CardHeader, { heading: heading, actions: actions, size: form.size }), children] }));
|
|
143
|
+
const content = (_jsxs(_Fragment, { children: [form.icon && _jsx(CardIcon, { name: form.icon, size: form.size }), _jsx(CardHeader, { heading: heading, actions: actions, size: form.size }), children] }));
|
|
132
144
|
// With a footer the root has no padding, so the body pads itself and takes the room left over.
|
|
133
145
|
return form.footer ? (_jsx("div", { className: cx(grow, !form.flush && BODY_PAD[form.size]), children: content })) : (content);
|
|
134
146
|
}
|
package/dist/CardParts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { IconName } from "./icons.js";
|
|
2
3
|
import type { Size } from "./size.js";
|
|
3
4
|
export declare function CardHeading({ id, title, count, status, bar, spaced, size, }: {
|
|
4
5
|
id: string | undefined;
|
|
@@ -9,6 +10,11 @@ export declare function CardHeading({ id, title, count, status, bar, spaced, siz
|
|
|
9
10
|
spaced: boolean;
|
|
10
11
|
size: Size;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
13
|
+
/** A card's `icon`: the feature-size mark above the heading, in the accent colour. */
|
|
14
|
+
export declare function CardIcon({ name, size }: {
|
|
15
|
+
name: IconName;
|
|
16
|
+
size: Size;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
12
18
|
export declare function CardBar({ heading, actions, size, }: {
|
|
13
19
|
heading: ReactNode;
|
|
14
20
|
actions: ReactNode;
|
package/dist/CardParts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "./cx.js";
|
|
3
|
+
import { Icon } from "./Icon.js";
|
|
3
4
|
import { Overline } from "./typography.js";
|
|
4
5
|
/** The space under a card's heading, by `size`. */
|
|
5
6
|
const HEADING_GAP = { sm: "mb-2", md: "mb-3", lg: "mb-4" };
|
|
@@ -9,6 +10,10 @@ export function CardHeading({ id, title, count, status, bar, spaced, size, }) {
|
|
|
9
10
|
const ink = bar ? "text-slate-500" : "text-slate-400";
|
|
10
11
|
return (_jsxs("div", { className: cx("flex gap-2", status ? "items-center" : "items-baseline", spaced && HEADING_GAP[size]), children: [_jsx(Overline, { as: "h2", id: id, color: bar ? "tertiary" : "muted", children: title }), count !== undefined && _jsx("span", { className: cx("text-xs font-medium", ink), children: count }), status] }));
|
|
11
12
|
}
|
|
13
|
+
/** A card's `icon`: the feature-size mark above the heading, in the accent colour. */
|
|
14
|
+
export function CardIcon({ name, size }) {
|
|
15
|
+
return (_jsx("div", { className: cx("flex text-violet-600", HEADING_GAP[size]), children: _jsx(Icon, { name: name, size: "feature" }) }));
|
|
16
|
+
}
|
|
12
17
|
function CardActions({ children }) {
|
|
13
18
|
return _jsx("div", { className: "ml-auto flex items-center gap-2", children: children });
|
|
14
19
|
}
|
package/dist/CodeBlock.d.ts
CHANGED
|
@@ -12,16 +12,31 @@ type CodeBlockSource = {
|
|
|
12
12
|
children?: never;
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* A
|
|
16
|
-
* `language` colours it, and `label` names the language or file (`bash`,
|
|
17
|
-
* the code. `text` (the default) leaves the code in one colour, for a
|
|
18
|
-
* Give the text as `children` or as `lines`.
|
|
19
|
-
*
|
|
15
|
+
* A block of code: mono, in a bordered inset box. No client JS unless `copy` adds a copy button
|
|
16
|
+
* beside the code, which copies it exactly as given and needs the page hydrated. `language` colours it, and `label` names the language or file (`bash`,
|
|
17
|
+
* `css`) in a caption above the code. `text` (the default) leaves the code in one colour, for a
|
|
18
|
+
* block that is not a language. Give the text as `children` or as `lines`. Long lines wrap, so
|
|
19
|
+
* nothing is cut off and a command stays one copyable line; `overflow="scroll"` keeps lines whole
|
|
20
|
+
* and scrolls them sideways under a thin themed scrollbar, for code whose layout matters. Every
|
|
21
|
+
* other figure prop goes on the root `<figure>`. For one short line to copy, `CopyLine` is the
|
|
22
|
+
* smaller box.
|
|
20
23
|
*/
|
|
21
|
-
export declare function CodeBlock({ label, language, children, lines, ...rest }: {
|
|
24
|
+
export declare function CodeBlock({ label, language, overflow, copy, children, lines, ...rest }: {
|
|
22
25
|
/** A caption above the code naming its language or file (`bash`, `theme.css`). */
|
|
23
26
|
label?: string;
|
|
24
27
|
/** What the code is written in; `text` (the default) colours nothing. */
|
|
25
28
|
language?: CodeLanguage;
|
|
29
|
+
/**
|
|
30
|
+
* What a line longer than the box does: `wrap` onto the next line (the default, nothing is cut
|
|
31
|
+
* off), or `scroll` sideways inside the box.
|
|
32
|
+
* @default wrap
|
|
33
|
+
*/
|
|
34
|
+
overflow?: "wrap" | "scroll";
|
|
35
|
+
/**
|
|
36
|
+
* A copy button beside the code, which copies it exactly as given. It is a client component:
|
|
37
|
+
* leave it off on a page that is rendered to static HTML without hydration.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
copy?: boolean;
|
|
26
41
|
} & CodeBlockSource & Unstyled<Omit<ComponentPropsWithoutRef<"figure">, "children">>): ReactElement;
|
|
27
42
|
export {};
|
package/dist/CodeBlock.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { CodeBody } from "./CodeBody.js";
|
|
3
3
|
/**
|
|
4
|
-
* A
|
|
5
|
-
* `language` colours it, and `label` names the language or file (`bash`,
|
|
6
|
-
* the code. `text` (the default) leaves the code in one colour, for a
|
|
7
|
-
* Give the text as `children` or as `lines`.
|
|
8
|
-
*
|
|
4
|
+
* A block of code: mono, in a bordered inset box. No client JS unless `copy` adds a copy button
|
|
5
|
+
* beside the code, which copies it exactly as given and needs the page hydrated. `language` colours it, and `label` names the language or file (`bash`,
|
|
6
|
+
* `css`) in a caption above the code. `text` (the default) leaves the code in one colour, for a
|
|
7
|
+
* block that is not a language. Give the text as `children` or as `lines`. Long lines wrap, so
|
|
8
|
+
* nothing is cut off and a command stays one copyable line; `overflow="scroll"` keeps lines whole
|
|
9
|
+
* and scrolls them sideways under a thin themed scrollbar, for code whose layout matters. Every
|
|
10
|
+
* other figure prop goes on the root `<figure>`. For one short line to copy, `CopyLine` is the
|
|
11
|
+
* smaller box.
|
|
9
12
|
*/
|
|
10
|
-
export function CodeBlock({ label, language = "text", children, lines, ...rest }) {
|
|
11
|
-
return (_jsxs("figure", { ...rest, className: "overflow-hidden rounded-lg border border-slate-200 bg-slate-50 font-mono text-[12.5px] text-slate-900", children: [label && (_jsx("figcaption", { className: "border-b border-slate-200 px-3 py-1.5 text-xs text-slate-500", children: label })), _jsx(
|
|
13
|
+
export function CodeBlock({ label, language = "text", overflow = "wrap", copy = false, children, lines, ...rest }) {
|
|
14
|
+
return (_jsxs("figure", { ...rest, className: "overflow-hidden rounded-lg border border-slate-200 bg-slate-50 font-mono text-[12.5px] text-slate-900", children: [label && (_jsx("figcaption", { className: "border-b border-slate-200 px-3 py-1.5 text-xs text-slate-500", children: label })), _jsx(CodeBody, { code: lines ? lines.join("\n") : children, language: language, wrap: overflow === "wrap", copy: copy })] }));
|
|
12
15
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { CodeLanguage } from "./highlight.js";
|
|
3
|
+
/**
|
|
4
|
+
* The code of a `CodeBlock` or a `CodeExample`: the `<pre>` and, with `copy`, a `CopyButton` beside
|
|
5
|
+
* it that copies `code` exactly as given (wrapping is CSS, so it adds no characters). `wrap` wraps
|
|
6
|
+
* long lines at whitespace only (a flag with hyphens stays whole; a word wider than the box still
|
|
7
|
+
* breaks inside itself); without it they scroll sideways under a themed thin scrollbar. The button's column has
|
|
8
|
+
* a fixed width, so "copy" turning into "copied" never rewraps the code beside it. The button needs
|
|
9
|
+
* client JS, which is why it is opt-in: a statically rendered page would show a dead one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function CodeBody({ code, language, wrap, copy, }: {
|
|
12
|
+
code: string;
|
|
13
|
+
language: CodeLanguage;
|
|
14
|
+
wrap: boolean;
|
|
15
|
+
copy: boolean;
|
|
16
|
+
}): ReactElement;
|
package/dist/CodeBody.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CodeText } from "./CodeText.js";
|
|
3
|
+
import { CopyButton } from "./CopyButton.js";
|
|
4
|
+
import { cx, themedScrollbar } from "./cx.js";
|
|
5
|
+
/**
|
|
6
|
+
* The code of a `CodeBlock` or a `CodeExample`: the `<pre>` and, with `copy`, a `CopyButton` beside
|
|
7
|
+
* it that copies `code` exactly as given (wrapping is CSS, so it adds no characters). `wrap` wraps
|
|
8
|
+
* long lines at whitespace only (a flag with hyphens stays whole; a word wider than the box still
|
|
9
|
+
* breaks inside itself); without it they scroll sideways under a themed thin scrollbar. The button's column has
|
|
10
|
+
* a fixed width, so "copy" turning into "copied" never rewraps the code beside it. The button needs
|
|
11
|
+
* client JS, which is why it is opt-in: a statically rendered page would show a dead one.
|
|
12
|
+
*/
|
|
13
|
+
export function CodeBody({ code, language, wrap, copy, }) {
|
|
14
|
+
return (_jsxs("div", { className: cx("flex items-start", copy && "gap-1 pr-1.5"), children: [_jsx("div", { className: cx("min-w-0 flex-1", !wrap && `overflow-x-auto ${themedScrollbar}`), children: _jsx("pre", { className: cx("px-3 py-2.5 font-mono text-[12.5px] text-slate-900", wrap ? "break-words whitespace-pre-wrap" : "w-max min-w-full whitespace-pre"), children: _jsx(CodeText, { code: code, language: language, breakAtSpaces: wrap }) }) }), copy && (_jsx("div", { className: "flex w-14 shrink-0 justify-end pt-2", children: _jsx(CopyButton, { text: code }) }))] }));
|
|
15
|
+
}
|
package/dist/CodeExample.d.ts
CHANGED
|
@@ -2,16 +2,23 @@ import type { ReactElement, ReactNode } from "react";
|
|
|
2
2
|
import type { Passthrough } from "./props.js";
|
|
3
3
|
/**
|
|
4
4
|
* One framed example: the rendered `children` on top and the `code` that makes them attached
|
|
5
|
-
* underneath, in a single bordered box with one divider between.
|
|
6
|
-
*
|
|
7
|
-
* height, never padded to match a neighbour, down to
|
|
8
|
-
* start alike. A wide preview or a long line of code
|
|
9
|
-
* page or
|
|
5
|
+
* underneath, in a single bordered box with one divider between. With `copy`, a copy
|
|
6
|
+
* button beside the code copies it exactly as given (client JS; off by default). An optional `caption` is the frame's last band. The code is React, so
|
|
7
|
+
* it is coloured as TSX. The preview is its own height, never padded to match a neighbour, down to
|
|
8
|
+
* a shared minimum so two frames side by side start alike. A wide preview or a long line of code
|
|
9
|
+
* scrolls inside the frame, under a thin themed scrollbar, instead of widening the page or
|
|
10
|
+
* wrapping.
|
|
10
11
|
*/
|
|
11
|
-
export declare function CodeExample({ code, caption, children, ...rest }: {
|
|
12
|
+
export declare function CodeExample({ code, caption, copy, children, ...rest }: {
|
|
12
13
|
/** The source shown under the preview, newlines kept. */
|
|
13
14
|
code: string;
|
|
14
15
|
/** One note on the example, under the code. */
|
|
15
16
|
caption?: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* A copy button beside the code. It is a client component: leave it off on a page that is
|
|
19
|
+
* rendered to static HTML without hydration.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
copy?: boolean;
|
|
16
23
|
children: ReactNode;
|
|
17
24
|
} & Passthrough): ReactElement;
|
package/dist/CodeExample.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { CodeBody } from "./CodeBody.js";
|
|
3
|
+
import { themedScrollbar } from "./cx.js";
|
|
3
4
|
/**
|
|
4
5
|
* One framed example: the rendered `children` on top and the `code` that makes them attached
|
|
5
|
-
* underneath, in a single bordered box with one divider between.
|
|
6
|
-
*
|
|
7
|
-
* height, never padded to match a neighbour, down to
|
|
8
|
-
* start alike. A wide preview or a long line of code
|
|
9
|
-
* page or
|
|
6
|
+
* underneath, in a single bordered box with one divider between. With `copy`, a copy
|
|
7
|
+
* button beside the code copies it exactly as given (client JS; off by default). An optional `caption` is the frame's last band. The code is React, so
|
|
8
|
+
* it is coloured as TSX. The preview is its own height, never padded to match a neighbour, down to
|
|
9
|
+
* a shared minimum so two frames side by side start alike. A wide preview or a long line of code
|
|
10
|
+
* scrolls inside the frame, under a thin themed scrollbar, instead of widening the page or
|
|
11
|
+
* wrapping.
|
|
10
12
|
*/
|
|
11
|
-
export function CodeExample({ code, caption, children, ...rest }) {
|
|
12
|
-
return (_jsxs("figure", { ...rest, className: "overflow-hidden rounded-lg border border-slate-200 bg-white", children: [_jsx("div", { className:
|
|
13
|
+
export function CodeExample({ code, caption, copy = false, children, ...rest }) {
|
|
14
|
+
return (_jsxs("figure", { ...rest, className: "overflow-hidden rounded-lg border border-slate-200 bg-white", children: [_jsx("div", { className: `min-h-24 overflow-x-auto p-5 ${themedScrollbar}`, children: _jsx("div", { className: "text-slate-900", children: children }) }), _jsx("div", { className: "border-t border-slate-200 bg-slate-50", children: _jsx(CodeBody, { code: code, language: "tsx", wrap: false, copy: copy }) }), caption && (_jsx("figcaption", { className: "border-t border-slate-200 px-3 py-2.5 text-sm/5 text-slate-500", children: caption }))] }));
|
|
13
15
|
}
|
package/dist/CodeText.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import { type CodeLanguage } from "./highlight.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The `<code>` for a `<pre>`: the source in mono, one span per coloured run. With `breakAtSpaces` a
|
|
5
|
+
* long line may break only at whitespace (see `wholeWords` above).
|
|
6
|
+
*/
|
|
7
|
+
export declare function CodeText({ code, language, breakAtSpaces, }: {
|
|
5
8
|
code: string;
|
|
6
9
|
language: CodeLanguage;
|
|
10
|
+
breakAtSpaces?: boolean;
|
|
7
11
|
}): ReactElement;
|
package/dist/CodeText.js
CHANGED
|
@@ -15,7 +15,47 @@ const TOKEN_CLASS = {
|
|
|
15
15
|
number: "text-amber-700",
|
|
16
16
|
function: "text-blue-700",
|
|
17
17
|
};
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** One coloured run: bare text for plain, a span with the kind's palette utility otherwise. */
|
|
19
|
+
const colour = (kind, key, text) => kind === "plain" ? (text) : (_jsx("span", { className: TOKEN_CLASS[kind], children: text }, key));
|
|
20
|
+
/**
|
|
21
|
+
* The tokens regrouped into whitespace-separated words, each word one atomic inline box, with the
|
|
22
|
+
* whitespace between them left as text. A browser may break a line after a hyphen (`--skip` splits
|
|
23
|
+
* into `-` and `-skip`), but never inside an inline box, so a flag or a word stays whole. The box
|
|
24
|
+
* is capped at the line's width, so a word wider than the line still breaks inside itself instead
|
|
25
|
+
* of clipping. The characters are untouched: no joiner or non-breaking hyphen is swapped in, so
|
|
26
|
+
* selecting and copying gives the source.
|
|
27
|
+
*/
|
|
28
|
+
function wholeWords(code, language) {
|
|
29
|
+
const out = [];
|
|
30
|
+
let word = [];
|
|
31
|
+
const flush = () => {
|
|
32
|
+
if (word.length > 0) {
|
|
33
|
+
out.push(_jsx("span", { className: "inline-block max-w-full align-top", children: word }, `w${out.length}`));
|
|
34
|
+
word = [];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
for (const token of highlight(code, language)) {
|
|
38
|
+
for (const piece of token.text.split(/(\s+)/)) {
|
|
39
|
+
if (piece === "")
|
|
40
|
+
continue;
|
|
41
|
+
if (/^\s/.test(piece)) {
|
|
42
|
+
flush();
|
|
43
|
+
out.push(colour(token.kind, out.length, piece));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
word.push(colour(token.kind, word.length, piece));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
flush();
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The `<code>` for a `<pre>`: the source in mono, one span per coloured run. With `breakAtSpaces` a
|
|
55
|
+
* long line may break only at whitespace (see `wholeWords` above).
|
|
56
|
+
*/
|
|
57
|
+
export function CodeText({ code, language, breakAtSpaces = false, }) {
|
|
58
|
+
return (_jsx("code", { children: breakAtSpaces
|
|
59
|
+
? wholeWords(code, language)
|
|
60
|
+
: highlight(code, language).map((token, index) => colour(token.kind, index, token.text)) }));
|
|
21
61
|
}
|
package/dist/CopyButton.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type ReactElement } from "react";
|
|
2
2
|
import type { Unstyled } from "./props.js";
|
|
3
|
-
/** Writes `text` to the clipboard and swaps the label to `copiedLabel
|
|
3
|
+
/** Writes `text` to the clipboard and swaps the label to `copiedLabel` for two seconds. Defaults to `type="button"`. A refused or missing clipboard leaves the label on `label`. Every other button prop, `disabled` included, goes on the `<button>`. */
|
|
4
4
|
export declare function CopyButton({ text, label, copiedLabel, onClick, ...rest }: {
|
|
5
5
|
/** What goes on the clipboard. */
|
|
6
6
|
text: string;
|
package/dist/CopyButton.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useEffect, useState, } from "react";
|
|
3
3
|
import { focusRing } from "./cx.js";
|
|
4
4
|
const BASE = `shrink-0 rounded px-1.5 py-0.5 font-sans text-xs text-slate-400 hover:bg-slate-100 disabled:pointer-events-none disabled:opacity-50 ${focusRing}`;
|
|
5
|
-
/**
|
|
5
|
+
/** How long the copied state stays before the label goes back, so a second copy is seen too. */
|
|
6
|
+
const COPIED_MS = 2000;
|
|
7
|
+
/** Writes `text` to the clipboard and swaps the label to `copiedLabel` for two seconds. Defaults to `type="button"`. A refused or missing clipboard leaves the label on `label`. Every other button prop, `disabled` included, goes on the `<button>`. */
|
|
6
8
|
export function CopyButton({ text, label = "copy", copiedLabel = "copied", onClick, ...rest }) {
|
|
7
9
|
const [copied, setCopied] = useState(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const timer = copied ? setTimeout(() => setCopied(false), COPIED_MS) : undefined;
|
|
12
|
+
return () => clearTimeout(timer);
|
|
13
|
+
}, [copied]);
|
|
8
14
|
return (_jsx("button", { type: "button", ...rest, className: BASE, onClick: (event) => {
|
|
9
15
|
onClick?.(event);
|
|
10
16
|
// The rejection is swallowed on purpose: a denied write is not an error the page can act on.
|
package/dist/Icon.d.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { type IconName } from "./icons.js";
|
|
2
3
|
import type { Passthrough } from "./props.js";
|
|
3
4
|
import type { Size } from "./size.js";
|
|
4
5
|
import { type TextTone } from "./typography.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* decorative (`aria-hidden`) by default; a `label` makes it an image with that name, for an icon
|
|
9
|
-
* that carries meaning no text beside it says.
|
|
10
|
-
*/
|
|
11
|
-
export declare function Icon({ size, tone, label, children, ...rest }: Passthrough & {
|
|
12
|
-
/** The box: sm 14px, md 16px (the default), lg 20px. */
|
|
13
|
-
size?: Size;
|
|
6
|
+
type IconProps = Passthrough & {
|
|
7
|
+
/** The box: sm 14px, md 16px (the default), lg 20px, `feature` 32px for an icon that stands for an idea on its own (a feature card). */
|
|
8
|
+
size?: Size | "feature";
|
|
14
9
|
/** A text tone, or `inherit` (the default) for the colour of the text around it. */
|
|
15
10
|
tone?: TextTone | "inherit";
|
|
16
11
|
/** Names the icon (`role="img"`). Without one the icon is hidden from assistive tech. */
|
|
17
12
|
label?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* One inline icon, sized to a fixed box (sm 14px, md 16px, lg 20px, `feature` 32px) and drawn in
|
|
16
|
+
* `currentColor`, so it takes the text colour around it unless a `tone` says otherwise. `name`
|
|
17
|
+
* picks one of the library's own icons, all drawn on one grid with one stroke (`diff`,
|
|
18
|
+
* `baseline`, `review`, `ci`, `self-host`, `privacy`, `capture`, `branch`, `theme`, `speed`);
|
|
19
|
+
* without a name, a bare `<svg>` child fills the box. It is decorative (`aria-hidden`) by
|
|
20
|
+
* default; a `label` makes it an image with that name, for an icon that carries meaning no text
|
|
21
|
+
* beside it says.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Icon({ size, tone, label, name, children, ...rest }: IconProps & ({
|
|
24
|
+
/** One of the library's icons, named for the idea it stands for. */
|
|
25
|
+
name: IconName;
|
|
26
|
+
children?: undefined;
|
|
27
|
+
} | {
|
|
28
|
+
name?: undefined;
|
|
18
29
|
/** The `<svg>`, drawn in `currentColor`; it fills the box. */
|
|
19
30
|
children: ReactNode;
|
|
20
|
-
}): ReactElement;
|
|
31
|
+
})): ReactElement;
|
|
32
|
+
export {};
|
package/dist/Icon.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "./cx.js";
|
|
3
|
+
import { iconGlyph } from "./icons.js";
|
|
3
4
|
import { TEXT_TONE } from "./typography.js";
|
|
4
|
-
const BOX = {
|
|
5
|
+
const BOX = {
|
|
6
|
+
sm: "size-3.5",
|
|
7
|
+
md: "size-4",
|
|
8
|
+
lg: "size-5",
|
|
9
|
+
feature: "size-8",
|
|
10
|
+
};
|
|
5
11
|
/**
|
|
6
|
-
* One inline icon
|
|
7
|
-
* `currentColor`, so it takes the text colour around it unless a `tone` says otherwise.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* One inline icon, sized to a fixed box (sm 14px, md 16px, lg 20px, `feature` 32px) and drawn in
|
|
13
|
+
* `currentColor`, so it takes the text colour around it unless a `tone` says otherwise. `name`
|
|
14
|
+
* picks one of the library's own icons, all drawn on one grid with one stroke (`diff`,
|
|
15
|
+
* `baseline`, `review`, `ci`, `self-host`, `privacy`, `capture`, `branch`, `theme`, `speed`);
|
|
16
|
+
* without a name, a bare `<svg>` child fills the box. It is decorative (`aria-hidden`) by
|
|
17
|
+
* default; a `label` makes it an image with that name, for an icon that carries meaning no text
|
|
18
|
+
* beside it says.
|
|
10
19
|
*/
|
|
11
|
-
export function Icon({ size = "md", tone = "inherit", label, children, ...rest }) {
|
|
12
|
-
return (_jsx("span", { ...rest, ...(label ? { role: "img", "aria-label": label } : { "aria-hidden": true }), className: cx("inline-flex shrink-0 [&>svg]:size-full", BOX[size], tone !== "inherit" && TEXT_TONE[tone]), children: children }));
|
|
20
|
+
export function Icon({ size = "md", tone = "inherit", label, name, children, ...rest }) {
|
|
21
|
+
return (_jsx("span", { ...rest, ...(label ? { role: "img", "aria-label": label } : { "aria-hidden": true }), className: cx("inline-flex shrink-0 [&>svg]:size-full", BOX[size], tone !== "inherit" && TEXT_TONE[tone]), children: name === undefined ? children : iconGlyph(name) }));
|
|
13
22
|
}
|
package/dist/cx.d.ts
CHANGED
|
@@ -10,3 +10,10 @@ export declare const focusRing = "focus-visible:outline-2 focus-visible:outline-
|
|
|
10
10
|
* paint over the page's sticky bars (AppShell's header, a StickyBar).
|
|
11
11
|
*/
|
|
12
12
|
export declare const liftInteractive = "isolate [&_:is(a,button,label,select,textarea,input,summary):not(.link-stretch):not(.absolute)]:relative [&_:is(a,button,label,select,textarea,input,summary):not(.link-stretch)]:z-10";
|
|
13
|
+
/**
|
|
14
|
+
* A thin scrollbar in the theme's muted ink instead of the heavy native one, for a box that
|
|
15
|
+
* scrolls sideways (code). `scrollbar-color` takes no Tailwind colour utility, so the thumb is the
|
|
16
|
+
* element's own text colour (`currentColor`, a palette utility every theme remaps) and the track is
|
|
17
|
+
* transparent. The element must not hold text of its own: put the readable colour on an inner box.
|
|
18
|
+
*/
|
|
19
|
+
export declare const themedScrollbar = "text-slate-400 [scrollbar-width:thin] [scrollbar-color:currentColor_transparent]";
|
package/dist/cx.js
CHANGED
|
@@ -12,3 +12,10 @@ export const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2
|
|
|
12
12
|
* paint over the page's sticky bars (AppShell's header, a StickyBar).
|
|
13
13
|
*/
|
|
14
14
|
export const liftInteractive = "isolate [&_:is(a,button,label,select,textarea,input,summary):not(.link-stretch):not(.absolute)]:relative [&_:is(a,button,label,select,textarea,input,summary):not(.link-stretch)]:z-10";
|
|
15
|
+
/**
|
|
16
|
+
* A thin scrollbar in the theme's muted ink instead of the heavy native one, for a box that
|
|
17
|
+
* scrolls sideways (code). `scrollbar-color` takes no Tailwind colour utility, so the thumb is the
|
|
18
|
+
* element's own text colour (`currentColor`, a palette utility every theme remaps) and the track is
|
|
19
|
+
* transparent. The element must not hold text of its own: put the readable colour on an inner box.
|
|
20
|
+
*/
|
|
21
|
+
export const themedScrollbar = "text-slate-400 [scrollbar-width:thin] [scrollbar-color:currentColor_transparent]";
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The icon set: one grammar, drawn here and nowhere else. Every icon sits on a 24-unit grid with
|
|
4
|
+
* two units of padding (its ink lives in 2..22), is drawn as an outline with a 1.5 stroke, round
|
|
5
|
+
* caps and round joins, and never fills: `GLYPH` below is the only place those are set, so an
|
|
6
|
+
* icon's own shapes carry no `fill`, `stroke` or `stroke-width` (`Icon.test.tsx` holds them to
|
|
7
|
+
* that). A name is the idea the icon stands for, not a product.
|
|
8
|
+
*/
|
|
9
|
+
declare const NAMES: readonly ["diff", "baseline", "review", "ci", "self-host", "privacy", "capture", "branch", "theme", "speed"];
|
|
10
|
+
/** The names of the icon set, one per idea. */
|
|
11
|
+
export type IconName = (typeof NAMES)[number];
|
|
12
|
+
/** The icon `name` as an `<svg>`: the set's one grid, stroke, caps and joins, drawn in `currentColor`. */
|
|
13
|
+
export declare function iconGlyph(name: IconName): ReactElement;
|
|
14
|
+
/** Every icon name, for tests. Not exported from the package. */
|
|
15
|
+
export declare const ICON_NAMES: readonly IconName[];
|
|
16
|
+
export {};
|
package/dist/icons.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The icon set: one grammar, drawn here and nowhere else. Every icon sits on a 24-unit grid with
|
|
4
|
+
* two units of padding (its ink lives in 2..22), is drawn as an outline with a 1.5 stroke, round
|
|
5
|
+
* caps and round joins, and never fills: `GLYPH` below is the only place those are set, so an
|
|
6
|
+
* icon's own shapes carry no `fill`, `stroke` or `stroke-width` (`Icon.test.tsx` holds them to
|
|
7
|
+
* that). A name is the idea the icon stands for, not a product.
|
|
8
|
+
*/
|
|
9
|
+
const NAMES = [
|
|
10
|
+
"diff",
|
|
11
|
+
"baseline",
|
|
12
|
+
"review",
|
|
13
|
+
"ci",
|
|
14
|
+
"self-host",
|
|
15
|
+
"privacy",
|
|
16
|
+
"capture",
|
|
17
|
+
"branch",
|
|
18
|
+
"theme",
|
|
19
|
+
"speed",
|
|
20
|
+
];
|
|
21
|
+
const GLYPHS = {
|
|
22
|
+
/** Before and after, side by side: a minus on the left, a plus on the right. */
|
|
23
|
+
diff: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "4.5", width: "18", height: "15", rx: "2.5" }), _jsx("path", { d: "M12 4.5v15M6.5 12h3M14.5 12h3M16 10.5v3" })] })),
|
|
24
|
+
/** The approved picture to compare against: a frame holding a sun over hills. */
|
|
25
|
+
baseline: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "4.5", width: "18", height: "15", rx: "2.5" }), _jsx("circle", { cx: "8.5", cy: "9.5", r: "1.5" }), _jsx("path", { d: "m3.5 17 5-4.5 3.5 3 3-2.5 5.5 4" })] })),
|
|
26
|
+
/** Looking something over: an eye. */
|
|
27
|
+
review: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] })),
|
|
28
|
+
/** A pipeline that comes round and passes: a loop with a tick in it. */
|
|
29
|
+
ci: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M20 12a8 8 0 1 1-2.34-5.66M13.8 6.34h3.86V2.5" }), _jsx("path", { d: "m8.8 12.2 2.2 2.2 4.2-4.6" })] })),
|
|
30
|
+
/** Your own machine: two stacked servers with their lights. */
|
|
31
|
+
"self-host": (_jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "6.5", rx: "2" }), _jsx("rect", { x: "3", y: "13.5", width: "18", height: "6.5", rx: "2" }), _jsx("path", { d: "M7 7.25h.01M7 16.75h.01M11 7.25h6M11 16.75h6" })] })),
|
|
32
|
+
/** Kept to yourself: a closed padlock. */
|
|
33
|
+
privacy: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "4.5", y: "10.5", width: "15", height: "10", rx: "2.5" }), _jsx("path", { d: "M8 10.5V8a4 4 0 0 1 8 0v2.5M12 14.5v2" })] })),
|
|
34
|
+
/** Taking a screenshot: viewfinder corners round a lens. */
|
|
35
|
+
capture: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M4 8V6.5A2.5 2.5 0 0 1 6.5 4H8M16 4h1.5A2.5 2.5 0 0 1 20 6.5V8M20 16v1.5a2.5 2.5 0 0 1-2.5 2.5H16M8 20H6.5A2.5 2.5 0 0 1 4 17.5V16" }), _jsx("circle", { cx: "12", cy: "12", r: "3.25" })] })),
|
|
36
|
+
/** A line of work that splits off and comes back: a branch joining its trunk. */
|
|
37
|
+
branch: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "6", cy: "5.5", r: "2.5" }), _jsx("circle", { cx: "6", cy: "18.5", r: "2.5" }), _jsx("circle", { cx: "18", cy: "8", r: "2.5" }), _jsx("path", { d: "M6 8v8M18 10.5c0 3.2-2.4 4.5-6 4.5H6" })] })),
|
|
38
|
+
/** The look of a site, which a theme swaps: a drop of colour. */
|
|
39
|
+
theme: (_jsx("path", { d: "M12 3.5S5.75 9.9 5.75 14.25a6.25 6.25 0 0 0 12.5 0C18.25 9.9 12 3.5 12 3.5ZM9 14.5a3 3 0 0 0 3 3" })),
|
|
40
|
+
/** Fast: a bolt. */
|
|
41
|
+
speed: _jsx("path", { d: "M13.25 3 5 13.5h6.25L10.75 21 19 10.5h-6.25Z" }),
|
|
42
|
+
};
|
|
43
|
+
/** The icon `name` as an `<svg>`: the set's one grid, stroke, caps and joins, drawn in `currentColor`. */
|
|
44
|
+
export function iconGlyph(name) {
|
|
45
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: GLYPHS[name] }));
|
|
46
|
+
}
|
|
47
|
+
/** Every icon name, for tests. Not exported from the package. */
|
|
48
|
+
export const ICON_NAMES = NAMES;
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export { Dialog, type DialogProps } from "./Dialog.js";
|
|
|
40
40
|
export { Drawer, type DrawerProps } from "./Drawer.js";
|
|
41
41
|
export { Lightbox, type LightboxProps } from "./Lightbox.js";
|
|
42
42
|
export { Icon } from "./Icon.js";
|
|
43
|
+
export type { IconName } from "./icons.js";
|
|
43
44
|
export { IconButton, type IconButtonTone, type IconButtonVariant } from "./IconButton.js";
|
|
44
45
|
export { ColourSchemeToggle } from "./ColourSchemeToggle.js";
|
|
45
46
|
export { applyColourScheme, schemeClass, type ColourSchemeClasses, colourSchemeScript, readColourScheme, storeColourScheme, type ColourScheme, } from "./colourScheme.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hackcove/ui-react",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Hack Cove React components: primitives, typography roles and a safe Markdown renderer, written against Tailwind v4 utilities. Pair with a @hackcove/theme-* package.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|