@hackcove/ui 0.2.5 → 0.2.6

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 CHANGED
@@ -40,7 +40,7 @@ export const Example = () => (
40
40
  ## What is exported
41
41
 
42
42
  - Primitives: `Card` (a bordered panel; `title` and `count` make a small heading; `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; `as` is `section` (default), `article` or `div`), `EpicHeader`, `Empty`, `PageTitle` (deprecated: use `PageHeader`), `Pill`, `groupByEpic`, `Markdown`.
43
- - Layout (see Layout): `AppShell`, `appShellRootClass`, `Container`, `PageHeader`, `Stack`, `Section`, and the page layouts `ListLayout`, `DetailLayout`, `FormLayout`, `ProseLayout`, `CardGrid`, `SidebarLayout`, `FullBleedLayout`, `CenteredLayout`.
43
+ - Layout (see Layout): `AppShell`, `AppShellBrand`, `appShellRootClass`, `appShellRootClassFor`, `Container`, `PageHeader`, `Stack`, `Section`, and the page layouts `ListLayout`, `DetailLayout`, `FormLayout`, `ProseLayout`, `CardGrid`, `SidebarLayout`, `FullBleedLayout`, `CenteredLayout`.
44
44
  - Typography roles (each pins size + weight, takes a `color` and an `as`): `Display`, `Lead`,
45
45
  `PageHeading`, `SectionHeading`, `SubsectionHeading`, `ModalHeading`, `Subtitle`, `Overline`,
46
46
  `Body`, `BodyStrong`, `Caption`. Three opt-in props, none changing today's look: `SectionHeading rule`
@@ -137,9 +137,25 @@ import { AppShell, PageHeader, Section, Stack, appShellRootClass } from "@hackco
137
137
  with fixed `py-8`, and an optional `footer`. The header row is filled by slots, left to right:
138
138
  `brand` (logo and name, at the brand type with the logo gap), `nav` (a `NavTabs`), `header` (a
139
139
  free node, the escape hatch) and `controls` (end-aligned, `gap-2`, centred): no wrapper, no
140
- `className`. `hideChromeInPrint` is opt-in: the header and footer are hidden in print and main
140
+ `className`. Nothing in `controls` shrinks, and a full-width control there (a `Select` at its
141
+ default `width="full"`) sizes to its content, so a `Select` next to a `ColourSchemeToggle` leaves
142
+ the toggle its square. `hideChromeInPrint` is opt-in: the header and footer are hidden in print and main
141
143
  drops its padding (the page margin is then your `@page`); without it a print is unchanged. `bleed` drops main's `Container` for a full-bleed
142
144
  page; the header keeps its own, so put a `Container` back around anything that should line up.
145
+ - **`AppShellBrand`** is the `brand` slot as one link: `logo` and `label` inside the same `<a>`, so
146
+ the whole brand goes home (the label names the link; the logo is hidden from assistive tech). With
147
+ `render` it draws your router's `<Link>`, as `NavTabs`, `Breadcrumbs` and `TextLink` do:
148
+
149
+ ```tsx
150
+ <AppShell brand={<AppShellBrand logo={<Avatar name="Quotes" size="sm" />} label="Quotes"
151
+ render={(props) => <Link to="/" {...props} />} />} …>
152
+ ```
153
+
154
+ A plain node in `brand` still works. Stories: `Layout/AppShell › LinkedBrand`, `RouterBrand`.
155
+ - **`appShellRootClassFor(scheme, schemes?)`** is what goes on `<html>` when the server knows the
156
+ scheme: that scheme's class and `appShellRootClass`, joined, so there is no string concatenation:
157
+ `<html className={appShellRootClassFor(scheme, schemes)}>`. `appShellRootClass` and `schemeClass`
158
+ stay exported.
143
159
  - **`Container`** is the only place the content width (`max-w-6xl`) and side padding
144
160
  (`px-4 sm:px-6`) are defined. **Pages never set their own width or outer padding**: no `max-w-*`,
145
161
  no `mx-auto`, no `px-*` on a page. AppShell already gives them the edge.
@@ -1,9 +1,34 @@
1
1
  import { type ReactElement, type ReactNode } from "react";
2
+ import { type ColourScheme, type ColourSchemeClasses } from "./colourScheme.js";
3
+ import { type LinkRender } from "./link.js";
2
4
  /**
3
5
  * The class an app puts on its own `<html>`: it always reserves the scrollbar, so centred content
4
6
  * does not shift between a short page and a long one. AppShell never touches `<html>` itself.
5
7
  */
6
8
  export declare const appShellRootClass = "overflow-y-scroll";
9
+ /**
10
+ * Everything an app puts on its own `<html>` from the scheme the server knows: that scheme's class
11
+ * (`schemeClass`) and `appShellRootClass`, joined. `<html className={appShellRootClassFor(scheme,
12
+ * schemes)}>` replaces `schemeClass(scheme, schemes) + " " + appShellRootClass`.
13
+ */
14
+ export declare function appShellRootClassFor(scheme: ColourScheme, schemes?: ColourSchemeClasses): string;
15
+ /**
16
+ * The `brand` slot as one link: a logo and a label inside the same `<a>`, so the whole brand is the
17
+ * way home. The label names the link; the logo is decoration and hidden from assistive tech. With
18
+ * `render` it draws the app's own router `<Link>` (`render={(p) => <Link to="/" {...p} />}`).
19
+ */
20
+ export declare function AppShellBrand({ logo, label, href, render, }: {
21
+ /** The mark before the label, e.g. an `Avatar` or an `<img>`. */
22
+ logo?: ReactNode;
23
+ /** The name beside the logo. It is the link's accessible name. */
24
+ label: ReactNode;
25
+ } & ({
26
+ href: string;
27
+ render?: LinkRender | undefined;
28
+ } | {
29
+ href?: string | undefined;
30
+ render: LinkRender;
31
+ })): ReactElement;
7
32
  /**
8
33
  * The page frame: a sticky one-row header, an optional second `subnav` row, the main area and an
9
34
  * optional footer. Header, main and footer each sit in a `Container`, so pages never set their own
@@ -15,11 +40,11 @@ export declare const appShellRootClass = "overflow-y-scroll";
15
40
  * `className` for any of them.
16
41
  */
17
42
  export declare function AppShell({ brand, nav, controls, header, subnav, footer, bleed, hideChromeInPrint, children, }: {
18
- /** The brand: a logo and a name, in the header's brand type, with the gap between them. */
43
+ /** The brand: a logo and a name, in the header's brand type, with the gap between them. `AppShellBrand` makes both one link. */
19
44
  brand?: ReactNode;
20
45
  /** The main navigation, e.g. `NavTabs` (it is already a `<nav>`). */
21
46
  nav?: ReactNode;
22
- /** The end of the row: a picker, a theme toggle. Gap-2, vertically centred. */
47
+ /** The end of the row: a picker, a theme toggle. Gap-2, vertically centred; no child shrinks, and a full-width control (a `Select` at its default) sizes to its content. */
23
48
  controls?: ReactNode;
24
49
  /** The escape hatch: a free node in the header row, after `brand` and `nav`. */
25
50
  header?: ReactNode;
package/dist/AppShell.js CHANGED
@@ -1,12 +1,35 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from "react";
3
3
  import { Container } from "./Container.js";
4
- import { cx } from "./cx.js";
4
+ import { schemeClass } from "./colourScheme.js";
5
+ import { cx, focusRing } from "./cx.js";
6
+ import { renderLink } from "./link.js";
5
7
  /**
6
8
  * The class an app puts on its own `<html>`: it always reserves the scrollbar, so centred content
7
9
  * does not shift between a short page and a long one. AppShell never touches `<html>` itself.
8
10
  */
9
11
  export const appShellRootClass = "overflow-y-scroll";
12
+ /**
13
+ * Everything an app puts on its own `<html>` from the scheme the server knows: that scheme's class
14
+ * (`schemeClass`) and `appShellRootClass`, joined. `<html className={appShellRootClassFor(scheme,
15
+ * schemes)}>` replaces `schemeClass(scheme, schemes) + " " + appShellRootClass`.
16
+ */
17
+ export function appShellRootClassFor(scheme, schemes) {
18
+ return cx(schemeClass(scheme, schemes), appShellRootClass);
19
+ }
20
+ /**
21
+ * The `brand` slot as one link: a logo and a label inside the same `<a>`, so the whole brand is the
22
+ * way home. The label names the link; the logo is decoration and hidden from assistive tech. With
23
+ * `render` it draws the app's own router `<Link>` (`render={(p) => <Link to="/" {...p} />}`).
24
+ */
25
+ export function AppShellBrand({ logo, label, href, render, }) {
26
+ return renderLink({
27
+ href,
28
+ render,
29
+ className: cx("flex items-center gap-2 rounded", focusRing),
30
+ children: (_jsxs(_Fragment, { children: [logo && (_jsx("span", { "aria-hidden": "true", className: "flex shrink-0 items-center", children: logo })), _jsx("span", { children: label })] })),
31
+ });
32
+ }
10
33
  // One fixed-height row that scrolls sideways and never wraps; its children keep their width.
11
34
  const ROW = "flex items-center gap-4 overflow-x-auto whitespace-nowrap *:shrink-0";
12
35
  // While a row has more to scroll to on a side, that edge fades out (a mask, so it follows any theme).
@@ -44,7 +67,7 @@ function ScrollRow({ className, children }) {
44
67
  }
45
68
  /** The header row's slots in order: brand, nav, the free `children`, then controls at the end. */
46
69
  function HeaderSlots({ brand, nav, controls, children, }) {
47
- return (_jsxs(_Fragment, { children: [brand && (_jsx("div", { className: "flex items-center gap-2 text-base font-bold tracking-tight text-slate-900", children: brand })), nav, children, controls && _jsx("div", { className: "ml-auto flex items-center gap-2", children: controls })] }));
70
+ return (_jsxs(_Fragment, { children: [brand && (_jsx("div", { className: "flex items-center gap-2 text-base font-bold tracking-tight text-slate-900", children: brand })), nav, children, controls && (_jsx("div", { className: "ml-auto flex items-center gap-2 *:shrink-0 [&>.w-full]:w-auto", children: controls }))] }));
48
71
  }
49
72
  /**
50
73
  * The page frame: a sticky one-row header, an optional second `subnav` row, the main area and an
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { StatTile, type StatTileTone } from "./StatTile.js";
4
4
  export { ProgressBar, type BarSize, type BarTone } from "./ProgressBar.js";
5
5
  export { SplitBar } from "./SplitBar.js";
6
6
  export { Markdown } from "./Markdown.js";
7
- export { AppShell, appShellRootClass } from "./AppShell.js";
7
+ export { AppShell, AppShellBrand, appShellRootClass, appShellRootClassFor } from "./AppShell.js";
8
8
  export { Container } from "./Container.js";
9
9
  export { PageHeader, type PageHeaderProps } from "./PageHeader.js";
10
10
  export { CardGrid, CenteredLayout, DetailLayout, FormLayout, FullBleedLayout, ListLayout, ProseLayout, SidebarLayout, } from "./PageLayouts.js";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { StatTile } from "./StatTile.js";
4
4
  export { ProgressBar } from "./ProgressBar.js";
5
5
  export { SplitBar } from "./SplitBar.js";
6
6
  export { Markdown } from "./Markdown.js";
7
- export { AppShell, appShellRootClass } from "./AppShell.js";
7
+ export { AppShell, AppShellBrand, appShellRootClass, appShellRootClassFor } from "./AppShell.js";
8
8
  export { Container } from "./Container.js";
9
9
  export { PageHeader } from "./PageHeader.js";
10
10
  export { CardGrid, CenteredLayout, DetailLayout, FormLayout, FullBleedLayout, ListLayout, ProseLayout, SidebarLayout, } from "./PageLayouts.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackcove/ui",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
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",