@apliteni/apliteni-ui 0.55.0 → 0.56.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/package.json +1 -1
- package/react/README.md +16 -6
- package/react/dist/index.css +54 -2
- package/react/dist/index.d.ts +4 -1
- package/react/dist/index.js +60 -26
package/package.json
CHANGED
package/react/README.md
CHANGED
|
@@ -385,8 +385,20 @@ uses its normal opening focus.
|
|
|
385
385
|
|
|
386
386
|
`Timeline` displays a record’s events in the order provided. Pass events from oldest
|
|
387
387
|
to newest, with stable IDs, machine-readable `dateTime` values, and formatted
|
|
388
|
-
`timestamp` text.
|
|
389
|
-
|
|
388
|
+
`timestamp` text. Place Timeline on a page or card reading surface, never a grey
|
|
389
|
+
fill; the standalone stories compose it inside `Card`.
|
|
390
|
+
Descriptions should say what changed and to where. Optional `kind` accepts
|
|
391
|
+
`person`, `rule`, or `reversal` (`TimelineEventKind`); these show the kit’s user,
|
|
392
|
+
bolt, or refresh glyph in a 20px ring. The last event is newest and its ring is
|
|
393
|
+
filled, using the danger colour for a reversal. Events without a kind keep their
|
|
394
|
+
dot. Mixed histories mute older dots; older reversal glyphs keep danger ink.
|
|
395
|
+
|
|
396
|
+
Initial history stays still, including history loaded into an empty list.
|
|
397
|
+
New IDs added to a non-empty history enter with the kit’s slide-up motion (400ms)
|
|
398
|
+
and marker scale-in (250ms, delayed 60ms). Existing IDs stay still when reordered
|
|
399
|
+
or edited. Reduced motion disables the entrance. Optional `relativeTimestamp`
|
|
400
|
+
(such as "just now") appears beside the absolute stamp only on the newest row,
|
|
401
|
+
in accent ink. The caller updates this text; Timeline does not run a clock.
|
|
390
402
|
|
|
391
403
|
```tsx
|
|
392
404
|
<Timeline aria-label="Record history" events={[
|
|
@@ -400,11 +412,9 @@ Descriptions should say what changed and to where.
|
|
|
400
412
|
|
|
401
413
|
Omit `undo` for read-only events. For a reversible batch, pass
|
|
402
414
|
`undo: { label: 'Undo batch DEMO-12', onUndo }`. The app checks permissions,
|
|
403
|
-
|
|
404
|
-
entire batch, and adds a new event. Timeline only calls `onUndo`; it does not
|
|
415
|
+
reverses the entire batch, and adds a new event without a confirmation dialog. Timeline only calls `onUndo`; it does not
|
|
405
416
|
modify history.
|
|
406
|
-
Import both the kit CSS and React CSS. The Privileged story shows
|
|
407
|
-
and a reversing event.
|
|
417
|
+
Import both the kit CSS and React CSS. The Privileged story shows Undo adding a reversing event.
|
|
408
418
|
|
|
409
419
|
## Fields
|
|
410
420
|
|
package/react/dist/index.css
CHANGED
|
@@ -189,8 +189,7 @@
|
|
|
189
189
|
.ui-timeline__event:last-child::before {
|
|
190
190
|
display: none;
|
|
191
191
|
}
|
|
192
|
-
.ui-
|
|
193
|
-
content: "";
|
|
192
|
+
.ui-timeline__marker {
|
|
194
193
|
position: absolute;
|
|
195
194
|
inset-inline-start: 0;
|
|
196
195
|
inset-block-start: calc(0.75em - var(--space-2) / 2);
|
|
@@ -214,6 +213,10 @@
|
|
|
214
213
|
.ui-timeline__meta {
|
|
215
214
|
font-size: var(--text-xs);
|
|
216
215
|
}
|
|
216
|
+
.ui-timeline__relative {
|
|
217
|
+
color: var(--accent);
|
|
218
|
+
font-size: var(--text-xs);
|
|
219
|
+
}
|
|
217
220
|
.ui-timeline__description {
|
|
218
221
|
margin: var(--space-1) 0;
|
|
219
222
|
}
|
|
@@ -224,6 +227,55 @@
|
|
|
224
227
|
.ui-timeline__undo:focus-visible {
|
|
225
228
|
color: var(--pink);
|
|
226
229
|
}
|
|
230
|
+
.ui-timeline--kinds .ui-timeline__event {
|
|
231
|
+
padding-inline-start: var(--space-8);
|
|
232
|
+
}
|
|
233
|
+
.ui-timeline--kinds .ui-timeline__event::before {
|
|
234
|
+
inset-inline-start: calc(var(--space-5) / 2);
|
|
235
|
+
}
|
|
236
|
+
.ui-timeline--kinds .ui-timeline__marker {
|
|
237
|
+
inset-inline-start: calc((var(--space-5) - var(--space-2)) / 2);
|
|
238
|
+
background: var(--muted);
|
|
239
|
+
}
|
|
240
|
+
.ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker {
|
|
241
|
+
background: var(--accent);
|
|
242
|
+
}
|
|
243
|
+
.ui-timeline--kinds .ui-timeline__marker--kind {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
inset-inline-start: 0;
|
|
248
|
+
inset-block-start: calc(0.75em - var(--space-5) / 2);
|
|
249
|
+
width: var(--space-5);
|
|
250
|
+
height: var(--space-5);
|
|
251
|
+
border: 1px solid var(--muted);
|
|
252
|
+
background: var(--surface);
|
|
253
|
+
color: var(--muted);
|
|
254
|
+
}
|
|
255
|
+
.ui-timeline--kinds .ui-timeline__marker--reversal {
|
|
256
|
+
color: var(--pink);
|
|
257
|
+
}
|
|
258
|
+
.ui-timeline__marker--kind svg {
|
|
259
|
+
width: var(--space-3);
|
|
260
|
+
height: var(--space-3);
|
|
261
|
+
}
|
|
262
|
+
.ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--kind {
|
|
263
|
+
border-color: var(--accent-strong);
|
|
264
|
+
background: var(--accent-strong);
|
|
265
|
+
color: var(--accent-contrast);
|
|
266
|
+
}
|
|
267
|
+
.ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--reversal {
|
|
268
|
+
border-color: var(--pink);
|
|
269
|
+
background: var(--pink);
|
|
270
|
+
color: var(--danger-contrast);
|
|
271
|
+
}
|
|
272
|
+
.ui-timeline__event.is-entering {
|
|
273
|
+
--m-dist: var(--space-2);
|
|
274
|
+
animation: m-slide-up var(--dur-slow) var(--ease-out) both;
|
|
275
|
+
}
|
|
276
|
+
.ui-timeline__marker.is-entering {
|
|
277
|
+
animation: m-scale-in var(--dur-med) var(--ease-out) var(--delay-1) both;
|
|
278
|
+
}
|
|
227
279
|
|
|
228
280
|
/* ../src/styles/input.css */
|
|
229
281
|
.ui-field {
|
package/react/dist/index.d.ts
CHANGED
|
@@ -498,11 +498,14 @@ type TabsProps = {
|
|
|
498
498
|
};
|
|
499
499
|
declare function Tabs({ items, value, onChange, label }: TabsProps): react.JSX.Element;
|
|
500
500
|
|
|
501
|
+
type TimelineEventKind = 'person' | 'rule' | 'reversal';
|
|
501
502
|
type TimelineEvent = {
|
|
502
503
|
id: string;
|
|
504
|
+
kind?: TimelineEventKind;
|
|
503
505
|
actor: string;
|
|
504
506
|
dateTime: string;
|
|
505
507
|
timestamp: string;
|
|
508
|
+
relativeTimestamp?: string;
|
|
506
509
|
description: string;
|
|
507
510
|
meta?: ReactNode;
|
|
508
511
|
undo?: {
|
|
@@ -608,4 +611,4 @@ declare function ThemeToggle({ labelled }: ThemeToggleProps): react.JSX.Element;
|
|
|
608
611
|
declare const PAGE_SIZES: readonly number[];
|
|
609
612
|
declare const DEFAULT_PAGE_SIZE: number;
|
|
610
613
|
|
|
611
|
-
export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
|
|
614
|
+
export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineEventKind, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
|
package/react/dist/index.js
CHANGED
|
@@ -1890,23 +1890,57 @@ function Tabs({ items, value, onChange, label }) {
|
|
|
1890
1890
|
}
|
|
1891
1891
|
|
|
1892
1892
|
// src/Timeline.tsx
|
|
1893
|
+
import { useEffect as useEffect7, useRef as useRef12 } from "react";
|
|
1894
|
+
import { playEntrance as playEntrance2, prefersReducedMotion } from "@apliteni/apliteni-ui/motion";
|
|
1893
1895
|
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1896
|
+
var kindIcons = { person: "user", rule: "bolt", reversal: "refresh" };
|
|
1894
1897
|
function Timeline({ events, className, ...rest }) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1898
|
+
const previousIds = useRef12(new Set(events.map((event) => event.id)));
|
|
1899
|
+
const rows = useRef12(/* @__PURE__ */ new Map());
|
|
1900
|
+
useEffect7(() => {
|
|
1901
|
+
if (previousIds.current.size > 0 && !prefersReducedMotion()) {
|
|
1902
|
+
for (const event of events) {
|
|
1903
|
+
if (previousIds.current.has(event.id)) continue;
|
|
1904
|
+
const row = rows.current.get(event.id);
|
|
1905
|
+
if (!row) continue;
|
|
1906
|
+
playEntrance2(row);
|
|
1907
|
+
playEntrance2(row.querySelector(".ui-timeline__marker"));
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
previousIds.current = new Set(events.map((event) => event.id));
|
|
1911
|
+
}, [events]);
|
|
1912
|
+
return /* @__PURE__ */ jsx22("ol", { ...rest, role: "list", className: ["ui-timeline", events.some((event) => event.kind) && "ui-timeline--kinds", className].filter(Boolean).join(" "), children: events.map((event, index) => /* @__PURE__ */ jsxs18(
|
|
1913
|
+
"li",
|
|
1914
|
+
{
|
|
1915
|
+
className: "ui-timeline__event",
|
|
1916
|
+
ref: (row) => {
|
|
1917
|
+
if (row) rows.current.set(event.id, row);
|
|
1918
|
+
else rows.current.delete(event.id);
|
|
1919
|
+
},
|
|
1920
|
+
children: [
|
|
1921
|
+
/* @__PURE__ */ jsx22("span", { "aria-hidden": "true", className: [
|
|
1922
|
+
"ui-timeline__marker",
|
|
1923
|
+
event.kind && "ui-timeline__marker--kind",
|
|
1924
|
+
event.kind === "reversal" && "ui-timeline__marker--reversal"
|
|
1925
|
+
].filter(Boolean).join(" "), children: event.kind && /* @__PURE__ */ jsx22(Icon, { name: kindIcons[event.kind] }) }),
|
|
1926
|
+
/* @__PURE__ */ jsxs18("div", { className: "ui-timeline__head", children: [
|
|
1927
|
+
/* @__PURE__ */ jsx22("span", { className: "ui-timeline__actor", children: event.actor }),
|
|
1928
|
+
/* @__PURE__ */ jsx22("time", { dateTime: event.dateTime, children: event.timestamp }),
|
|
1929
|
+
index === events.length - 1 && event.relativeTimestamp && /* @__PURE__ */ jsx22("span", { className: "ui-timeline__relative", children: event.relativeTimestamp })
|
|
1930
|
+
] }),
|
|
1931
|
+
/* @__PURE__ */ jsx22("p", { className: "ui-timeline__description", children: event.description }),
|
|
1932
|
+
(event.meta != null || event.undo) && /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__meta", children: [
|
|
1933
|
+
event.meta != null && /* @__PURE__ */ jsx22("span", { children: event.meta }),
|
|
1934
|
+
event.undo && /* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
|
|
1935
|
+
] })
|
|
1936
|
+
]
|
|
1937
|
+
},
|
|
1938
|
+
event.id
|
|
1939
|
+
)) });
|
|
1906
1940
|
}
|
|
1907
1941
|
|
|
1908
1942
|
// src/Field.tsx
|
|
1909
|
-
import { useEffect as
|
|
1943
|
+
import { useEffect as useEffect8, useId as useId8, useRef as useRef13, useState as useState7 } from "react";
|
|
1910
1944
|
import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
|
|
1911
1945
|
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1912
1946
|
function Frame({ id, label, hint, error, required, children }) {
|
|
@@ -1978,10 +2012,10 @@ function SelectField({ label, hint, error, required, className = "", children, .
|
|
|
1978
2012
|
}
|
|
1979
2013
|
function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
|
|
1980
2014
|
const id = useId8();
|
|
1981
|
-
const input =
|
|
2015
|
+
const input = useRef13(null);
|
|
1982
2016
|
const [file, setFile] = useState7(null);
|
|
1983
2017
|
const [dragging, setDragging] = useState7(false);
|
|
1984
|
-
|
|
2018
|
+
useEffect8(() => {
|
|
1985
2019
|
const form = input.current?.form;
|
|
1986
2020
|
const reset = () => {
|
|
1987
2021
|
setFile(null);
|
|
@@ -2040,7 +2074,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
|
|
|
2040
2074
|
}
|
|
2041
2075
|
|
|
2042
2076
|
// src/Toast.tsx
|
|
2043
|
-
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as
|
|
2077
|
+
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect9, useRef as useRef14, useState as useState8 } from "react";
|
|
2044
2078
|
import { createPortal as createPortal4 } from "react-dom";
|
|
2045
2079
|
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2046
2080
|
var Context = createContext2(null);
|
|
@@ -2051,10 +2085,10 @@ function useToast() {
|
|
|
2051
2085
|
return push;
|
|
2052
2086
|
}
|
|
2053
2087
|
function Notice({ notice, remove }) {
|
|
2054
|
-
const root =
|
|
2088
|
+
const root = useRef14(null);
|
|
2055
2089
|
const [leaving2, setLeaving] = useState8(false);
|
|
2056
|
-
const dismissed =
|
|
2057
|
-
const remaining =
|
|
2090
|
+
const dismissed = useRef14(false);
|
|
2091
|
+
const remaining = useRef14(5e3);
|
|
2058
2092
|
const [hovered, setHovered] = useState8(false);
|
|
2059
2093
|
const [focused, setFocused] = useState8(false);
|
|
2060
2094
|
const paused = hovered || focused;
|
|
@@ -2065,7 +2099,7 @@ function Notice({ notice, remove }) {
|
|
|
2065
2099
|
if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) remove();
|
|
2066
2100
|
else setLeaving(true);
|
|
2067
2101
|
}, [remove]);
|
|
2068
|
-
|
|
2102
|
+
useEffect9(() => {
|
|
2069
2103
|
if (leaving2) {
|
|
2070
2104
|
const element = root.current;
|
|
2071
2105
|
const finish = (event) => {
|
|
@@ -2079,7 +2113,7 @@ function Notice({ notice, remove }) {
|
|
|
2079
2113
|
};
|
|
2080
2114
|
}
|
|
2081
2115
|
}, [leaving2, remove]);
|
|
2082
|
-
|
|
2116
|
+
useEffect9(() => {
|
|
2083
2117
|
if (leaving2 || notice.action || paused) return;
|
|
2084
2118
|
const started = Date.now();
|
|
2085
2119
|
const timer = setTimeout(dismiss, remaining.current);
|
|
@@ -2137,7 +2171,7 @@ function Notice({ notice, remove }) {
|
|
|
2137
2171
|
}
|
|
2138
2172
|
function Toast({ children }) {
|
|
2139
2173
|
const [entries, setEntries] = useState8([]);
|
|
2140
|
-
const nextId =
|
|
2174
|
+
const nextId = useRef14(0);
|
|
2141
2175
|
const push = useCallback3((notice) => {
|
|
2142
2176
|
const id = nextId.current++;
|
|
2143
2177
|
const remove = () => setEntries((current) => current.filter((entry) => entry.id !== id));
|
|
@@ -2153,7 +2187,7 @@ function Toast({ children }) {
|
|
|
2153
2187
|
}
|
|
2154
2188
|
|
|
2155
2189
|
// src/AppShell.tsx
|
|
2156
|
-
import { useEffect as
|
|
2190
|
+
import { useEffect as useEffect10, useId as useId9, useState as useState9 } from "react";
|
|
2157
2191
|
import { applyTheme, paletteHotkey, prism, railCollapsed, themeIcon, themeName } from "@apliteni/apliteni-ui";
|
|
2158
2192
|
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2159
2193
|
var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
@@ -2183,14 +2217,14 @@ function AppShell({
|
|
|
2183
2217
|
const [theme, setTheme] = useState9("dark");
|
|
2184
2218
|
const current = path(pathname);
|
|
2185
2219
|
const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
|
|
2186
|
-
|
|
2220
|
+
useEffect10(() => {
|
|
2187
2221
|
setCollapsed(railCollapsed() ?? defaultCollapsed);
|
|
2188
2222
|
}, [defaultCollapsed]);
|
|
2189
|
-
|
|
2223
|
+
useEffect10(() => {
|
|
2190
2224
|
setMore(false);
|
|
2191
2225
|
setSearch(false);
|
|
2192
2226
|
}, [pathname]);
|
|
2193
|
-
|
|
2227
|
+
useEffect10(() => {
|
|
2194
2228
|
const root = document.documentElement;
|
|
2195
2229
|
const sync2 = () => setTheme(root.getAttribute("data-theme") === "light" ? "light" : "dark");
|
|
2196
2230
|
sync2();
|
|
@@ -2198,7 +2232,7 @@ function AppShell({
|
|
|
2198
2232
|
observer.observe(root, { attributes: true, attributeFilter: ["data-theme"] });
|
|
2199
2233
|
return () => observer.disconnect();
|
|
2200
2234
|
}, []);
|
|
2201
|
-
|
|
2235
|
+
useEffect10(() => {
|
|
2202
2236
|
const hotkey = (event) => {
|
|
2203
2237
|
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
|
|
2204
2238
|
const target = event.target;
|