@crewlethq/ui 0.85.0 → 0.86.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.
Files changed (36) hide show
  1. package/README.md +18 -4
  2. package/dist/DataView/index.js +2 -2
  3. package/dist/KeyValueRows/KeyValueRows.d.ts +125 -0
  4. package/dist/KeyValueRows/index.d.ts +3 -0
  5. package/dist/KeyValueRows/index.js +12 -0
  6. package/dist/KeyValueRows-US5KW7JK.css +44 -0
  7. package/dist/PresenterView/index.js +2 -2
  8. package/dist/RailList/RailList.d.ts +74 -0
  9. package/dist/RailList/index.d.ts +3 -0
  10. package/dist/RailList/index.js +17 -0
  11. package/dist/RailList-N2ZBPRM2.css +97 -0
  12. package/dist/RelatedRow/index.js +2 -2
  13. package/dist/RichEditor/index.js +1 -1
  14. package/dist/SetupWizard/index.js +2 -2
  15. package/dist/ShareDialog/index.js +1 -1
  16. package/dist/TableCells/TableCells.d.ts +60 -0
  17. package/dist/TableCells/index.d.ts +2 -2
  18. package/dist/TableCells/index.js +2 -1
  19. package/dist/{TableCells-TKSNLRK3.css → TableCells-ZALZLLEF.css} +28 -0
  20. package/dist/Text/Text.d.ts +25 -3
  21. package/dist/Text/index.js +1 -1
  22. package/dist/{chunk-AUSFKGKL.js → chunk-3AMWXJQX.js} +1 -1
  23. package/dist/{chunk-AFLS26AC.js → chunk-3CALM534.js} +36 -2
  24. package/dist/{chunk-EAHEJNVJ.js → chunk-CL3FWLDO.js} +1 -1
  25. package/dist/{chunk-QP2P6GDQ.js → chunk-O5CEG7TQ.js} +14 -0
  26. package/dist/chunk-REWKR3S4.js +73 -0
  27. package/dist/chunk-TRZS7DLB.js +159 -0
  28. package/dist/generated/index.d.ts +2 -0
  29. package/dist/generated/index.js +59 -57
  30. package/dist/index.js +59 -57
  31. package/dist/styles.css +176 -1
  32. package/package.json +3 -3
  33. package/dist/{Editor-5D5Z3LBQ.js → Editor-VGNB35NJ.js} +1 -1
  34. package/dist/{chunk-VIX3GZCD.js → chunk-PAGITACP.js} +0 -0
  35. package/dist/{chunk-LMEMH6DO.js → chunk-WCJTOKTY.js} +1 -1
  36. package/dist/{chunk-3BVNNQZY.js → chunk-WRZPO4YL.js} +1 -1
@@ -57,10 +57,22 @@ export interface TextProps extends HTMLAttributes<HTMLElement> {
57
57
  * it on every push, because the proportional digits have different widths.
58
58
  */
59
59
  numeric?: boolean | undefined;
60
- /** One line, cut with an ellipsis. */
60
+ /**
61
+ * One line, cut with an ellipsis: a name in a narrow column, a caption
62
+ * under it. While the words are cut, the pointer's title carries them whole
63
+ * (see `titleWhenCut`).
64
+ */
61
65
  truncate?: boolean | undefined;
62
- /** Two or three lines, then an ellipsis. */
66
+ /** Two or three lines, then an ellipsis, with the whole words on the title while they are cut. */
63
67
  clamp?: 2 | 3 | undefined;
68
+ /**
69
+ * Whether words cut by `truncate` or `clamp` carry their whole text as the
70
+ * element's title while they are cut. On by default. Turn it off where
71
+ * something else already shows the whole words, a `Tooltip` around the
72
+ * text or a title on the element around it, so the reader is not shown
73
+ * them twice. A `title` the caller passes always wins.
74
+ */
75
+ titleWhenCut?: boolean | undefined;
64
76
  /** Bounds the line length, for anything a reader reads rather than scans. */
65
77
  measure?: TextMeasure | undefined;
66
78
  }
