@w5-ui/react 0.2.1 → 0.5.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 +118 -4
- package/package.json +2 -2
- package/src/components/AppLayout.tsx +38 -0
- package/src/components/AppShell.tsx +41 -49
- package/src/components/Body.tsx +32 -0
- package/src/components/BodyLg.tsx +29 -0
- package/src/components/Caption.tsx +29 -0
- package/src/components/Card.tsx +13 -0
- package/src/components/CardMore.tsx +22 -0
- package/src/components/Code.tsx +27 -0
- package/src/components/ContextMenu.tsx +4 -0
- package/src/components/Delta.tsx +29 -0
- package/src/components/Display1.tsx +33 -0
- package/src/components/Display2.tsx +32 -0
- package/src/components/Eyebrow.tsx +28 -0
- package/src/components/FieldStack.tsx +19 -0
- package/src/components/FormActions.tsx +20 -0
- package/src/components/FormRow.tsx +19 -0
- package/src/components/Grid.tsx +21 -0
- package/src/components/H1.tsx +32 -0
- package/src/components/H2.tsx +32 -0
- package/src/components/H3.tsx +32 -0
- package/src/components/H4.tsx +31 -0
- package/src/components/Label.tsx +30 -0
- package/src/components/Mac.tsx +25 -0
- package/src/components/Modal.tsx +16 -2
- package/src/components/Mono.tsx +31 -0
- package/src/components/Muted.tsx +27 -0
- package/src/components/NameCell.tsx +45 -0
- package/src/components/NotifDot.tsx +24 -0
- package/src/components/PageHeader.tsx +20 -0
- package/src/components/SrOnly.tsx +25 -0
- package/src/components/StatNumber.tsx +25 -0
- package/src/components/StatusRing.tsx +38 -0
- package/src/components/Submeta.tsx +23 -0
- package/src/components/TablePager.tsx +27 -0
- package/src/components/TableShell.tsx +23 -0
- package/src/components/TopbarActions.tsx +70 -0
- package/src/components/Unit.tsx +25 -0
- package/src/index.ts +33 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface FormRowProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Two-column form grid: `1fr 1fr` with the canonical 14 px gap and 6 px top
|
|
9
|
+
* offset that every Settings card uses. Typed equivalent of
|
|
10
|
+
* `<div className="form-row">` from `@w5-ui/tokens/dashboard.css`. Use inside
|
|
11
|
+
* wider `<Card>` tiles for dense editors.
|
|
12
|
+
*/
|
|
13
|
+
export function FormRow({ className = '', children, ...rest }: FormRowProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div className={`form-row ${className}`.trim()} {...rest}>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface GridProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The canonical 12-column page grid: `repeat(12, 1fr)` with a 12 px gap and
|
|
9
|
+
* `16px 24px` padding, reserving room for the bottom panel when present.
|
|
10
|
+
*
|
|
11
|
+
* Typed equivalent of `<div className="grid">` from `@w5-ui/tokens/dashboard.css`.
|
|
12
|
+
* Use as a direct child of the scrolling `.content` region; place `<Card>` /
|
|
13
|
+
* `<Stat>` tiles inside with `span={4 | 6 | 8 | 12}`.
|
|
14
|
+
*/
|
|
15
|
+
export function Grid({ className = '', children, ...rest }: GridProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div className={`grid ${className}`.trim()} {...rest}>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface H1Props extends HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Page title - the 38 px / bold / `--lh-snug` rhythm with
|
|
9
|
+
* `--tracking-display` (-0.02em) used for the top-of-page heading on
|
|
10
|
+
* marketing surfaces (login, onboarding splash, top-level landing).
|
|
11
|
+
* Renders `<h1 class="dash-ui-h1">` so the `.dash-ui-h1` rule in
|
|
12
|
+
* `@w5-ui/tokens/tokens.css` pins the canonical size, weight,
|
|
13
|
+
* line-height, letter-spacing, and `--text-1` colour. Stops userland
|
|
14
|
+
* reaching for a one-off `<h1 style={{ fontSize: 36 }}>` (or worse,
|
|
15
|
+
* leaving an unstyled browser default `<h1>` at 32 px / 700 / 1.2
|
|
16
|
+
* with no negative tracking, which is the documented "headings break
|
|
17
|
+
* awkwardly across two lines" drift fixed by `--tracking-display`).
|
|
18
|
+
*
|
|
19
|
+
* Typed equivalent of `<h1 className="dash-ui-h1">`. This is the
|
|
20
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
21
|
+
* Use `<H1>` for marketing-grade page titles. In-app pages should reach
|
|
22
|
+
* for `<PageHeader>` (which already styles the page title) and use
|
|
23
|
+
* `<H2>` for section heads, `<H3>` for sub-sections, `<H4>` for card
|
|
24
|
+
* titles.
|
|
25
|
+
*/
|
|
26
|
+
export function H1({ className = '', children, ...rest }: H1Props) {
|
|
27
|
+
return (
|
|
28
|
+
<h1 className={`dash-ui-h1 ${className}`.trim()} {...rest}>
|
|
29
|
+
{children}
|
|
30
|
+
</h1>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface H2Props extends HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Page section heading - the 30 px / semibold / `--lh-snug` rhythm used
|
|
9
|
+
* for top-of-section labels on a dashboard page (above a grid of cards,
|
|
10
|
+
* above a table, above a settings group). Renders
|
|
11
|
+
* `<h2 class="dash-ui-h2">` so the `.dash-ui-h2` rule in
|
|
12
|
+
* `@w5-ui/tokens/tokens.css` pins the canonical size, weight,
|
|
13
|
+
* line-height, and `--text-1` colour. Stops userland reaching for a
|
|
14
|
+
* one-off `<h2 style={{ fontSize: 30, fontWeight: 600 }}>` (or worse,
|
|
15
|
+
* leaving an unstyled browser default `<h2>` at ~24 px with 700 weight
|
|
16
|
+
* and 1.2 line-height, which is the documented "section headings feel
|
|
17
|
+
* cramped on one line" drift).
|
|
18
|
+
*
|
|
19
|
+
* Typed equivalent of `<h2 className="dash-ui-h2">`. This is the
|
|
20
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
21
|
+
* Use `<H2>` for the page section head ("Devices", "Wi-Fi", "Settings")
|
|
22
|
+
* sitting above a content block. For card titles prefer `<H4>` (or just
|
|
23
|
+
* `<Card title="…">`, which injects `.dash-ui-h4` for you); for full
|
|
24
|
+
* page titles in marketing pages prefer `<H1>`.
|
|
25
|
+
*/
|
|
26
|
+
export function H2({ className = '', children, ...rest }: H2Props) {
|
|
27
|
+
return (
|
|
28
|
+
<h2 className={`dash-ui-h2 ${className}`.trim()} {...rest}>
|
|
29
|
+
{children}
|
|
30
|
+
</h2>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface H3Props extends HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sub-section heading - the 24 px / semibold / `--lh-snug` rhythm used
|
|
9
|
+
* for sub-section labels on a dashboard page (a heading inside a card
|
|
10
|
+
* region, a settings sub-group, a divider above a secondary table).
|
|
11
|
+
* Renders `<h3 class="dash-ui-h3">` so the `.dash-ui-h3` rule in
|
|
12
|
+
* `@w5-ui/tokens/tokens.css` pins the canonical size, weight,
|
|
13
|
+
* line-height, and `--text-1` colour. Stops userland reaching for a
|
|
14
|
+
* one-off `<h3 style={{ fontSize: 24, fontWeight: 600 }}>` (or worse,
|
|
15
|
+
* leaving an unstyled browser default `<h3>` at ~18.7 px with 700
|
|
16
|
+
* weight and 1.2 line-height, which is the documented "sub-section
|
|
17
|
+
* headings drift away from the reference" case).
|
|
18
|
+
*
|
|
19
|
+
* Typed equivalent of `<h3 className="dash-ui-h3">`. This is the
|
|
20
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
21
|
+
* Use `<H3>` for a sub-section head sitting below an `<H2>` page
|
|
22
|
+
* section head. For card titles prefer `<H4>` (or just
|
|
23
|
+
* `<Card title="…">`, which injects `.dash-ui-h4` for you); for page
|
|
24
|
+
* section heads prefer `<H2>`.
|
|
25
|
+
*/
|
|
26
|
+
export function H3({ className = '', children, ...rest }: H3Props) {
|
|
27
|
+
return (
|
|
28
|
+
<h3 className={`dash-ui-h3 ${className}`.trim()} {...rest}>
|
|
29
|
+
{children}
|
|
30
|
+
</h3>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface H4Props extends HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Card / tile title - the 18 px / semibold / `--lh-snug` rhythm used
|
|
9
|
+
* for the heading that sits inside a card, a stat tile, or a settings
|
|
10
|
+
* sub-panel. Renders `<h4 class="dash-ui-h4">` so the `.dash-ui-h4`
|
|
11
|
+
* rule in `@w5-ui/tokens/tokens.css` pins the canonical size, weight,
|
|
12
|
+
* line-height, and `--text-1` colour. Stops userland reaching for a
|
|
13
|
+
* one-off `<h3 style={{ fontSize: 20 }}>` or `text-xl` (the documented
|
|
14
|
+
* "card title size doesn't match the reference" case), and stops them
|
|
15
|
+
* leaving an unstyled browser default `<h4>` at ~13.3 px / 700 / 1.2
|
|
16
|
+
* which reads as caption text instead of a title.
|
|
17
|
+
*
|
|
18
|
+
* Typed equivalent of `<h4 className="dash-ui-h4">`. This is the
|
|
19
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
20
|
+
* Use `<H4>` when you build a card-like tile by hand and need the
|
|
21
|
+
* canonical 18 / 600 title. If you reach for `<Card title="…">` you
|
|
22
|
+
* get the same class injected for you - prefer that. For page section
|
|
23
|
+
* heads prefer `<H2>`; for sub-section heads prefer `<H3>`.
|
|
24
|
+
*/
|
|
25
|
+
export function H4({ className = '', children, ...rest }: H4Props) {
|
|
26
|
+
return (
|
|
27
|
+
<h4 className={`dash-ui-h4 ${className}`.trim()} {...rest}>
|
|
28
|
+
{children}
|
|
29
|
+
</h4>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface LabelProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Small categorical label - the line above a KPI number ("Active devices"),
|
|
9
|
+
* the cell heading on a hand-rolled form row, the tag on a definition list
|
|
10
|
+
* term, anywhere a 13 px / medium-weight tag belongs without the full form
|
|
11
|
+
* field machinery. Renders `<span class="dash-ui-label">` so the
|
|
12
|
+
* `.dash-ui-label` rule in `@w5-ui/tokens/tokens.css` applies the canonical
|
|
13
|
+
* 13 px / 500 weight, 1.25 line-height, and `--text-2` colour. Stops
|
|
14
|
+
* userland reaching for a one-off `font-size: 13px; font-weight: 500;` duo
|
|
15
|
+
* on every KPI label or hand-rolled form caption.
|
|
16
|
+
*
|
|
17
|
+
* Typed equivalent of `<span className="dash-ui-label">`. This is the
|
|
18
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
19
|
+
* For real form fields prefer `<Field label="…">` - it wires `htmlFor`,
|
|
20
|
+
* required, hint, and error semantics for you. For KPI tiles prefer the
|
|
21
|
+
* `<Stat>` label slot. Reach for `<Label>` only when hand-rolling a tile
|
|
22
|
+
* or row where the higher-level component doesn't fit.
|
|
23
|
+
*/
|
|
24
|
+
export function Label({ className = '', children, ...rest }: LabelProps) {
|
|
25
|
+
return (
|
|
26
|
+
<span className={`dash-ui-label ${className}`.trim()} {...rest}>
|
|
27
|
+
{children}
|
|
28
|
+
</span>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface MacProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inline monospace label for MAC addresses, IPs, SKUs, serial numbers and
|
|
9
|
+
* other fixed-width identifiers that read inside a table cell or a property
|
|
10
|
+
* row. Renders a `<span class="mac">` so the `.mac` rule in
|
|
11
|
+
* `@w5-ui/tokens/dashboard.css` applies the canonical JetBrains Mono family,
|
|
12
|
+
* 12 px size, and muted colour. Stops userland reaching for inline
|
|
13
|
+
* `font-family` / `font-size` overrides on bare identifiers.
|
|
14
|
+
*
|
|
15
|
+
* Typed equivalent of `<span className="mac">` (and `<td className="mac">`
|
|
16
|
+
* inside a table — apply the class directly to the `<td>` there, or wrap the
|
|
17
|
+
* cell contents with `<Mac>` when the cell carries other markup too).
|
|
18
|
+
*/
|
|
19
|
+
export function Mac({ className = '', children, ...rest }: MacProps) {
|
|
20
|
+
return (
|
|
21
|
+
<span className={`mac ${className}`.trim()} {...rest}>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
)
|
|
25
|
+
}
|
package/src/components/Modal.tsx
CHANGED
|
@@ -2,18 +2,32 @@ import { useEffect, useId, useRef, type ReactNode } from 'react'
|
|
|
2
2
|
import { CloseIcon } from '../icons.js'
|
|
3
3
|
import { IconButton } from './Button.js'
|
|
4
4
|
|
|
5
|
+
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
6
|
+
|
|
5
7
|
export interface ModalProps {
|
|
6
8
|
open: boolean
|
|
7
9
|
title: string
|
|
10
|
+
/** Panel width. `md` (520px) is the default; larger sizes suit
|
|
11
|
+
* data-dense or editor-style dialogs. Always capped at 90vw. */
|
|
12
|
+
size?: ModalSize
|
|
8
13
|
onClose: () => void
|
|
9
14
|
children?: ReactNode
|
|
10
15
|
footer?: ReactNode
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
/** Width modifier appended to `.modal`; `md` is the base rule. */
|
|
19
|
+
const MODAL_SIZE: Record<ModalSize, string> = {
|
|
20
|
+
sm: 'modal--sm',
|
|
21
|
+
md: '',
|
|
22
|
+
lg: 'modal--lg',
|
|
23
|
+
xl: 'modal--xl',
|
|
24
|
+
'2xl': 'modal--2xl',
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
const FOCUSABLE =
|
|
14
28
|
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
15
29
|
|
|
16
|
-
export function Modal({ open, title, onClose, children, footer }: ModalProps) {
|
|
30
|
+
export function Modal({ open, title, size = 'md', onClose, children, footer }: ModalProps) {
|
|
17
31
|
const titleId = useId()
|
|
18
32
|
const modalRef = useRef<HTMLDivElement>(null)
|
|
19
33
|
const downOnBackdropRef = useRef(false)
|
|
@@ -67,7 +81,7 @@ export function Modal({ open, title, onClose, children, footer }: ModalProps) {
|
|
|
67
81
|
>
|
|
68
82
|
<div
|
|
69
83
|
ref={modalRef}
|
|
70
|
-
className=
|
|
84
|
+
className={`modal ${MODAL_SIZE[size]}`.trim()}
|
|
71
85
|
role="dialog"
|
|
72
86
|
aria-modal="true"
|
|
73
87
|
aria-labelledby={titleId}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface MonoProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inline monospace identifier for MAC addresses, IPs, hex IDs, ports,
|
|
9
|
+
* firmware versions, config keys, and other machine-shaped values that
|
|
10
|
+
* read inside body copy or a property row. Renders a
|
|
11
|
+
* `<span class="dash-ui-mono">` so the `.dash-ui-mono` rule in
|
|
12
|
+
* `@w5-ui/tokens/tokens.css` applies the canonical JetBrains Mono family,
|
|
13
|
+
* 13 px size, regular weight, 1.4 line-height, and `--text-2` colour. The
|
|
14
|
+
* class also enables `font-variant-numeric: tabular-nums` so a column of
|
|
15
|
+
* MACs / IPs / versions aligns vertically. Stops userland reaching for
|
|
16
|
+
* bare `font-family: monospace` (which falls back to a serif Courier with
|
|
17
|
+
* proportional digits) or one-off `font-size` overrides on identifiers.
|
|
18
|
+
*
|
|
19
|
+
* Typed equivalent of `<span className="dash-ui-mono">`. This is the
|
|
20
|
+
* userland-facing semantic class documented in **Foundations / Type**.
|
|
21
|
+
* Prefer `<Mono>` for new userland code. Reach for `<Mac>` only when you
|
|
22
|
+
* need the dashboard's denser 12 px `.mac` variant (table cells in the
|
|
23
|
+
* reference Devices / Clients pages).
|
|
24
|
+
*/
|
|
25
|
+
export function Mono({ className = '', children, ...rest }: MonoProps) {
|
|
26
|
+
return (
|
|
27
|
+
<span className={`dash-ui-mono ${className}`.trim()} {...rest}>
|
|
28
|
+
{children}
|
|
29
|
+
</span>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface MutedProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inline span that drops a phrase to the tertiary text colour
|
|
9
|
+
* (`--text-3`). Renders `<span class="t-muted">` so the `.t-muted` rule in
|
|
10
|
+
* `@w5-ui/tokens/dashboard.css` applies the canonical muted colour used by
|
|
11
|
+
* `.submeta`, `.stat .unit` and the `.card h3` title chrome. Use for
|
|
12
|
+
* "X active" counters next to an icon, caption-style metadata sitting
|
|
13
|
+
* beside a primary label, or any sub-line that would otherwise tempt
|
|
14
|
+
* userland to reach for a one-off `color: #...` override.
|
|
15
|
+
*
|
|
16
|
+
* Typed equivalent of `<span className="t-muted">`. The contrast pair is
|
|
17
|
+
* AA against every motif background (verified for `--text-3` over
|
|
18
|
+
* `--depthBg-0`/`--depthBg-1`/`--depthBg-2` in both dark and light
|
|
19
|
+
* motifs).
|
|
20
|
+
*/
|
|
21
|
+
export function Muted({ className = '', children, ...rest }: MutedProps) {
|
|
22
|
+
return (
|
|
23
|
+
<span className={`t-muted ${className}`.trim()} {...rest}>
|
|
24
|
+
{children}
|
|
25
|
+
</span>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface NameCellProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
/**
|
|
5
|
+
* Square 28 px avatar / initial that sits to the left of the name. Pass a
|
|
6
|
+
* letter (`r[0]`), an emoji, or a small SVG. Rendered inside a
|
|
7
|
+
* `<span class="nc-thumb">` so the canonical thumbnail rule applies.
|
|
8
|
+
* Omit if the row has no leading affordance.
|
|
9
|
+
*/
|
|
10
|
+
thumb?: ReactNode
|
|
11
|
+
/**
|
|
12
|
+
* Extra `className` for the inner `.nc-thumb` (e.g. `'cam'` for the
|
|
13
|
+
* camera gradient variant). Ignored when `thumb` is not supplied.
|
|
14
|
+
*/
|
|
15
|
+
thumbClassName?: string
|
|
16
|
+
children?: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* First-column row in a list / table: a 28 px square thumbnail or initial
|
|
21
|
+
* followed by the device / client / alert name. Renders the canonical
|
|
22
|
+
* `<div class="name-cell"><span class="nc-thumb">…</span>…</div>` markup so
|
|
23
|
+
* the `.name-cell` and `.nc-thumb` rules from `@w5-ui/tokens/dashboard.css`
|
|
24
|
+
* apply (28 px thumb, 10 px gap, white name colour, JetBrains Mono initial).
|
|
25
|
+
* Stops userland reaching for ad-hoc flex / padding / colour overrides on the
|
|
26
|
+
* first column.
|
|
27
|
+
*
|
|
28
|
+
* Typed equivalent of the manual `<div className="name-cell"><span
|
|
29
|
+
* className="nc-thumb">{x}</span>{name}</div>` pattern used across the
|
|
30
|
+
* Dashboard, Devices, Wi-Fi, and Alarms reference pages.
|
|
31
|
+
*/
|
|
32
|
+
export function NameCell({
|
|
33
|
+
className = '',
|
|
34
|
+
thumb,
|
|
35
|
+
thumbClassName = '',
|
|
36
|
+
children,
|
|
37
|
+
...rest
|
|
38
|
+
}: NameCellProps) {
|
|
39
|
+
return (
|
|
40
|
+
<div className={`name-cell ${className}`.trim()} {...rest}>
|
|
41
|
+
{thumb !== undefined && <span className={`nc-thumb ${thumbClassName}`.trim()}>{thumb}</span>}
|
|
42
|
+
{children}
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface NotifDotProps extends HTMLAttributes<HTMLSpanElement> {}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tiny red dot that sits on top of a bell / mail / inbox icon to flag unread
|
|
7
|
+
* activity. Renders `<span aria-hidden class="topbar-notif-dot">` so the
|
|
8
|
+
* `.topbar-notif-dot` rule in `@w5-ui/tokens/dashboard.css` paints the canonical
|
|
9
|
+
* 6 px circle with the danger colour and a neutral-10 ring (the ring is what
|
|
10
|
+
* keeps the dot legible over the deep topbar background).
|
|
11
|
+
*
|
|
12
|
+
* The dot is presentational — wrap your bell `<IconButton>` and put the live
|
|
13
|
+
* count in its `aria-label` (e.g. `aria-label="Notifications, 3 new"`) so
|
|
14
|
+
* screen readers announce the number while sighted users see the indicator.
|
|
15
|
+
* The hosting button must be `position: relative` for the dot to anchor in the
|
|
16
|
+
* top-right corner.
|
|
17
|
+
*
|
|
18
|
+
* Typed equivalent of `<span aria-hidden className="topbar-notif-dot" />`.
|
|
19
|
+
* Use when userland slots a custom `<Topbar actions>` block and needs to keep
|
|
20
|
+
* the unread indicator wired up without copying the class name.
|
|
21
|
+
*/
|
|
22
|
+
export function NotifDot({ className = '', ...rest }: NotifDotProps) {
|
|
23
|
+
return <span aria-hidden="true" className={`topbar-notif-dot ${className}`.trim()} {...rest} />
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface PageHeaderProps {
|
|
4
|
+
title: ReactNode
|
|
5
|
+
actions?: ReactNode
|
|
6
|
+
sticky?: boolean
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function PageHeader({ title, actions, sticky = true, className = '' }: PageHeaderProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className={`ph-bar ${className}`.trim()}
|
|
14
|
+
style={sticky ? undefined : { position: 'static' }}
|
|
15
|
+
>
|
|
16
|
+
<h2 className="ph-title">{title}</h2>
|
|
17
|
+
{actions && <div className="ph-actions">{actions}</div>}
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface SrOnlyProps extends HTMLAttributes<HTMLSpanElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Visually-hidden text that stays available to assistive tech. Renders a
|
|
9
|
+
* `<span class="sr-only">` so the `.sr-only` rule in
|
|
10
|
+
* `@w5-ui/tokens/dashboard.css` clips it from the visual layout while keeping
|
|
11
|
+
* it announceable by screen readers. Use it to name regions that the design
|
|
12
|
+
* intentionally leaves unlabelled, the way the reference Tables recipe wires
|
|
13
|
+
* `<caption className="sr-only">` onto every `<table>` (WCAG 1.3.1).
|
|
14
|
+
*
|
|
15
|
+
* Typed equivalent of `<span className="sr-only">`. Apply the class directly
|
|
16
|
+
* to a `<caption>` / `<label>` when the whole element is the hidden text, or
|
|
17
|
+
* wrap an inline phrase with `<SrOnly>` when it sits alongside visible markup.
|
|
18
|
+
*/
|
|
19
|
+
export function SrOnly({ className = '', children, ...rest }: SrOnlyProps) {
|
|
20
|
+
return (
|
|
21
|
+
<span className={`sr-only ${className}`.trim()} {...rest}>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface StatNumberProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hero-number row inside a `<Card>`: the big tabular-nums value (optionally
|
|
9
|
+
* followed by a trailing `<Unit>`). Renders a `<div class="stat">` so the
|
|
10
|
+
* `.stat` rule in `@w5-ui/tokens/dashboard.css` sets the canonical 30 px size,
|
|
11
|
+
* semibold weight, tabular figures, and primary text colour. Stops userland
|
|
12
|
+
* reaching for inline `font-size` / `font-weight` overrides on bare numbers.
|
|
13
|
+
*
|
|
14
|
+
* Typed equivalent of `<div className="stat">`; pair with `<Unit>` for the
|
|
15
|
+
* suffix label, and `<Submeta>` for the muted line underneath. `<Stat>`
|
|
16
|
+
* already wires all three internally — reach for `<StatNumber>` when you
|
|
17
|
+
* compose `.stat` by hand (e.g. number above a chart inside a `<Card>`).
|
|
18
|
+
*/
|
|
19
|
+
export function StatNumber({ className = '', children, ...rest }: StatNumberProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={`stat ${className}`.trim()} {...rest}>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
export type StatusRingStatus = 'ok' | 'warn' | 'danger' | 'neutral'
|
|
4
|
+
|
|
5
|
+
export interface StatusRingProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
/** Health colour of the ring. Defaults to `'ok'` (success green). */
|
|
7
|
+
status?: StatusRingStatus
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MODIFIER: Record<StatusRingStatus, string> = {
|
|
11
|
+
ok: '',
|
|
12
|
+
warn: 'status-ring--warn',
|
|
13
|
+
danger: 'status-ring--danger',
|
|
14
|
+
neutral: 'status-ring--neutral',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pulsing status indicator: a soft translucent ring around a solid coloured
|
|
19
|
+
* inner dot. Renders `<span aria-hidden class="status-ring">` so the
|
|
20
|
+
* `.status-ring` rule in `@w5-ui/tokens/dashboard.css` paints the canonical
|
|
21
|
+
* 18 px ring + 8 px inner dot without userland having to hand-roll the two
|
|
22
|
+
* layers.
|
|
23
|
+
*
|
|
24
|
+
* The `status` prop swaps the ring + dot colour: `'ok'` (default, success
|
|
25
|
+
* green), `'warn'` (amber), `'danger'` (red), `'neutral'` (grey). Use it to
|
|
26
|
+
* drive a live-health signal next to the site name in `<Topbar>` or on a
|
|
27
|
+
* tunnel / session / device row.
|
|
28
|
+
*
|
|
29
|
+
* Decorative — the wrapper sets `aria-hidden`; pair with a label so screen
|
|
30
|
+
* readers announce the state. Typed equivalent of
|
|
31
|
+
* `<span aria-hidden className="status-ring status-ring--<status>" />`. Stops
|
|
32
|
+
* userland copying the class name into bespoke status rows and silently
|
|
33
|
+
* drifting when the ring colour / size tokens change.
|
|
34
|
+
*/
|
|
35
|
+
export function StatusRing({ className = '', status = 'ok', ...rest }: StatusRingProps) {
|
|
36
|
+
const cls = ['status-ring', MODIFIER[status], className].filter(Boolean).join(' ')
|
|
37
|
+
return <span aria-hidden="true" className={cls} {...rest} />
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface SubmetaProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Secondary one-line caption inside a `<Card>` tile: the muted row that sits
|
|
9
|
+
* under the headline `.stat` number and carries supporting metrics, deltas, or
|
|
10
|
+
* status. Carries the canonical 12 px font size, `--text-2` colour, and the
|
|
11
|
+
* `↑` / `↓` separator rhythm the Dashboard reference tiles use.
|
|
12
|
+
*
|
|
13
|
+
* Typed equivalent of `<div className="submeta">` from
|
|
14
|
+
* `@w5-ui/tokens/dashboard.css`. Pair with `.delta-up` / `.delta-down` spans
|
|
15
|
+
* for trend text so screen readers announce the direction.
|
|
16
|
+
*/
|
|
17
|
+
export function Submeta({ className = '', children, ...rest }: SubmetaProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={`submeta ${className}`.trim()} {...rest}>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface TablePagerProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sibling row that sits directly under a `<TableShell>` (or
|
|
9
|
+
* `<div className="table-shell">`) and carries the rows-per-page control on
|
|
10
|
+
* the left and the `<Pagination>` nav on the right. Renders the canonical
|
|
11
|
+
* `<div class="table-pager">` markup so the flex / space-between / 12 px top
|
|
12
|
+
* margin from `@w5-ui/tokens/dashboard.css` applies and userland drops the
|
|
13
|
+
* inline `style={{ display: 'flex', justifyContent: 'space-between', … }}`
|
|
14
|
+
* wrapper that every reference page used to ship.
|
|
15
|
+
*
|
|
16
|
+
* Typed equivalent of the manual `<div className="table-pager">` pattern
|
|
17
|
+
* documented in **Foundations / Tables**. Render it as a sibling of
|
|
18
|
+
* `<TableShell>` — never inside it — so the focus rings on the pager buttons
|
|
19
|
+
* are not clipped by `overflow: hidden`.
|
|
20
|
+
*/
|
|
21
|
+
export function TablePager({ className = '', children, ...rest }: TablePagerProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div className={`table-pager ${className}`.trim()} {...rest}>
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface TableShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children?: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rounded, bordered wrapper for any full-width `<table>` on a page. Carries the
|
|
9
|
+
* 8 px radius, `--neutral-09` background, single 1 px border and `overflow:
|
|
10
|
+
* hidden` clip so userland tables read like the reference Devices / Clients
|
|
11
|
+
* pages without inline `border` / `borderRadius` styles.
|
|
12
|
+
*
|
|
13
|
+
* Typed equivalent of `<div className="table-shell">` from
|
|
14
|
+
* `@w5-ui/tokens/dashboard.css`. Render the `<Pagination>` row as a sibling
|
|
15
|
+
* below, not inside, so its focus rings aren't clipped.
|
|
16
|
+
*/
|
|
17
|
+
export function TableShell({ className = '', children, ...rest }: TableShellProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={`table-shell ${className}`.trim()} {...rest}>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|