Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ Versioning follows the policy in [CONTRIBUTING.md](CONTRIBUTING.md#versioning).

## [Unreleased]

### Added

- **`Board` draws an item's title row and description.** `BoardItem` takes `title`
and `description`, each a string or a node: a string title renders as a
level-5 heading, a string description as text, a node (a `BoardItemTitle`
with a tooltip and actions, say) as is. The drag handle sits in the inline
padding on the title row's first line, where it stays when the title wraps
or grows; without a title there is no header row and the description takes
the whole item. `itemPadding` sets every item's padding, a number (px) or
a CSS length for every side or one per side (default `--spacing-5` top
and bottom, `--spacing-8` left and right); the CSS knobs behind it are
`--board-item-padding-top` / `-right` / `-bottom` / `-left`.

### Changed

- **`BoardItemTitle` is a `Board` item's `title` node**, no longer a
self-padded, sticky row: the board pads and places it.

### Removed

- **`Board`'s `renderItem`**, with `BoardItemActions`, `removeItem`,
`removedItem`, `liveAnnouncementItemRemoved` and the `uic.Board.itemRemoved`
catalog key. An item is its `title` and `description`; removing one is the
consumer's, by passing `items` without it.

## [0.2.0-alpha.19]

Astryx 0.6.5 → 0.7.0 (`@astryxdesign/lab` 0.7.0-canary.97a0d31). Astryx's
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ import { Modal } from "@lablup/ui-common/Modal";
| `DeleteConfirmModal` | Confirms a deletion, with typed confirmation when needed | `/components/DeleteConfirmModal` |
| `StepNumberInput`, `NumberStepper` | A number field that steps along a list of values | `/components/StepNumberInput` |
| `Board` | A dashboard grid of movable, resizable items | `/components/Board` |
| `BoardItemTitle` | A dashboard panel's sticky title row | `/components/BoardItemTitle` |
| `BoardItemTitle` | A Board item's title row: heading, tooltip, actions | `/components/BoardItemTitle` |
| `Statistic` | A metric with a caption, a large value and a notched bar | `/components/Statistic` |
| `DividedRow` | A wrapping row with dividers between neighbours on a line | `/components/DividedRow` |
| `TokenList` | Values inline, the rest behind `+N` on hover | `/components/TokenList` |
Expand Down
29 changes: 14 additions & 15 deletions astryx/components/Board.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,28 @@ export default {
name: "items",
type: "ReadonlyArray<BoardItem<D>>",
description:
"The items: id, data, optional rowSpan (from 2) and columnSpan (from 1), definition (minRowSpan, minColumnSpan, defaultRowSpan, defaultColumnSpan) and columnOffset, a column per column count ({ 4: 2, 6: 3 }) the board writes back.",
"The items: id, data, optional title and description (each a string or a node), rowSpan (from 2) and columnSpan (from 1), definition (minRowSpan, minColumnSpan, defaultRowSpan, defaultColumnSpan) and columnOffset, a column per column count ({ 4: 2, 6: 3 }) the board writes back. A string title renders as a level-5 heading in a header row, with the drag handle on its first line; a node title (a BoardItemTitle, say) renders there as is, so a help tooltip or end-of-row actions are the node's to include. Without a title there is no header row and the description takes the whole item. A string description renders as text; a node as is, and scrolls.",
required: true,
},
{
name: "renderItem",
type: "(item: BoardItem<D>, actions: { removeItem: () => void }) => ReactNode",
name: "itemPadding",
type: "number | string | { top?: number | string; right?: …; bottom?: …; left?: … }",
description:
"An item's content. The board draws the surface, border and handles around it; the content area scrolls, so a BoardItemTitle inside stays at the top. removeItem reports the board without the item.",
required: true,
"The padding of every item's title row and description: a number (px) or a CSS length ('1rem', 'var(--spacing-3)') for every side, or one per side. The drag handle is not padded; it keeps the item's top-left corner, so the left padding is what clears it.",
default: "--spacing-5 top and bottom, --spacing-8 left and right",
},
{
name: "onItemsChange",
type: "(detail: BoardItemsChangeDetail<D>) => void",
description:
"Called when the user moves, resizes or removes an item, with items (the whole board in its new order, spans and columnOffset updated) and the movedItem, resizedItem or removedItem. A move that ends where it started does not fire.",
"Called when the user moves or resizes an item, with items (the whole board in its new order, spans and columnOffset updated) and the movedItem or resizedItem. A move that ends where it started does not fire. Removing an item is the consumer's: pass items without it.",
required: true,
},
{
name: "isMovable",
type: "boolean",
description: "Renders a drag handle at each item's top-left corner.",
description:
"Renders a drag handle at each item's top-left corner, in the inline padding on the title row's first line (the block padding from the top, --spacing-1 from the start).",
default: "false",
},
{
Expand Down Expand Up @@ -102,21 +103,19 @@ export default {
description: "Live-region announcement at each step; the placement is 0-based.",
default: "the catalog's uic.Board.itemMoved / itemResized",
},
{
name: "liveAnnouncementItemRemoved",
type: "(detail: { item: BoardItem<D> }) => string",
description: "Live-region announcement when an item is removed.",
default: "the catalog's uic.Board.itemRemoved",
},
],
usage: {
description:
"Persist what onItemsChange reports (minus data) and pass it back as items; the board snaps back otherwise. Drag a handle to move or resize by pointer (a resized item follows the pointer's size and snaps to the grid on release). Click a handle, or press Enter or Space on it, to activate it: direction buttons appear and they or the arrow keys step one cell, Enter, Space or leaving the handle commits, Escape discards. A step that would overlap an item only partly, in the direction of the move, is a conflict and does not commit. The drag handle overlays the item's top-left corner, so give the content a horizontal inset. A pointer drag held near an edge of the nearest scroll container scrolls it. CSS knobs on the root or above: --board-row-height (96px), --board-gap (--spacing-5), --board-item-radius (--radius-container), --board-transition-duration (200ms, off under prefers-reduced-motion), --board-handle-z (51; BoardItemTitle's sticky row is 50). Other div attributes reach the root.",
"Persist what onItemsChange reports (minus data) and pass it back as items; the board snaps back otherwise. Drag a handle to move or resize by pointer (a resized item follows the pointer's size and snaps to the grid on release). Click a handle, or press Enter or Space on it, to activate it: direction buttons appear and they or the arrow keys step one cell, Enter, Space or leaving the handle commits, Escape discards. A step that would overlap an item only partly, in the direction of the move, is a conflict and does not commit. The drag handle is absolute at the item's top-left corner, in the inline padding on the title row's first line, and stays there when a title wraps or a node title grows; an item without a title keeps it in the same spot. A pointer drag held near an edge of the nearest scroll container scrolls it. CSS knobs on the root or above: --board-row-height (96px), --board-gap (--spacing-5), --board-item-radius (--radius-container), --board-transition-duration (200ms, off under prefers-reduced-motion), --board-handle-z (51), --board-item-padding-top / -right / -bottom / -left (the title row's and the description's padding, --spacing-5 top and bottom, --spacing-8 left and right; itemPadding sets them per board). Other div attributes reach the root.",
},
examples: [
{
label: "A movable, resizable dashboard",
code: '<Board\n items={items}\n isMovable\n isResizable\n variant="bordered"\n renderItem={(item) => (\n <Stack gap={2} xstyle={inset}>\n <BoardItemTitle title={item.data.title} />\n {item.data.content}\n </Stack>\n )}\n onItemsChange={({ items }) => setItems(items)}\n/>',
code: '<Board\n items={[\n { id: "sessions", title: "Active sessions", description: <SessionCount />, data: {} },\n {\n id: "usage",\n title: <BoardItemTitle title="Resource usage" endContent={<Button label="Refresh" />} />,\n description: <UsageChart />,\n data: {},\n },\n ]}\n isMovable\n isResizable\n variant="bordered"\n onItemsChange={({ items }) => setItems(items)}\n/>',
},
{
label: "Title-less tiles with a tighter inset",
code: "<Board\n items={actions.map((a) => ({ id: a.id, description: <ActionTile {...a} />, data: a }))}\n isMovable\n itemPadding={{ top: 16, right: 20, bottom: 16, left: 20 }}\n onItemsChange={({ items }) => setItems(items)}\n/>",
},
],
};
6 changes: 3 additions & 3 deletions astryx/components/BoardItemTitle.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export default {
displayName: "BoardItemTitle",
import: "@lablup/ui-common",
category: "Layout",
keywords: ["title", "panel", "dashboard", "board", "sticky", "header"],
keywords: ["title", "panel", "dashboard", "board", "header"],
description:
"The title row of a dashboard panel: a heading with an optional help tooltip, and actions at the end. It sticks to the top of the panel's scroll area on the surface colour, and its two groups wrap onto separate lines when the panel is narrow.",
"A Board item's title node: a heading with an optional help tooltip, and actions at the end. The board pads and places the row, and its two groups wrap onto separate lines when the item is narrow.",
props: [
{
name: "title",
Expand Down Expand Up @@ -40,7 +40,7 @@ export default {
],
usage: {
description:
"At the top of a dashboard panel or a board item. Other div attributes (className, style, data-*) reach the row. The --board-item-title-z property sets its z-index (default 50).",
"As a Board item's title. Other div attributes (className, style, data-*) reach the row.",
},
examples: [
{
Expand Down
4 changes: 4 additions & 0 deletions docs/storybook.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ type Story = StoryObj<typeof meta>;
still renders and stays in the props table; only the edit field goes.
- Import the component relatively (`./StatCard`), never from
`@lablup/ui-common`.
- A `render` function that puts elements it creates into a prop's data (an
`items` array with node titles, say) hangs the Docs page's JSX snippet, which
walks every prop. Give that story
`parameters: { docs: { source: { type: "code" } } }`.
- A generic component takes its type argument in both places:
`component: DataGrid<Resource>` and `satisfies Meta<typeof DataGrid<Resource>>`.
- If `tsc` reports TS2742 on `meta` (the inferred type names an Astryx internal
Expand Down
62 changes: 57 additions & 5 deletions src/components/Board/Board.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
* --board-gap between cells (default --spacing-5)
* --board-item-radius an item's corners (default --radius-container)
* --board-transition-duration how long items take to slide (default 200ms)
* --board-handle-z the handles' z-index inside an item (default 51;
* BoardItemTitle's sticky row sits at 50)
* --board-handle-z the handles' z-index inside an item (default 51)
* --board-item-padding-top / -right / -bottom / -left
* the title row's and the description's padding
* (default --spacing-5 top and bottom, --spacing-8
* left and right; the drag handle sits in the left
* padding, and its top offset is the top padding,
* so it sits on the title row's first line). The
* `itemPadding` prop sets them per board.
*/
@layer ui-common {
.uic-board {
Expand All @@ -34,9 +40,14 @@
}

.uic-board-item {
--_board-item-padding-top: var(--board-item-padding-top, var(--spacing-5));
--_board-item-padding-right: var(--board-item-padding-right, var(--spacing-8));
--_board-item-padding-bottom: var(--board-item-padding-bottom, var(--spacing-5));
--_board-item-padding-left: var(--board-item-padding-left, var(--spacing-8));

position: relative;
/* Its own stacking context: the handles' z-index and a sticky title's
stay inside the item, and an active item lifts whole. */
/* Its own stacking context: the handles' z-index stays inside the item,
and an active item lifts whole. */
isolation: isolate;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -76,21 +87,62 @@
align-self: start;
}

/* The header row: the item's padding (the top value above and below the
title), and a title slot as tall as the sm drag handle, which sits in the
left padding on the row's first line. Outside the scrolling content, so
it needs no sticky. */
.uic-board-item__header {
display: flex;
flex-shrink: 0;
align-items: center;
min-width: 0;
padding: var(--_board-item-padding-top) var(--_board-item-padding-right)
var(--_board-item-padding-top) var(--_board-item-padding-left);
}

.uic-board-item__title {
display: flex;
flex: 1;
align-items: flex-start;
min-width: 0;
min-block-size: var(--size-element-sm);
}

.uic-board-item__title > * {
flex: 1;
min-width: 0;
min-block-size: var(--size-element-sm);
}

/* A string title: its first line is centered on the handle, the rest wrap below. */
.uic-board-item__title > .uic-board-item__heading {
min-block-size: 0;
padding-block: calc((var(--size-element-sm) - 1lh) / 2);
}

/* The description: the item's padding; after a header row, none on top.
Without a title it is the whole item. */
.uic-board-item__content {
flex: 1;
min-height: 0;
overflow: auto;
padding: var(--_board-item-padding-top) var(--_board-item-padding-right)
var(--_board-item-padding-bottom) var(--_board-item-padding-left);
border-radius: inherit;
}

.uic-board-item__header + .uic-board-item__content {
padding-block-start: 0;
}

.uic-board-item__handle {
position: absolute;
z-index: var(--board-handle-z, 51);
display: inline-flex;
}

.uic-board-item__handle--drag {
inset-block-start: var(--spacing-2);
inset-block-start: var(--_board-item-padding-top);
inset-inline-start: var(--spacing-1);
}

Expand Down
4 changes: 0 additions & 4 deletions src/components/Board/Board.messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ export const boardMessages = defineMessages({
defaultMessage: "Resize discarded.",
description: "Screen reader announcement when resizing a board item is cancelled",
},
"uic.Board.itemRemoved": {
defaultMessage: "Item removed.",
description: "Screen reader announcement when a board item is removed",
},
"uic.Board.directionUp": {
defaultMessage: "Up",
description:
Expand Down
Loading
Loading