@@ -77,5 +89,15 @@ export interface TextProps extends HTMLAttributes<HTMLElement> {
77
89
  * `variant` sets the size, the weight, the tracking and the ink together,
78
90
  * because those four move as one. `tone` overrides only the ink, for the case
79
91
  * where the same register carries a quieter fact.
92
+ *
93
+ * CUT WORDS ARE STILL READABLE WHOLE. A line cut with an ellipsis hid the
94
+ * end of a name from a sighted reader, who had no way to read it short of
95
+ * widening the window, while a screen reader read it whole (the cut is
96
+ * paint, not content). So a cut Text puts its whole words on its title. It
97
+ * is MEASURED AT THE MOMENT THE POINTER ARRIVES, which is the only moment a
98
+ * title is read: the words and the column are what they are then, so a title
99
+ * never goes stale after a resize or a new value, and a table of a thousand
100
+ * cut cells costs nothing until one is hovered. Words that fit carry no
101
+ * title, since a tooltip that repeats what is on the screen is noise.
80
102
  */
81
- export declare function Text({ as: Tag, variant, tone, mono, numeric, truncate, clamp, measure, className, children, ...rest }: TextProps): import("react").JSX.Element;
103
+ export declare function Text({ as: Tag, variant, tone, mono, numeric, truncate, clamp, measure, titleWhenCut, className, children, ...rest }: TextProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
- export { Text } from '../chunk-QP2P6GDQ.js';
1
+ export { Text } from '../chunk-O5CEG7TQ.js';
2
2
  import '../chunk-ODEQTPFM.js';
@@ -9,7 +9,7 @@ import { jsx } from 'react/jsx-runtime';
9
9
  var engine = null;
10
10
  var loading = null;
11
11
  function loadEngine() {
12
- loading ??= import('./Editor-5D5Z3LBQ.js').then(
12
+ loading ??= import('./Editor-VGNB35NJ.js').then(
13
13
  (module) => {
14
14
  engine = module.Editor;
15
15
  return engine;
@@ -1,3 +1,4 @@
1
+ import { Text } from './chunk-O5CEG7TQ.js';
1
2
  import { Link } from './chunk-QF5UNP4T.js';
2
3
  import { Tag } from './chunk-LBGKLYBY.js';
3
4
  import { RelativeTime } from './chunk-EJNJMHQG.js';
@@ -5,7 +6,7 @@ import { EmptyValue } from './chunk-RE3FBGQQ.js';
5
6
  import { Avatar } from './chunk-XZY6S3UC.js';
6
7
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
7
8
  import { cx } from './chunk-ODEQTPFM.js';
8
- import './TableCells-TKSNLRK3.css';
9
+ import './TableCells-ZALZLLEF.css';
9
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
11
 
11
12
  function TableKeyCell({ value, text, href, emptyLabel = "Not set" }) {
@@ -84,5 +85,38 @@ function TableTextCell({ children, icon, mark }) {
84
85
  /* @__PURE__ */ jsx("span", { className: "crewlet-table-cell__truncate", children })
85
86
  ] });
86
87
  }
88
+ function TableTwoLineCell({
89
+ value,
90
+ detail,
91
+ mono,
92
+ title,
93
+ titleWhenCut = true,
94
+ icon,
95
+ mark
96
+ }) {
97
+ const ownTitles = titleWhenCut && title === void 0;
98
+ const hasDetail = detail !== void 0 && detail !== null && detail !== false && detail !== "";
99
+ const leading = mark ?? (icon !== void 0 && icon !== null ? /* @__PURE__ */ jsx("span", { className: "crewlet-table-cell__icon", "aria-hidden": "true", children: icon }) : null);
100
+ const leads = leading !== null && leading !== void 0 && leading !== false;
101
+ const lines = /* @__PURE__ */ jsxs("span", { className: "crewlet-table-cell__lines", title: leads ? void 0 : title, children: [
102
+ /* @__PURE__ */ jsx(Text, { variant: "inherit", truncate: true, mono: mono === "value" || mono === "both", titleWhenCut: ownTitles, children: value }),
103
+ hasDetail ? /* @__PURE__ */ jsx(
104
+ Text,
105
+ {
106
+ variant: "caption",
107
+ tone: "secondary",
108
+ truncate: true,
109
+ mono: mono === "detail" || mono === "both",
110
+ titleWhenCut: ownTitles,
111
+ children: detail
112
+ }
113
+ ) : null
114
+ ] });
115
+ if (!leads) return lines;
116
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-table-cell__two-line", title, children: [
117
+ leading,
118
+ lines
119
+ ] });
120
+ }
87
121
 
88
- export { TABLE_TAGS_CELL_LABELS, TableDateCell, TableEntityCell, TableKeyCell, TableNumberCell, TableStatusCell, TableTagsCell, TableTextCell };
122
+ export { TABLE_TAGS_CELL_LABELS, TableDateCell, TableEntityCell, TableKeyCell, TableNumberCell, TableStatusCell, TableTagsCell, TableTextCell, TableTwoLineCell };
@@ -1,4 +1,4 @@
1
- import { Text } from './chunk-QP2P6GDQ.js';
1
+ import { Text } from './chunk-O5CEG7TQ.js';
2
2
  import { Inline, Stack, Spacer } from './chunk-ZAC7SKUN.js';
3
3
  import { Card } from './chunk-U2IQNDDR.js';
4
4
  import { cx } from './chunk-ODEQTPFM.js';
@@ -11,14 +11,22 @@ function Text({
11
11
  truncate = false,
12
12
  clamp,
13
13
  measure,
14
+ titleWhenCut = true,
14
15
  className,
15
16
  children,
16
17
  ...rest
17
18
  }) {
19
+ const cuts = truncate || clamp !== void 0;
20
+ const measures = cuts && titleWhenCut && rest.title === void 0;
21
+ const { onMouseEnter } = rest;
18
22
  return /* @__PURE__ */ jsx(
19
23
  Tag,
20
24
  {
21
25
  ...rest,
26
+ onMouseEnter: measures ? (event) => {
27
+ titleIfCut(event.currentTarget, truncate ? "inline" : "block");
28
+ onMouseEnter?.(event);
29
+ } : onMouseEnter,
22
30
  className: cx(
23
31
  "crewlet-text",
24
32
  `crewlet-text--${variant}`,
@@ -34,5 +42,11 @@ function Text({
34
42
  }
35
43
  );
36
44
  }
45
+ function titleIfCut(element, axis) {
46
+ const cut = axis === "inline" ? element.scrollWidth > element.clientWidth : element.scrollHeight > element.clientHeight;
47
+ const words = (element.textContent ?? "").replace(/\s+/g, " ").trim();
48
+ if (cut && words !== "") element.setAttribute("title", words);
49
+ else element.removeAttribute("title");
50
+ }
37
51
 
38
52
  export { Text };
@@ -0,0 +1,73 @@
1
+ import { SidebarNav, NavItem } from './chunk-BRK3HXGZ.js';
2
+ import { Select } from './chunk-3IWZVEXA.js';
3
+ import { Text } from './chunk-O5CEG7TQ.js';
4
+ import { Card } from './chunk-U2IQNDDR.js';
5
+ import './RailList-N2ZBPRM2.css';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var RAIL_LIST_LABELS = {
9
+ counted: "items",
10
+ option: (label, count) => count === void 0 ? label : `${label} (${count.toLocaleString()})`
11
+ };
12
+ var elsewhere = (event) => event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;
13
+ function RailList({
14
+ label,
15
+ items,
16
+ value,
17
+ onChange,
18
+ toolbar,
19
+ note,
20
+ children,
21
+ renderLink,
22
+ labels: overrides,
23
+ className
24
+ }) {
25
+ const words = overrides ? { ...RAIL_LIST_LABELS, ...overrides } : RAIL_LIST_LABELS;
26
+ const chosen = items.find((item) => item.key === value);
27
+ const about = chosen?.description;
28
+ const picker = /* @__PURE__ */ jsx("span", { className: "crewlet-rail-list__picker", children: /* @__PURE__ */ jsx(
29
+ Select,
30
+ {
31
+ ariaLabel: label,
32
+ size: "sm",
33
+ width: "auto",
34
+ value,
35
+ options: items.map((item) => ({ value: item.key, label: words.option(item.label, item.count), text: item.label })),
36
+ onChange: (next) => onChange(String(next))
37
+ }
38
+ ) });
39
+ const bar = toolbar === void 0 || toolbar === null ? /* @__PURE__ */ jsx("div", { className: "crewlet-rail-list__bar crewlet-rail-list__bar--picker-only", children: picker }) : /* @__PURE__ */ jsx("div", { className: "crewlet-rail-list__bar", children: typeof toolbar === "function" ? toolbar(picker) : /* @__PURE__ */ jsxs(Fragment, { children: [
40
+ picker,
41
+ toolbar
42
+ ] }) });
43
+ return /* @__PURE__ */ jsx(Card, { padding: "none", className, children: /* @__PURE__ */ jsxs("div", { className: "crewlet-rail-list", children: [
44
+ bar,
45
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-rail-list__body", children: [
46
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-rail-list__rail", children: [
47
+ /* @__PURE__ */ jsx(SidebarNav, { label, dense: true, children: items.map((item) => /* @__PURE__ */ jsx(
48
+ NavItem,
49
+ {
50
+ label: item.label,
51
+ href: item.href,
52
+ current: item.key === value,
53
+ count: item.count === void 0 ? void 0 : { value: item.count.toLocaleString(), label: words.counted },
54
+ renderLink,
55
+ onClick: (event) => {
56
+ if (item.href !== void 0 && elsewhere(event)) return;
57
+ event.preventDefault();
58
+ onChange(item.key);
59
+ }
60
+ },
61
+ item.key
62
+ )) }),
63
+ note !== void 0 && note !== null && note !== false ? /* @__PURE__ */ jsx(Text, { as: "p", variant: "caption", tone: "tertiary", className: "crewlet-rail-list__note", children: note }) : null
64
+ ] }),
65
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-rail-list__list", children: [
66
+ about !== void 0 && about !== null && about !== false && about !== "" ? /* @__PURE__ */ jsx(Text, { as: "p", variant: "caption", tone: "secondary", className: "crewlet-rail-list__about", children: about }) : null,
67
+ children
68
+ ] })
69
+ ] })
70
+ ] }) });
71
+ }
72
+
73
+ export { RAIL_LIST_LABELS, RailList };
@@ -0,0 +1,159 @@
1
+ import { FormField, Label, Input } from './chunk-O7N6W52M.js';
2
+ import { Button } from './chunk-DGXZ635L.js';
3
+ import { IconButton } from './chunk-UAUP6XGK.js';
4
+ import { announce } from './chunk-2G355KDJ.js';
5
+ import { cx } from './chunk-ODEQTPFM.js';
6
+ import './KeyValueRows-US5KW7JK.css';
7
+ import { useId, useRef, useEffect } from 'react';
8
+ import { TrashGlyph, PlusGlyph } from '@crewlethq/icons/glyphs';
9
+ import { jsx, jsxs } from 'react/jsx-runtime';
10
+
11
+ function keyValueRowsLabels(keyLabel, valueLabel = "Value") {
12
+ const item = keyLabel.toLocaleLowerCase();
13
+ return {
14
+ key: (position) => `${keyLabel} ${position}`,
15
+ value: (key, position) => key ? `${valueLabel} for ${key}` : `${valueLabel} for ${item} ${position}`,
16
+ remove: (key, position) => key ? `Remove ${key}` : `Remove ${item} ${position}`,
17
+ add: `Add ${item}`,
18
+ removed: (key, position) => key ? `Removed ${key}` : `Removed ${item} ${position}`
19
+ };
20
+ }
21
+ function KeyValueRows(props) {
22
+ const {
23
+ label,
24
+ hideLabel = false,
25
+ keyLabel,
26
+ valueLabel = "Value",
27
+ value: rows,
28
+ onChange,
29
+ keyPlaceholder,
30
+ valuePlaceholder,
31
+ secret = false,
32
+ keyReadOnly,
33
+ keyError,
34
+ valueError,
35
+ empty,
36
+ helper,
37
+ error,
38
+ disabled = false,
39
+ labels: overrides,
40
+ className,
41
+ createRow
42
+ } = props;
43
+ const words = { ...keyValueRowsLabels(keyLabel, valueLabel), ...overrides };
44
+ const base = useId();
45
+ const minted = useRef(0);
46
+ const keyFields = useRef(/* @__PURE__ */ new Map());
47
+ const addButton = useRef(null);
48
+ const focusNext = useRef(null);
49
+ useEffect(() => {
50
+ const want = focusNext.current;
51
+ if (want === null) return;
52
+ focusNext.current = null;
53
+ if (want === "add") addButton.current?.focus();
54
+ else keyFields.current.get(want.id)?.focus();
55
+ });
56
+ const blank = () => createRow ? createRow() : { id: `${base}-${minted.current += 1}`, key: "", value: "" };
57
+ const edit = (id, change) => onChange(rows.map((row) => row.id === id ? { ...row, ...change } : row));
58
+ const add = () => {
59
+ const row = blank();
60
+ focusNext.current = { id: row.id };
61
+ onChange([...rows, row]);
62
+ };
63
+ const remove = (index) => {
64
+ const gone = rows[index];
65
+ const next = rows.filter((_, i) => i !== index);
66
+ const neighbour = next[index] ?? next[index - 1];
67
+ focusNext.current = neighbour ? { id: neighbour.id } : "add";
68
+ onChange(next);
69
+ announce(words.removed(gone.key.trim(), index + 1));
70
+ };
71
+ const placeholderFor = (row) => typeof valuePlaceholder === "function" ? valuePlaceholder(row) : valuePlaceholder;
72
+ return /* @__PURE__ */ jsx(
73
+ FormField,
74
+ {
75
+ as: "fieldset",
76
+ label,
77
+ hideLabel,
78
+ helper,
79
+ error,
80
+ className: cx("crewlet-key-value-rows", className),
81
+ children: /* @__PURE__ */ jsxs("div", { className: "crewlet-key-value-rows__body", children: [
82
+ rows.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "crewlet-key-value-rows__table", children: [
83
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-key-value-rows__head", "aria-hidden": "true", children: [
84
+ /* @__PURE__ */ jsx(Label, { as: "span", children: keyLabel }),
85
+ /* @__PURE__ */ jsx(Label, { as: "span", children: valueLabel })
86
+ ] }),
87
+ rows.map((row, index) => {
88
+ const position = index + 1;
89
+ const key = row.key.trim();
90
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-key-value-rows__row", children: [
91
+ /* @__PURE__ */ jsx(FormField, { label: words.key(position), hideLabel: true, error: keyError?.(row, index), children: (field) => /* @__PURE__ */ jsx(
92
+ Input,
93
+ {
94
+ ref: (element) => {
95
+ if (element) keyFields.current.set(row.id, element);
96
+ else keyFields.current.delete(row.id);
97
+ },
98
+ id: field.id,
99
+ "aria-describedby": field.describedBy,
100
+ "aria-invalid": field.invalid || void 0,
101
+ error: field.invalid,
102
+ appearance: "reference",
103
+ value: row.key,
104
+ placeholder: keyPlaceholder,
105
+ readOnly: keyReadOnly?.(row) ?? false,
106
+ disabled,
107
+ autoComplete: "off",
108
+ spellCheck: false,
109
+ onChange: (event) => edit(row.id, { key: event.target.value })
110
+ }
111
+ ) }),
112
+ /* @__PURE__ */ jsx(FormField, { label: words.value(key, position), hideLabel: true, error: valueError?.(row, index), children: (field) => /* @__PURE__ */ jsx(
113
+ Input,
114
+ {
115
+ id: field.id,
116
+ "aria-describedby": field.describedBy,
117
+ "aria-invalid": field.invalid || void 0,
118
+ error: field.invalid,
119
+ type: secret ? "password" : "text",
120
+ autoComplete: secret ? "new-password" : "off",
121
+ spellCheck: false,
122
+ value: row.value,
123
+ placeholder: placeholderFor(row),
124
+ disabled,
125
+ onChange: (event) => edit(row.id, { value: event.target.value })
126
+ }
127
+ ) }),
128
+ /* @__PURE__ */ jsx(
129
+ IconButton,
130
+ {
131
+ variant: "ghost-danger",
132
+ label: words.remove(key, position),
133
+ icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }),
134
+ disabled,
135
+ onClick: () => remove(index)
136
+ }
137
+ )
138
+ ] }, row.id);
139
+ })
140
+ ] }) : empty !== void 0 && empty !== null && empty !== false ? /* @__PURE__ */ jsx("p", { className: "crewlet-key-value-rows__empty", children: empty }) : null,
141
+ /* @__PURE__ */ jsx("div", { className: "crewlet-key-value-rows__add", children: /* @__PURE__ */ jsx(
142
+ Button,
143
+ {
144
+ ref: addButton,
145
+ variant: "ghost",
146
+ size: "small",
147
+ flush: true,
148
+ leadingIcon: /* @__PURE__ */ jsx(PlusGlyph, { size: "sm" }),
149
+ disabled,
150
+ onClick: add,
151
+ children: words.add
152
+ }
153
+ ) })
154
+ ] })
155
+ }
156
+ );
157
+ }
158
+
159
+ export { KeyValueRows, keyValueRowsLabels };
@@ -122,6 +122,7 @@ export * from '../InlineRef/index.js';
122
122
  export * from '../Input/index.js';
123
123
  export * from '../Kbd/index.js';
124
124
  export * from '../KeyChip/index.js';
125
+ export * from '../KeyValueRows/index.js';
125
126
  export * from '../Layer/index.js';
126
127
  export * from '../LayersList/index.js';
127
128
  export * from '../Layout/index.js';
@@ -175,6 +176,7 @@ export * from '../PropertyList/index.js';
175
176
  export * from '../ProposalCard/index.js';
176
177
  export * from '../Prose/index.js';
177
178
  export * from '../QrCode/index.js';
179
+ export * from '../RailList/index.js';
178
180
  export * from '../ReactionBar/index.js';
179
181
  export * from '../ReadingPane/index.js';
180
182
  export * from '../ReceiptStrip/index.js';