@adecore/ui 0.33.2 → 0.34.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/dist/Banner.d.ts +2 -1
- package/dist/Banner.js +92 -51
- package/dist/CopyButton.d.ts +24 -0
- package/dist/CopyButton.js +144 -0
- package/dist/ErrorBoundary.js +16 -18
- package/dist/ProjectSwitcher.js +66 -73
- package/dist/RelativeTime.d.ts +9 -0
- package/dist/RelativeTime.js +38 -0
- package/dist/Segmented.d.ts +4 -1
- package/dist/Segmented.js +111 -86
- package/dist/Toasts.js +1 -1
- package/dist/combobox/index.parts.d.ts +1 -0
- package/dist/combobox/index.parts.js +1 -0
- package/dist/combobox/parts.d.ts +32 -0
- package/dist/combobox/parts.js +556 -0
- package/dist/command-palette/index.parts.d.ts +2 -0
- package/dist/command-palette/index.parts.js +3 -0
- package/dist/command-palette/parts.d.ts +32 -0
- package/dist/command-palette/parts.js +422 -0
- package/dist/format/datetime.d.ts +1 -0
- package/dist/format/datetime.js +8 -0
- package/dist/format/duration.d.ts +1 -0
- package/dist/format/duration.js +24 -1
- package/dist/format/index.d.ts +2 -2
- package/dist/format/index.js +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/locales/en.d.ts +8 -0
- package/dist/locales/en.js +8 -0
- package/dist/locales/nl.d.ts +8 -0
- package/dist/locales/nl.js +8 -0
- package/dist/match.d.ts +1 -0
- package/dist/match.js +47 -0
- package/dist/segmented-keys.d.ts +3 -0
- package/dist/segmented-keys.js +33 -0
- package/dist/theme.css +15 -0
- package/dist/toast-store.d.ts +1 -0
- package/dist/toast-store.js +16 -6
- package/dist/workspace/TabStrip.d.ts +2 -1
- package/dist/workspace/TabStrip.js +432 -372
- package/package.json +1 -1
- package/src/Banner.tsx +33 -9
- package/src/CopyButton.tsx +86 -0
- package/src/ErrorBoundary.tsx +3 -4
- package/src/ProjectSwitcher.tsx +4 -2
- package/src/RelativeTime.tsx +44 -0
- package/src/Segmented.tsx +60 -33
- package/src/Toasts.tsx +1 -1
- package/src/combobox/index.parts.ts +12 -0
- package/src/combobox/parts.tsx +147 -0
- package/src/command-palette/index.parts.ts +16 -0
- package/src/command-palette/parts.tsx +145 -0
- package/src/format/datetime.ts +5 -0
- package/src/format/duration.ts +25 -1
- package/src/format/index.ts +2 -1
- package/src/index.ts +7 -0
- package/src/locales/en.ts +8 -0
- package/src/locales/nl.ts +8 -0
- package/src/match.ts +50 -0
- package/src/segmented-keys.ts +33 -0
- package/src/theme.css +15 -0
- package/src/toast-store.ts +16 -6
- package/src/workspace/TabStrip.tsx +31 -2
package/dist/match.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const WORD_START = /[\s\-_./:]/;
|
|
2
|
+
/* Whether `query` starts the text or one of its words. */
|
|
3
|
+
const startsWord = (text, query) => {
|
|
4
|
+
let at = text.indexOf(query);
|
|
5
|
+
while (at !== -1) {
|
|
6
|
+
if (at === 0 || WORD_START.test(text[at - 1])) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
at = text.indexOf(query, at + 1);
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
};
|
|
13
|
+
const lettersInOrder = (text, query) => {
|
|
14
|
+
let at = 0;
|
|
15
|
+
for (const letter of query.replace(/\s+/g, '')) {
|
|
16
|
+
at = text.indexOf(letter, at);
|
|
17
|
+
if (at === -1) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
at += 1;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
/*
|
|
25
|
+
* How well `text` answers to what a person typed, ignoring case: 5 when it starts with the query,
|
|
26
|
+
* 4 when one of its words does, 3 when every typed word is in it and it starts with the first,
|
|
27
|
+
* 2 when every typed word is in it in any order, 1 when the letters are in it in order, and `null`
|
|
28
|
+
* when it does not match. An empty query matches everything with 0.
|
|
29
|
+
*/
|
|
30
|
+
export function matchScore(text, query) {
|
|
31
|
+
const wanted = query.trim().toLowerCase();
|
|
32
|
+
if (wanted === '') {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
const haystack = text.toLowerCase();
|
|
36
|
+
if (haystack.startsWith(wanted)) {
|
|
37
|
+
return 5;
|
|
38
|
+
}
|
|
39
|
+
if (startsWord(haystack, wanted)) {
|
|
40
|
+
return 4;
|
|
41
|
+
}
|
|
42
|
+
const words = wanted.split(/\s+/);
|
|
43
|
+
if (words.every(word => haystack.includes(word))) {
|
|
44
|
+
return haystack.startsWith(words[0]) ? 3 : 2;
|
|
45
|
+
}
|
|
46
|
+
return lettersInOrder(haystack, wanted) ? 1 : null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SegmentedOption } from './Segmented.tsx';
|
|
2
|
+
export declare function segmentedStop<T extends string>(options: readonly SegmentedOption<T>[], value: T): number;
|
|
3
|
+
export declare function segmentedTarget(options: readonly SegmentedOption<string>[], index: number, key: string): number | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const STEPS = {
|
|
2
|
+
ArrowRight: 1,
|
|
3
|
+
ArrowDown: 1,
|
|
4
|
+
ArrowLeft: -1,
|
|
5
|
+
ArrowUp: -1
|
|
6
|
+
};
|
|
7
|
+
// The index of the one tab stop: the picked option, or the first that can be picked when that one is missing or disabled.
|
|
8
|
+
export function segmentedStop(options, value) {
|
|
9
|
+
const picked = options.findIndex(option => option.id === value);
|
|
10
|
+
return picked !== -1 && !options[picked].disabled ? picked : options.findIndex(option => !option.disabled);
|
|
11
|
+
}
|
|
12
|
+
// The index a key moves to from `index`, passing over disabled options and around the ends; `null` when the key moves nothing.
|
|
13
|
+
export function segmentedTarget(options, index, key) {
|
|
14
|
+
const nearest = (from, step) => {
|
|
15
|
+
for (let offset = 0; offset < options.length; offset++) {
|
|
16
|
+
const candidate = ((from + step * offset) % options.length + options.length) % options.length;
|
|
17
|
+
if (!options[candidate].disabled) {
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
if (key in STEPS) {
|
|
24
|
+
return nearest(index + STEPS[key], STEPS[key]);
|
|
25
|
+
}
|
|
26
|
+
if (key === 'Home') {
|
|
27
|
+
return nearest(0, 1);
|
|
28
|
+
}
|
|
29
|
+
if (key === 'End') {
|
|
30
|
+
return nearest(options.length - 1, -1);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
package/dist/theme.css
CHANGED
|
@@ -58,6 +58,14 @@
|
|
|
58
58
|
--status-error: #dc2626;
|
|
59
59
|
--positive: #15803d;
|
|
60
60
|
--positive-text: #ffffff;
|
|
61
|
+
/* The series of a chart, in order: the accent, then a categorical order checked for color vision
|
|
62
|
+
deficiency on both grounds: orange, aqua, yellow, magenta, green. */
|
|
63
|
+
--chart-1: var(--accent);
|
|
64
|
+
--chart-2: #eb6834;
|
|
65
|
+
--chart-3: #1baf7a;
|
|
66
|
+
--chart-4: #eda100;
|
|
67
|
+
--chart-5: #e87ba4;
|
|
68
|
+
--chart-6: #008300;
|
|
61
69
|
/* A control drawn over a picture rather than over the interface, the handle of a wipe, so it
|
|
62
70
|
stays the same in both themes. */
|
|
63
71
|
--media-handle: #ffffff;
|
|
@@ -122,6 +130,13 @@
|
|
|
122
130
|
/* Dark on light green: the dark theme's positive is a foreground color, so text sits on it the
|
|
123
131
|
other way round from the light theme, where the same token is a fill deep enough for white. */
|
|
124
132
|
--positive-text: #052e16;
|
|
133
|
+
/* Repeated so a dark theme nested under a light one resolves its own accent. */
|
|
134
|
+
--chart-1: var(--accent);
|
|
135
|
+
--chart-2: #d95926;
|
|
136
|
+
--chart-3: #199e70;
|
|
137
|
+
--chart-4: #c98500;
|
|
138
|
+
--chart-5: #d55181;
|
|
139
|
+
--chart-6: #008300;
|
|
125
140
|
--file-icon-gray: #adadb1;
|
|
126
141
|
--file-icon-red: #ff6762;
|
|
127
142
|
--file-icon-vermilion: #d5512f;
|
package/dist/toast-store.d.ts
CHANGED
package/dist/toast-store.js
CHANGED
|
@@ -3,6 +3,8 @@ import { create } from 'zustand';
|
|
|
3
3
|
export const SUCCESS_MS = 4000;
|
|
4
4
|
// How long a deletion can still be taken back.
|
|
5
5
|
export const UNDO_MS = 8000;
|
|
6
|
+
// `setTimeout` fires at once past this, so a longer lifetime, `Infinity` too, is a toast that stays.
|
|
7
|
+
const LONGEST_TIMER_MS = 2 ** 31 - 1;
|
|
6
8
|
/* How far into its lifetime a toast is at `now`, held inside that lifetime. */
|
|
7
9
|
export const elapsedOf = (deadline, now) => Math.min(Math.max(now - deadline.start, 0), deadline.end - deadline.start);
|
|
8
10
|
/*
|
|
@@ -17,11 +19,19 @@ export const createToastStore = () => {
|
|
|
17
19
|
clearTimeout(timers.get(id));
|
|
18
20
|
timers.delete(id);
|
|
19
21
|
};
|
|
20
|
-
/*
|
|
21
|
-
|
|
22
|
+
/*
|
|
23
|
+
* A toast that went well takes itself away, and so does the offer to undo; a failure and a running
|
|
24
|
+
* action stay, unless the toast names a lifetime of its own.
|
|
25
|
+
*/
|
|
26
|
+
const schedule = (id, toast) => {
|
|
22
27
|
stop(id);
|
|
23
|
-
const
|
|
24
|
-
|
|
28
|
+
const {
|
|
29
|
+
kind,
|
|
30
|
+
persist
|
|
31
|
+
} = toast;
|
|
32
|
+
const own = toast.lifetime !== undefined && toast.lifetime > 0 ? toast.lifetime : undefined;
|
|
33
|
+
const lifetime = own ?? (kind === 'deleted' ? UNDO_MS : kind === 'success' && persist !== true ? SUCCESS_MS : null);
|
|
34
|
+
if (lifetime === null || lifetime > LONGEST_TIMER_MS) {
|
|
25
35
|
return undefined;
|
|
26
36
|
}
|
|
27
37
|
timers.set(id, setTimeout(() => {
|
|
@@ -42,7 +52,7 @@ export const createToastStore = () => {
|
|
|
42
52
|
const next = {
|
|
43
53
|
...toast,
|
|
44
54
|
id,
|
|
45
|
-
deadline: schedule(id, toast
|
|
55
|
+
deadline: schedule(id, toast)
|
|
46
56
|
};
|
|
47
57
|
const toasts = get().toasts;
|
|
48
58
|
set({
|
|
@@ -61,7 +71,7 @@ export const createToastStore = () => {
|
|
|
61
71
|
};
|
|
62
72
|
const next = {
|
|
63
73
|
...merged,
|
|
64
|
-
deadline: schedule(id, merged
|
|
74
|
+
deadline: schedule(id, merged)
|
|
65
75
|
};
|
|
66
76
|
set({
|
|
67
77
|
toasts: get().toasts.map(entry => entry.id === id ? next : entry)
|
|
@@ -21,6 +21,7 @@ export interface TabStripProps extends Omit<useRender.ComponentProps<'div'>, 'ch
|
|
|
21
21
|
renderTab?(item: TabStripItem): ReactNode;
|
|
22
22
|
focused?: boolean;
|
|
23
23
|
insertAt?: number | null;
|
|
24
|
+
pressToClose?: boolean;
|
|
24
25
|
onClose?(id: string): void;
|
|
25
26
|
onDoubleClick?(id: string, event: MouseEvent<HTMLButtonElement>): void;
|
|
26
27
|
onContextMenu?(id: string, event: MouseEvent<HTMLSpanElement>): void;
|
|
@@ -29,7 +30,7 @@ export interface TabStripProps extends Omit<useRender.ComponentProps<'div'>, 'ch
|
|
|
29
30
|
onTabDragOver?(index: number, event: DragEvent<HTMLDivElement>): void;
|
|
30
31
|
onTabDrop?(index: number, event: DragEvent<HTMLDivElement>): void;
|
|
31
32
|
}
|
|
32
|
-
export declare function TabStrip({ items, value, onValueChange, renderTab, focused, insertAt, onClose, onDoubleClick, onContextMenu, onDragStart, onDragEnd, onTabDragOver, onTabDrop, render, className, ref, ...props }: TabStripProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
33
|
+
export declare function TabStrip({ items, value, onValueChange, renderTab, focused, insertAt, pressToClose, onClose, onDoubleClick, onContextMenu, onDragStart, onDragEnd, onTabDragOver, onTabDrop, render, className, ref, ...props }: TabStripProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
33
34
|
export interface DocumentTabProps extends Omit<useRender.ComponentProps<'span'>, 'children' | 'onDoubleClick' | 'onDragStart' | 'onDragEnd'> {
|
|
34
35
|
item: TabStripItem;
|
|
35
36
|
onClose?(): void;
|