@hackcove/ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/Button.d.ts +8 -0
- package/dist/Button.js +16 -0
- package/dist/Callout.d.ts +10 -0
- package/dist/Callout.js +13 -0
- package/dist/Checkbox.d.ts +5 -0
- package/dist/Checkbox.js +6 -0
- package/dist/Field.d.ts +12 -0
- package/dist/Field.js +13 -0
- package/dist/FilterGroup.d.ts +5 -0
- package/dist/FilterGroup.js +6 -0
- package/dist/IconButton.d.ts +6 -0
- package/dist/IconButton.js +7 -0
- package/dist/Input.d.ts +5 -0
- package/dist/Input.js +10 -0
- package/dist/NavTabs.d.ts +16 -0
- package/dist/NavTabs.js +11 -0
- package/dist/Select.d.ts +3 -0
- package/dist/Select.js +7 -0
- package/dist/Textarea.d.ts +3 -0
- package/dist/Textarea.js +7 -0
- package/dist/ToggleChip.d.ts +7 -0
- package/dist/ToggleChip.js +9 -0
- package/dist/control.d.ts +2 -0
- package/dist/control.js +3 -0
- package/dist/cx.d.ts +4 -0
- package/dist/cx.js +6 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/typography.js +1 -3
- package/dist/ui.d.ts +7 -2
- package/dist/ui.js +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Hack Cove React components, written against Tailwind v4 utilities. They carry no
|
|
|
4
4
|
with a `@hackcove/theme-*` package such as `@hackcove/theme-catppuccin-mocha`, or use them on their
|
|
5
5
|
own for the default light look.
|
|
6
6
|
|
|
7
|
-
Status: `0.x`,
|
|
7
|
+
Status: `0.x`, published to npm. License: MIT.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -40,6 +40,27 @@ export const Example = () => (
|
|
|
40
40
|
- Typography roles (each pins size + weight, takes a `color` and an `as`): `Display`, `Lead`,
|
|
41
41
|
`PageHeading`, `SectionHeading`, `SubsectionHeading`, `ModalHeading`, `Subtitle`, `Overline`,
|
|
42
42
|
`Body`, `BodyStrong`, `Caption`.
|
|
43
|
+
- Controls: `Button`, `buttonClass` (the classes of a `Button`, for a router's own `<Link>`), `IconButton` (a square button holding one icon; `label` is required and becomes its `aria-label` and `title`), `Input` (a text input; `icon` adds a leading icon inside the field), `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). `Checkbox` (a native checkbox with its label; `className` goes on the label, other props on the input). `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). `Callout` (a tinted notice box; `tone` is `neutral`, `info` (default), `positive`, `warn` or `danger`; an optional `title` sits in bold above the body). `NavTabs` (a row of pill links in a `<nav>`; `items` are `{ label, href, active? }`, `label` names the landmark, the active one is filled and gets `aria-current="page"`). `navTabClass` (the classes of one tab, for a router's own `<Link>`). `ToggleChip` (an on/off filter pill; `pressed` sets `aria-pressed` and the filled look, `dot` adds a leading colour dot, and the caller toggles it in `onClick`). `FilterGroup` (a `<fieldset>` with a small uppercase `legend` over a wrapped row of its children, e.g. `ToggleChip`s; `className` goes on the fieldset).
|
|
44
|
+
|
|
45
|
+
## Palette
|
|
46
|
+
|
|
47
|
+
`palette.json` (in the repo, not in the package) lists the colour utilities the components may use,
|
|
48
|
+
by role.
|
|
49
|
+
|
|
50
|
+
| Role | For |
|
|
51
|
+
| ---------- | --------------------------------------------------------------------- |
|
|
52
|
+
| `surface` | Cards and panels: the main background. |
|
|
53
|
+
| `subtle` | A step off the surface: table stripes, hover rows. |
|
|
54
|
+
| `inset` | A step deeper: wells, chips, pressed fills. |
|
|
55
|
+
| `line` | Borders and dividers. |
|
|
56
|
+
| `ink` | Text, from primary down to muted, plus input placeholders. |
|
|
57
|
+
| `accent` | Primary actions, links, focus rings and controls; text on the accent. |
|
|
58
|
+
| `positive` | Success: fill, text and border. |
|
|
59
|
+
| `warn` | Caution: fill, text and border. |
|
|
60
|
+
| `danger` | Errors and destructive actions: fill, text and border. |
|
|
61
|
+
| `info` | Neutral notes: fill, text and border. |
|
|
62
|
+
|
|
63
|
+
A theme remaps every utility in `palette.json`; a component uses no colour utility outside it.
|
|
43
64
|
|
|
44
65
|
## Develop
|
|
45
66
|
|
package/dist/Button.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement } from "react";
|
|
2
|
+
export type ButtonVariant = "primary" | "outline" | "ghost";
|
|
3
|
+
/** The classes of a Button, for a router's own <Link> that should look like one. */
|
|
4
|
+
export declare function buttonClass(variant?: ButtonVariant): string;
|
|
5
|
+
/** A button in the primary, outline or ghost style. Defaults to `type="button"`. */
|
|
6
|
+
export declare function Button({ variant, className, ...rest }: {
|
|
7
|
+
variant?: ButtonVariant;
|
|
8
|
+
} & ComponentPropsWithoutRef<"button">): ReactElement;
|
package/dist/Button.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
const BASE = `inline-flex items-center justify-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition disabled:pointer-events-none disabled:opacity-50 ${focusRing}`;
|
|
4
|
+
const VARIANT = {
|
|
5
|
+
primary: "border-violet-600 bg-violet-600 text-violet-50 hover:bg-violet-700",
|
|
6
|
+
outline: "border-violet-600 text-violet-600 hover:bg-violet-50",
|
|
7
|
+
ghost: "border-transparent text-slate-600 hover:bg-slate-100",
|
|
8
|
+
};
|
|
9
|
+
/** The classes of a Button, for a router's own <Link> that should look like one. */
|
|
10
|
+
export function buttonClass(variant = "primary") {
|
|
11
|
+
return cx(BASE, VARIANT[variant]);
|
|
12
|
+
}
|
|
13
|
+
/** A button in the primary, outline or ghost style. Defaults to `type="button"`. */
|
|
14
|
+
export function Button({ variant = "primary", className, ...rest }) {
|
|
15
|
+
return _jsx("button", { type: "button", className: cx(buttonClass(variant), className), ...rest });
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** The colour family of a callout. */
|
|
3
|
+
export type CalloutTone = "neutral" | "info" | "positive" | "warn" | "danger";
|
|
4
|
+
/** A tinted notice box, with an optional bold title above its body. */
|
|
5
|
+
export declare function Callout({ tone, title, children, className, ...rest }: {
|
|
6
|
+
/** Defaults to "info". */
|
|
7
|
+
tone?: CalloutTone;
|
|
8
|
+
title?: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, "title">): ReactElement;
|
package/dist/Callout.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
3
|
+
const TONES = {
|
|
4
|
+
neutral: "border-slate-200 bg-slate-50 text-slate-700",
|
|
5
|
+
info: "border-blue-200 bg-blue-50 text-blue-800",
|
|
6
|
+
positive: "border-emerald-200 bg-emerald-50 text-emerald-800",
|
|
7
|
+
warn: "border-amber-200 bg-amber-50 text-amber-800",
|
|
8
|
+
danger: "border-red-200 bg-red-50 text-red-800",
|
|
9
|
+
};
|
|
10
|
+
/** A tinted notice box, with an optional bold title above its body. */
|
|
11
|
+
export function Callout({ tone = "info", title, children, className, ...rest }) {
|
|
12
|
+
return (_jsx("div", { className: cx("rounded-xl border p-4 text-sm", TONES[tone], className), ...rest, children: title ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "font-semibold", children: title }), _jsx("div", { className: "mt-1", children: children })] })) : (children) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** A native checkbox with its label. `className` goes on the label; every other prop goes on the input. */
|
|
3
|
+
export declare function Checkbox({ label, className, ...rest }: {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
} & Omit<ComponentPropsWithoutRef<"input">, "type">): ReactElement;
|
package/dist/Checkbox.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
/** A native checkbox with its label. `className` goes on the label; every other prop goes on the input. */
|
|
4
|
+
export function Checkbox({ label, className, ...rest }) {
|
|
5
|
+
return (_jsxs("label", { className: cx("inline-flex cursor-pointer items-center gap-2 text-sm text-slate-700", className), children: [_jsx("input", { type: "checkbox", className: cx("h-4 w-4 accent-violet-600", focusRing), ...rest }), label] }));
|
|
6
|
+
}
|
package/dist/Field.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
/** A label above a control, with a hint or an error line under it. An error replaces the hint. */
|
|
3
|
+
export declare function Field({ label, htmlFor, hint, error, children, className, }: {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
/** The id of the control inside; the label's htmlFor. */
|
|
6
|
+
htmlFor: string;
|
|
7
|
+
hint?: ReactNode;
|
|
8
|
+
/** Replaces the hint when set. */
|
|
9
|
+
error?: ReactNode;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): ReactElement;
|
package/dist/Field.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
3
|
+
/** A label above a control, with a hint or an error line under it. An error replaces the hint. */
|
|
4
|
+
export function Field({ label, htmlFor, hint, error, children, className, }) {
|
|
5
|
+
let note = null;
|
|
6
|
+
if (error) {
|
|
7
|
+
note = (_jsx("p", { role: "alert", className: "text-xs text-red-700", children: error }));
|
|
8
|
+
}
|
|
9
|
+
else if (hint) {
|
|
10
|
+
note = _jsx("p", { className: "text-xs text-slate-500", children: hint });
|
|
11
|
+
}
|
|
12
|
+
return (_jsxs("div", { className: cx("flex flex-col gap-1.5", className), children: [_jsx("label", { htmlFor: htmlFor, className: "text-sm font-medium text-slate-700", children: label }), children, note] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** A labelled filter section: a small uppercase legend over a wrapped row of chips (e.g. ToggleChip). Other props go on the fieldset. */
|
|
3
|
+
export declare function FilterGroup({ legend, children, className, ...rest }: {
|
|
4
|
+
legend: ReactNode;
|
|
5
|
+
} & ComponentPropsWithoutRef<"fieldset">): ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
3
|
+
/** A labelled filter section: a small uppercase legend over a wrapped row of chips (e.g. ToggleChip). Other props go on the fieldset. */
|
|
4
|
+
export function FilterGroup({ legend, children, className, ...rest }) {
|
|
5
|
+
return (_jsxs("fieldset", { className: cx("min-w-0", className), ...rest, children: [_jsx("legend", { className: "mb-2 text-xs font-medium tracking-wide text-slate-500 uppercase", children: legend }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: children })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** A square bordered button holding one icon. `label` is its accessible name and tooltip. Defaults to `type="button"`. */
|
|
3
|
+
export declare function IconButton({ label, className, children, ...rest }: {
|
|
4
|
+
label: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
} & Omit<ComponentPropsWithoutRef<"button">, "children">): ReactElement;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
const BASE = `inline-flex h-9 w-9 items-center justify-center rounded-lg border border-slate-200 text-slate-500 transition hover:bg-slate-50 disabled:pointer-events-none disabled:opacity-50 ${focusRing}`;
|
|
4
|
+
/** A square bordered button holding one icon. `label` is its accessible name and tooltip. Defaults to `type="button"`. */
|
|
5
|
+
export function IconButton({ label, className, children, ...rest }) {
|
|
6
|
+
return (_jsx("button", { type: "button", ...rest, "aria-label": label, title: label, className: cx(BASE, className), children: children }));
|
|
7
|
+
}
|
package/dist/Input.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** A text input. `icon` puts a leading icon inside the field (e.g. a search glass). */
|
|
3
|
+
export declare function Input({ icon, className, ...rest }: {
|
|
4
|
+
icon?: ReactNode;
|
|
5
|
+
} & ComponentPropsWithoutRef<"input">): ReactElement;
|
package/dist/Input.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { controlClass } from "./control.js";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
/** A text input. `icon` puts a leading icon inside the field (e.g. a search glass). */
|
|
5
|
+
export function Input({ icon, className, ...rest }) {
|
|
6
|
+
if (icon === undefined) {
|
|
7
|
+
return _jsx("input", { ...rest, className: cx(controlClass, "px-3", className) });
|
|
8
|
+
}
|
|
9
|
+
return (_jsxs("div", { className: "relative", children: [_jsx("span", { "aria-hidden": "true", className: "pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-slate-400 [&>svg]:h-4 [&>svg]:w-4", children: icon }), _jsx("input", { ...rest, className: cx(controlClass, "pr-3 pl-9", className) })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
/** One pill link in a NavTabs row. */
|
|
3
|
+
export type NavTab = {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
href: string;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** The classes of one tab, for a router's own <Link>. */
|
|
9
|
+
export declare function navTabClass(active: boolean): string;
|
|
10
|
+
/** A row of pill links where the active one is filled. Renders plain <a>; use navTabClass with a router's Link. */
|
|
11
|
+
export declare function NavTabs({ items, label, className, }: {
|
|
12
|
+
items: NavTab[];
|
|
13
|
+
/** The accessible name of the nav landmark. */
|
|
14
|
+
label: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}): ReactElement;
|
package/dist/NavTabs.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
const BASE = `rounded-lg px-3 py-1.5 transition ${focusRing}`;
|
|
4
|
+
/** The classes of one tab, for a router's own <Link>. */
|
|
5
|
+
export function navTabClass(active) {
|
|
6
|
+
return cx(BASE, active ? "bg-slate-100 font-medium text-slate-900" : "text-slate-600 hover:bg-slate-100");
|
|
7
|
+
}
|
|
8
|
+
/** A row of pill links where the active one is filled. Renders plain <a>; use navTabClass with a router's Link. */
|
|
9
|
+
export function NavTabs({ items, label, className, }) {
|
|
10
|
+
return (_jsx("nav", { "aria-label": label, className: cx("flex flex-wrap gap-1 text-sm", className), children: items.map((item) => (_jsx("a", { href: item.href, className: navTabClass(!!item.active), "aria-current": item.active ? "page" : undefined, children: item.label }, item.href))) }));
|
|
11
|
+
}
|
package/dist/Select.d.ts
ADDED
package/dist/Select.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { controlClass } from "./control.js";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
/** A native `<select>` styled like `Input`, keeping the browser's own arrow. */
|
|
5
|
+
export function Select({ className, children, ...rest }) {
|
|
6
|
+
return (_jsx("select", { ...rest, className: cx(controlClass, "px-3", className), children: children }));
|
|
7
|
+
}
|
package/dist/Textarea.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { controlClass } from "./control.js";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
/** A multi-line text input, styled like `Input`. `rows` defaults to 3. */
|
|
5
|
+
export function Textarea({ className, ...rest }) {
|
|
6
|
+
return _jsx("textarea", { rows: 3, ...rest, className: cx(controlClass, "px-3", className) });
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement } from "react";
|
|
2
|
+
/** An on/off pill button for filters. `pressed` sets its state; `dot` adds a leading colour dot. The caller toggles it in onClick. */
|
|
3
|
+
export declare function ToggleChip({ pressed, dot, className, children, ...rest }: {
|
|
4
|
+
pressed: boolean;
|
|
5
|
+
/** A CSS colour for a leading dot (e.g. a category colour); no dot when omitted. */
|
|
6
|
+
dot?: string;
|
|
7
|
+
} & ComponentPropsWithoutRef<"button">): ReactElement;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
const BASE = `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition ${focusRing}`;
|
|
4
|
+
const OFF = "border-slate-200 bg-white text-slate-600 hover:border-violet-400";
|
|
5
|
+
const ON = "border-violet-600 bg-violet-600 text-violet-50";
|
|
6
|
+
/** An on/off pill button for filters. `pressed` sets its state; `dot` adds a leading colour dot. The caller toggles it in onClick. */
|
|
7
|
+
export function ToggleChip({ pressed, dot, className, children, ...rest }) {
|
|
8
|
+
return (_jsxs("button", { type: "button", "aria-pressed": pressed, className: cx(BASE, pressed ? ON : OFF, className), ...rest, children: [dot && (_jsx("span", { "aria-hidden": "true", className: cx("h-1.5 w-1.5 rounded-full", pressed && "ring-1 ring-current"), style: { backgroundColor: dot } })), children] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Shared look of text controls; each control adds its own horizontal padding. */
|
|
2
|
+
export declare const controlClass = "w-full rounded-lg border border-slate-300 bg-white py-2 text-sm text-slate-900 placeholder:text-slate-400 transition hover:border-violet-400 disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600";
|
package/dist/control.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { focusRing } from "./cx.js";
|
|
2
|
+
/** Shared look of text controls; each control adds its own horizontal padding. */
|
|
3
|
+
export const controlClass = `w-full rounded-lg border border-slate-300 bg-white py-2 text-sm text-slate-900 placeholder:text-slate-400 transition hover:border-violet-400 disabled:opacity-50 ${focusRing}`;
|
package/dist/cx.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Join class names, dropping falsy parts. */
|
|
2
|
+
export declare function cx(...parts: Array<string | false | null | undefined>): string;
|
|
3
|
+
/** The keyboard focus ring every interactive control shares. */
|
|
4
|
+
export declare const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600";
|
package/dist/cx.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Join class names, dropping falsy parts. */
|
|
2
|
+
export function cx(...parts) {
|
|
3
|
+
return parts.filter(Boolean).join(" ");
|
|
4
|
+
}
|
|
5
|
+
/** The keyboard focus ring every interactive control shares. */
|
|
6
|
+
export const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
export { Card, EpicHeader, Empty, PageTitle, Pill, groupByEpic } from "./ui.js";
|
|
2
2
|
export { Markdown } from "./Markdown.js";
|
|
3
|
+
export { Button, buttonClass, type ButtonVariant } from "./Button.js";
|
|
4
|
+
export { Callout, type CalloutTone } from "./Callout.js";
|
|
5
|
+
export { IconButton } from "./IconButton.js";
|
|
6
|
+
export { NavTabs, navTabClass, type NavTab } from "./NavTabs.js";
|
|
7
|
+
export { ToggleChip } from "./ToggleChip.js";
|
|
8
|
+
export { FilterGroup } from "./FilterGroup.js";
|
|
9
|
+
export { Checkbox } from "./Checkbox.js";
|
|
10
|
+
export { Field } from "./Field.js";
|
|
11
|
+
export { Input } from "./Input.js";
|
|
12
|
+
export { Select } from "./Select.js";
|
|
13
|
+
export { Textarea } from "./Textarea.js";
|
|
3
14
|
export { Body, BodyStrong, Caption, Display, Lead, ModalHeading, Overline, PageHeading, SectionHeading, SubsectionHeading, Subtitle, type TypographyColor, type TypographyProps, } from "./typography.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
export { Card, EpicHeader, Empty, PageTitle, Pill, groupByEpic } from "./ui.js";
|
|
2
2
|
export { Markdown } from "./Markdown.js";
|
|
3
|
+
export { Button, buttonClass } from "./Button.js";
|
|
4
|
+
export { Callout } from "./Callout.js";
|
|
5
|
+
export { IconButton } from "./IconButton.js";
|
|
6
|
+
export { NavTabs, navTabClass } from "./NavTabs.js";
|
|
7
|
+
export { ToggleChip } from "./ToggleChip.js";
|
|
8
|
+
export { FilterGroup } from "./FilterGroup.js";
|
|
9
|
+
export { Checkbox } from "./Checkbox.js";
|
|
10
|
+
export { Field } from "./Field.js";
|
|
11
|
+
export { Input } from "./Input.js";
|
|
12
|
+
export { Select } from "./Select.js";
|
|
13
|
+
export { Textarea } from "./Textarea.js";
|
|
3
14
|
export { Body, BodyStrong, Caption, Display, Lead, ModalHeading, Overline, PageHeading, SectionHeading, SubsectionHeading, Subtitle, } from "./typography.js";
|
package/dist/typography.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
2
3
|
/** Colour token → slate/violet utility (remapped to Catppuccin Mocha in dark mode by theme.css). */
|
|
3
4
|
const COLOR = {
|
|
4
5
|
default: "text-slate-900", // Catppuccin text #cdd6f4
|
|
@@ -7,9 +8,6 @@ const COLOR = {
|
|
|
7
8
|
muted: "text-slate-400", // Catppuccin overlay2 #9399b2
|
|
8
9
|
brand: "text-violet-600", // Catppuccin mauve #cba6f7
|
|
9
10
|
};
|
|
10
|
-
function cx(...parts) {
|
|
11
|
-
return parts.filter(Boolean).join(" ");
|
|
12
|
-
}
|
|
13
11
|
/**
|
|
14
12
|
* Build a semantic component that pins `variantClass` (size + weight + tracking)
|
|
15
13
|
* and a default colour. The scale/weight for each role therefore lives in exactly
|
package/dist/ui.d.ts
CHANGED
|
@@ -3,9 +3,12 @@ export declare function PageTitle({ title, subtitle }: {
|
|
|
3
3
|
title: string;
|
|
4
4
|
subtitle?: string;
|
|
5
5
|
}): import("react").JSX.Element;
|
|
6
|
-
|
|
6
|
+
/** A bordered panel with an optional title and count; `as` picks the element (default `section`). */
|
|
7
|
+
export declare function Card({ title, count, as: Tag, className, children, }: {
|
|
7
8
|
title?: string;
|
|
8
9
|
count?: number;
|
|
10
|
+
as?: "section" | "article" | "div";
|
|
11
|
+
className?: string;
|
|
9
12
|
children: ReactNode;
|
|
10
13
|
}): import("react").JSX.Element;
|
|
11
14
|
/** A coloured epic header row: a dot in the epic's colour + its label. */
|
|
@@ -13,9 +16,11 @@ export declare function EpicHeader({ color, label }: {
|
|
|
13
16
|
color: string;
|
|
14
17
|
label: string;
|
|
15
18
|
}): import("react").JSX.Element;
|
|
16
|
-
|
|
19
|
+
/** A small tinted label; `dot` adds a leading dot in the pill's own text colour. */
|
|
20
|
+
export declare function Pill({ children, tone, dot, }: {
|
|
17
21
|
children: ReactNode;
|
|
18
22
|
tone?: "slate" | "amber" | "emerald" | "blue" | "red";
|
|
23
|
+
dot?: boolean;
|
|
19
24
|
}): import("react").JSX.Element;
|
|
20
25
|
export declare function Empty({ children }: {
|
|
21
26
|
children: ReactNode;
|
package/dist/ui.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
2
3
|
// Small presentational primitives written against Tailwind's slate-based class vocabulary
|
|
3
4
|
// (remapped to Catppuccin Mocha in dark mode by theme.css).
|
|
4
5
|
export function PageTitle({ title, subtitle }) {
|
|
5
6
|
return (_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight text-slate-900", children: title }), subtitle && _jsx("p", { className: "mt-1 text-sm text-slate-500", children: subtitle })] }));
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/** A bordered panel with an optional title and count; `as` picks the element (default `section`). */
|
|
9
|
+
export function Card({ title, count, as: Tag = "section", className, children, }) {
|
|
10
|
+
return (_jsxs(Tag, { className: cx("rounded-xl border border-slate-200/70 bg-white p-5 shadow-sm", className), children: [title && (_jsxs("div", { className: "mb-3 flex items-baseline gap-2", children: [_jsx("h2", { className: "text-xs font-semibold uppercase tracking-wide text-slate-400", children: title }), count !== undefined && (_jsx("span", { className: "text-xs font-medium text-slate-400", children: count }))] })), children] }));
|
|
9
11
|
}
|
|
10
12
|
/** A coloured epic header row: a dot in the epic's colour + its label. */
|
|
11
13
|
export function EpicHeader({ color, label }) {
|
|
12
14
|
return (_jsxs("div", { className: "mb-1.5 mt-3 flex items-center gap-2 first:mt-0", children: [_jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { backgroundColor: color } }), _jsx("span", { className: "text-sm font-semibold text-slate-700", children: label })] }));
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
/** A small tinted label; `dot` adds a leading dot in the pill's own text colour. */
|
|
17
|
+
export function Pill({ children, tone = "slate", dot = false, }) {
|
|
15
18
|
const tones = {
|
|
16
19
|
slate: "bg-slate-100 text-slate-600",
|
|
17
20
|
amber: "bg-amber-50 text-amber-700",
|
|
@@ -19,7 +22,7 @@ export function Pill({ children, tone = "slate", }) {
|
|
|
19
22
|
blue: "bg-blue-50 text-blue-700",
|
|
20
23
|
red: "bg-red-50 text-red-900",
|
|
21
24
|
};
|
|
22
|
-
return (
|
|
25
|
+
return (_jsxs("span", { className: cx("rounded-md px-1.5 py-0.5 text-xs font-medium", tones[tone], dot && "inline-flex items-center gap-1"), children: [dot && _jsx("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 rounded-full bg-current" }), children] }));
|
|
23
26
|
}
|
|
24
27
|
export function Empty({ children }) {
|
|
25
28
|
return _jsx("p", { className: "text-sm text-slate-400", children: children });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hackcove/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
"license": "MIT",
|
|
6
6
|
"repository": {
|