@vectara/vectara-ui 18.4.0 → 19.1.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 (81) hide show
  1. package/lib/components/card/SimpleCard.d.ts +1 -1
  2. package/lib/components/card/_index.scss +5 -30
  3. package/lib/components/chart/BarChart.d.ts +20 -0
  4. package/lib/components/chart/BarChart.js +28 -0
  5. package/lib/components/chart/ComposedChart.d.ts +21 -0
  6. package/lib/components/chart/ComposedChart.js +29 -0
  7. package/lib/components/chart/LineChart.d.ts +22 -0
  8. package/lib/components/chart/LineChart.js +42 -0
  9. package/lib/components/chart/PieChart.d.ts +15 -0
  10. package/lib/components/chart/PieChart.js +36 -0
  11. package/lib/components/chart/ScatterChart.d.ts +18 -0
  12. package/lib/components/chart/ScatterChart.js +34 -0
  13. package/lib/components/chart/Sparkline.d.ts +13 -0
  14. package/lib/components/chart/Sparkline.js +23 -0
  15. package/lib/components/chart/TreeMap.d.ts +13 -0
  16. package/lib/components/chart/TreeMap.js +51 -0
  17. package/lib/components/chart/_index.scss +19 -0
  18. package/lib/components/chart/chartTheme.d.ts +21 -0
  19. package/lib/components/chart/chartTheme.js +13 -0
  20. package/lib/components/chart/palette.d.ts +5 -0
  21. package/lib/components/chart/palette.js +29 -0
  22. package/lib/components/composer/Composer.d.ts +41 -0
  23. package/lib/components/composer/Composer.js +148 -0
  24. package/lib/components/composer/_index.scss +4 -0
  25. package/lib/components/composer/useComposerHistory.d.ts +34 -0
  26. package/lib/components/composer/useComposerHistory.js +127 -0
  27. package/lib/components/context/Theme.d.ts +32 -0
  28. package/lib/components/context/Theme.js +116 -3
  29. package/lib/components/fileDropTarget/FileDropTarget.d.ts +8 -0
  30. package/lib/components/fileDropTarget/FileDropTarget.js +69 -0
  31. package/lib/components/fileDropTarget/_index.scss +34 -0
  32. package/lib/components/index.d.ts +13 -2
  33. package/lib/components/index.js +12 -1
  34. package/lib/components/patch/VuiPatch.d.ts +10 -0
  35. package/lib/components/patch/VuiPatch.js +32 -0
  36. package/lib/components/patch/_index.scss +30 -0
  37. package/lib/styles/index.css +143 -27
  38. package/package.json +4 -3
  39. package/src/docs/Docs.tsx +10 -0
  40. package/src/docs/pages/barChart/Basic.tsx +21 -0
  41. package/src/docs/pages/barChart/Horizontal.tsx +20 -0
  42. package/src/docs/pages/barChart/Stacked.tsx +23 -0
  43. package/src/docs/pages/barChart/index.tsx +29 -0
  44. package/src/docs/pages/card/SimpleCard.tsx +72 -6
  45. package/src/docs/pages/colorPalette/CategoricalColors.tsx +89 -0
  46. package/src/docs/pages/colorPalette/ChartColors.tsx +68 -0
  47. package/src/docs/pages/colorPalette/NeutralColors.tsx +42 -0
  48. package/src/docs/pages/colorPalette/SemanticColors.tsx +64 -0
  49. package/src/docs/pages/colorPalette/Swatch.tsx +59 -0
  50. package/src/docs/pages/colorPalette/TextAndBorderColors.tsx +37 -0
  51. package/src/docs/pages/colorPalette/Usage.tsx +72 -0
  52. package/src/docs/pages/colorPalette/index.tsx +50 -0
  53. package/src/docs/pages/composedChart/Basic.tsx +25 -0
  54. package/src/docs/pages/composedChart/index.tsx +15 -0
  55. package/src/docs/pages/composer/Composer.tsx +130 -0
  56. package/src/docs/pages/composer/index.tsx +11 -0
  57. package/src/docs/pages/fileDropTarget/FileDropTarget.tsx +80 -0
  58. package/src/docs/pages/fileDropTarget/index.tsx +11 -0
  59. package/src/docs/pages/lineChart/Area.tsx +25 -0
  60. package/src/docs/pages/lineChart/Basic.tsx +23 -0
  61. package/src/docs/pages/lineChart/Curved.tsx +25 -0
  62. package/src/docs/pages/lineChart/StackedArea.tsx +27 -0
  63. package/src/docs/pages/lineChart/index.tsx +36 -0
  64. package/src/docs/pages/patch/Icons.tsx +16 -0
  65. package/src/docs/pages/patch/Sizes.tsx +20 -0
  66. package/src/docs/pages/patch/index.tsx +22 -0
  67. package/src/docs/pages/pieChart/Basic.tsx +13 -0
  68. package/src/docs/pages/pieChart/Donut.tsx +12 -0
  69. package/src/docs/pages/pieChart/ManyValues.tsx +51 -0
  70. package/src/docs/pages/pieChart/Unlabeled.tsx +15 -0
  71. package/src/docs/pages/pieChart/index.tsx +36 -0
  72. package/src/docs/pages/scatterChart/Basic.tsx +31 -0
  73. package/src/docs/pages/scatterChart/Dense.tsx +144 -0
  74. package/src/docs/pages/scatterChart/index.tsx +22 -0
  75. package/src/docs/pages/sparkline/Basic.tsx +7 -0
  76. package/src/docs/pages/sparkline/Inline.tsx +21 -0
  77. package/src/docs/pages/sparkline/index.tsx +22 -0
  78. package/src/docs/pages/treeMap/Basic.tsx +14 -0
  79. package/src/docs/pages/treeMap/Nested.tsx +88 -0
  80. package/src/docs/pages/treeMap/index.tsx +22 -0
  81. package/src/docs/pages.tsx +20 -3
@@ -0,0 +1,148 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { BiPaperclip } from "react-icons/bi";
4
+ import classNames from "classnames";
5
+ import { VuiFlexContainer } from "../flex/FlexContainer";
6
+ import { VuiFlexItem } from "../flex/FlexItem";
7
+ import { VuiIcon } from "../icon/Icon";
8
+ import { VuiIconButton } from "../button/IconButton";
9
+ import { VuiButtonPrimary } from "../button/ButtonPrimary";
10
+ import { VuiBadge } from "../badge/Badge";
11
+ import { VuiSpacer } from "../spacer/Spacer";
12
+ import { VuiTextArea } from "../form";
13
+ import { VuiFileDropTarget } from "../fileDropTarget/FileDropTarget";
14
+ import { useComposerHistory } from "./useComposerHistory";
15
+ const MOD_ORDER = ["mod", "alt", "shift"];
16
+ // Single-character keys are matched case-insensitively; named keys keep their case.
17
+ const normalizeKeyName = (key) => (key.length === 1 ? key.toLowerCase() : key);
18
+ const canonical = (mods, key) => [...MOD_ORDER.filter((mod) => mods.has(mod)), normalizeKeyName(key)].join("+");
19
+ const eventSignature = (e) => {
20
+ const mods = new Set();
21
+ // Treat Cmd and Ctrl interchangeably so "Mod+k" works across platforms.
22
+ if (e.metaKey || e.ctrlKey)
23
+ mods.add("mod");
24
+ if (e.altKey)
25
+ mods.add("alt");
26
+ if (e.shiftKey)
27
+ mods.add("shift");
28
+ return canonical(mods, e.key);
29
+ };
30
+ const comboSignature = (combo) => {
31
+ var _a;
32
+ const segments = combo.split("+").map((segment) => segment.trim());
33
+ const key = (_a = segments.pop()) !== null && _a !== void 0 ? _a : "";
34
+ const mods = new Set();
35
+ for (const segment of segments) {
36
+ const mod = segment.toLowerCase();
37
+ mods.add(mod === "ctrl" || mod === "meta" ? "mod" : mod);
38
+ }
39
+ return canonical(mods, key);
40
+ };
41
+ export const VuiComposer = ({ onSubmit, isRunning, onCancel, isSendDisabled, sendLabel = "Send", cancelLabel = "Cancel", placeholder, autoFocus, onChange, value, enableHistory, historyKey, canUploadFiles, accept, validateFile, onFilesRejected, fileDropScope = "document", fileDropMessage, onShortcutKeys, leadingActions, footer, className, "data-testid": dataTestId }) => {
42
+ const isControlled = value !== undefined;
43
+ const [internalValue, setInternalValue] = useState("");
44
+ const [files, setFiles] = useState([]);
45
+ const fileInputRef = useRef(null);
46
+ const composerRef = useRef(null);
47
+ const currentValue = isControlled ? value : internalValue;
48
+ const setValue = (next) => {
49
+ if (!isControlled)
50
+ setInternalValue(next);
51
+ onChange === null || onChange === void 0 ? void 0 : onChange(next);
52
+ };
53
+ const clear = () => setValue("");
54
+ const history = useComposerHistory({ storageKey: historyKey, value: currentValue, setValue });
55
+ const isEmptyMessage = currentValue.trim() === "" && files.length === 0;
56
+ const addFiles = (incoming) => {
57
+ const existingNames = new Set(files.map((file) => file.name));
58
+ const deduped = incoming.filter((file) => !existingNames.has(file.name));
59
+ if (!validateFile) {
60
+ if (deduped.length > 0)
61
+ setFiles((prev) => [...prev, ...deduped]);
62
+ return;
63
+ }
64
+ const valid = [];
65
+ const rejected = [];
66
+ for (const file of deduped) {
67
+ const message = validateFile(file);
68
+ if (message)
69
+ rejected.push({ file, message });
70
+ else
71
+ valid.push(file);
72
+ }
73
+ if (valid.length > 0)
74
+ setFiles((prev) => [...prev, ...valid]);
75
+ if (rejected.length > 0)
76
+ onFilesRejected === null || onFilesRejected === void 0 ? void 0 : onFilesRejected(rejected);
77
+ };
78
+ const removeFile = (index) => {
79
+ setFiles((prev) => prev.filter((_, i) => i !== index));
80
+ };
81
+ const submit = () => {
82
+ if (isEmptyMessage || isSendDisabled)
83
+ return;
84
+ onSubmit({ text: currentValue, files });
85
+ if (enableHistory)
86
+ history.record(currentValue);
87
+ setValue("");
88
+ setFiles([]);
89
+ };
90
+ const handleChange = (e) => {
91
+ setValue(e.target.value);
92
+ // Any edit cancels in-progress history navigation.
93
+ if (enableHistory)
94
+ history.reset();
95
+ };
96
+ const handleKeyDown = (e) => {
97
+ var _a;
98
+ // Consumer shortcuts run first; preventDefault suppresses built-in handling.
99
+ if (onShortcutKeys) {
100
+ const signature = eventSignature(e);
101
+ for (const combo in onShortcutKeys) {
102
+ if (comboSignature(combo) === signature) {
103
+ onShortcutKeys[combo](e, { value: currentValue, setValue, clear });
104
+ break;
105
+ }
106
+ }
107
+ if (e.defaultPrevented)
108
+ return;
109
+ }
110
+ // Enter submits; Shift+Enter falls through so the textarea inserts a newline.
111
+ if (!e.shiftKey && e.code === "Enter") {
112
+ e.preventDefault();
113
+ submit();
114
+ return;
115
+ }
116
+ // Only let history cycling kick in when the caret is at the textarea's
117
+ // vertical boundary in the direction of the arrow key — otherwise the native
118
+ // textarea must move the caret between internal lines. Without this gate, the
119
+ // hook's primed→cycling state machine (designed for a single-line input)
120
+ // would replace the textarea contents regardless of caret position.
121
+ if (enableHistory && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
122
+ const el = e.currentTarget;
123
+ const pos = (_a = el.selectionStart) !== null && _a !== void 0 ? _a : 0;
124
+ // A caret at position 0 is always on the first line. We special-case it
125
+ // because lastIndexOf("\n", -1) clamps its negative fromIndex to 0 and then
126
+ // inspects index 0 — so a value that begins with a newline would be wrongly
127
+ // reported as "not first line", trapping history navigation. Otherwise,
128
+ // lastIndexOf("\n", pos - 1) scans backward from the character just before
129
+ // the caret; -1 means no newline precedes it, so it is on the first line.
130
+ const cursorOnFirstLine = pos === 0 || el.value.lastIndexOf("\n", pos - 1) === -1;
131
+ // indexOf("\n", pos) scans forward from the caret; -1 means no newline at or
132
+ // after it, so it is on the last line.
133
+ const cursorOnLastLine = el.value.indexOf("\n", pos) === -1;
134
+ if (e.key === "ArrowUp" && !cursorOnFirstLine)
135
+ return;
136
+ if (e.key === "ArrowDown" && !cursorOnLastLine)
137
+ return;
138
+ history.handleKeyDown(e);
139
+ }
140
+ };
141
+ const handleFileInputChange = (e) => {
142
+ if (e.target.files)
143
+ addFiles(Array.from(e.target.files));
144
+ // Reset so selecting the same file again still fires a change event.
145
+ e.target.value = "";
146
+ };
147
+ return (_jsxs("div", Object.assign({ className: classNames("vuiComposer", className), "data-testid": dataTestId, ref: composerRef }, { children: [_jsxs(VuiFlexContainer, Object.assign({ alignItems: "end", fullWidth: true, spacing: "xs" }, { children: [canUploadFiles && (_jsx(VuiFlexItem, { children: _jsx(VuiIconButton, { color: "neutral", "aria-label": "Attach files", icon: _jsx(VuiIcon, { children: _jsx(BiPaperclip, {}) }), onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }) })), leadingActions, _jsx(VuiFlexItem, Object.assign({ grow: 1 }, { children: _jsx(VuiTextArea, { autoFocus: autoFocus, autoGrow: true, fullWidth: true, rows: 1, placeholder: placeholder, value: currentValue, onChange: handleChange, onKeyDown: handleKeyDown }) })), _jsx(VuiFlexItem, { children: isRunning ? (_jsx(VuiButtonPrimary, Object.assign({ color: "subdued", size: "l", onClick: onCancel }, { children: cancelLabel }))) : (_jsx(VuiButtonPrimary, Object.assign({ color: "primary", size: "l", onClick: submit, isDisabled: isEmptyMessage || isSendDisabled }, { children: sendLabel }))) })] })), canUploadFiles && (_jsx("input", { ref: fileInputRef, type: "file", multiple: true, accept: accept, style: { display: "none" }, onChange: handleFileInputChange })), files.length > 0 && (_jsxs(_Fragment, { children: [_jsx(VuiSpacer, { size: "s" }), _jsx(VuiFlexContainer, Object.assign({ fullWidth: true, wrap: true, spacing: "xxs" }, { children: files.map((file, index) => (_jsx(VuiBadge, Object.assign({ color: "primary", onClose: () => removeFile(index) }, { children: file.name }), file.name))) }))] })), footer, canUploadFiles && (_jsx(VuiFileDropTarget, { onFilesDropped: addFiles, message: fileDropMessage, scopeRef: fileDropScope === "composer" ? composerRef : undefined }))] })));
148
+ };
@@ -0,0 +1,4 @@
1
+ .vuiComposer {
2
+ position: relative;
3
+ width: 100%;
4
+ }
@@ -0,0 +1,34 @@
1
+ import { KeyboardEvent } from "react";
2
+ type Args = {
3
+ storageKey?: string;
4
+ value: string;
5
+ setValue: (value: string) => void;
6
+ };
7
+ /**
8
+ * Cycles through previous submissions with UP/DOWN arrow keys.
9
+ *
10
+ * State machine for navigation (3 phases):
11
+ *
12
+ * idle — no navigation active.
13
+ * primed — first key press (UP/DOWN) consumed by native cursor movement.
14
+ * cycling — UP/DOWN keys walk through the history stack.
15
+ *
16
+ * Transitions on arrow key press:
17
+ *
18
+ * idle + empty input + UP → cycling (skip primed, start navigating immediately)
19
+ * idle + empty input + DOWN → idle (nothing to cycle to)
20
+ * idle + has text + UP/DN → primed (let native cursor move first)
21
+ * primed + same direction → cycling (second press enters history)
22
+ * primed + diff direction → primed (re-prime for the new direction)
23
+ * cycling + same direction → cycling (keep walking history)
24
+ * cycling + diff direction → primed (let native cursor adjust, re-prime)
25
+ *
26
+ * The composer calls reset() on any user edit and record() on submit, both of
27
+ * which return the machine to idle.
28
+ */
29
+ export declare function useComposerHistory({ storageKey, value, setValue }: Args): {
30
+ handleKeyDown: (e: KeyboardEvent) => void;
31
+ record: (input: string) => void;
32
+ reset: () => void;
33
+ };
34
+ export {};
@@ -0,0 +1,127 @@
1
+ import { useRef, useState } from "react";
2
+ function loadHistory(storageKey) {
3
+ try {
4
+ const stored = sessionStorage.getItem(storageKey);
5
+ return stored ? JSON.parse(stored) : [];
6
+ }
7
+ catch (_a) {
8
+ return [];
9
+ }
10
+ }
11
+ function saveHistory(storageKey, history) {
12
+ try {
13
+ sessionStorage.setItem(storageKey, JSON.stringify(history));
14
+ }
15
+ catch (_a) {
16
+ // Quota exceeded or unavailable — silently drop.
17
+ }
18
+ }
19
+ /**
20
+ * Cycles through previous submissions with UP/DOWN arrow keys.
21
+ *
22
+ * State machine for navigation (3 phases):
23
+ *
24
+ * idle — no navigation active.
25
+ * primed — first key press (UP/DOWN) consumed by native cursor movement.
26
+ * cycling — UP/DOWN keys walk through the history stack.
27
+ *
28
+ * Transitions on arrow key press:
29
+ *
30
+ * idle + empty input + UP → cycling (skip primed, start navigating immediately)
31
+ * idle + empty input + DOWN → idle (nothing to cycle to)
32
+ * idle + has text + UP/DN → primed (let native cursor move first)
33
+ * primed + same direction → cycling (second press enters history)
34
+ * primed + diff direction → primed (re-prime for the new direction)
35
+ * cycling + same direction → cycling (keep walking history)
36
+ * cycling + diff direction → primed (let native cursor adjust, re-prime)
37
+ *
38
+ * The composer calls reset() on any user edit and record() on submit, both of
39
+ * which return the machine to idle.
40
+ */
41
+ export function useComposerHistory({ storageKey, value, setValue }) {
42
+ const historyRef = useRef(storageKey ? loadHistory(storageKey) : []);
43
+ const [historyIndex, setHistoryIndex] = useState(null);
44
+ const draftRef = useRef("");
45
+ const phaseRef = useRef("idle");
46
+ const directionRef = useRef(null);
47
+ const reset = () => {
48
+ setHistoryIndex(null);
49
+ phaseRef.current = "idle";
50
+ directionRef.current = null;
51
+ };
52
+ const record = (input) => {
53
+ if (input.trim()) {
54
+ historyRef.current = [...historyRef.current, input];
55
+ if (storageKey)
56
+ saveHistory(storageKey, historyRef.current);
57
+ }
58
+ reset();
59
+ };
60
+ const handleKeyDown = (e) => {
61
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown")
62
+ return;
63
+ const history = historyRef.current;
64
+ if (history.length === 0)
65
+ return;
66
+ const direction = e.key === "ArrowUp" ? "up" : "down";
67
+ const phase = phaseRef.current;
68
+ const lastDirection = directionRef.current;
69
+ // Idle with text in input — let native cursor handle the first press.
70
+ if (phase === "idle" && value.length > 0) {
71
+ phaseRef.current = "primed";
72
+ directionRef.current = direction;
73
+ return;
74
+ }
75
+ // Idle with empty input — skip primed and start cycling immediately on UP; ignore DOWN.
76
+ if (phase === "idle") {
77
+ if (direction === "down")
78
+ return;
79
+ e.preventDefault();
80
+ draftRef.current = value;
81
+ const newIndex = history.length - 1;
82
+ setHistoryIndex(newIndex);
83
+ setValue(history[newIndex]);
84
+ phaseRef.current = "cycling";
85
+ directionRef.current = direction;
86
+ return;
87
+ }
88
+ // Direction changed — let native cursor handle it, re-prime.
89
+ if (direction !== lastDirection) {
90
+ phaseRef.current = "primed";
91
+ directionRef.current = direction;
92
+ return;
93
+ }
94
+ // Same direction: primed → cycling, or cycling → cycling.
95
+ // Primed DOWN with no active cycling — nothing to cycle to.
96
+ if (direction === "down" && historyIndex === null)
97
+ return;
98
+ e.preventDefault();
99
+ if (direction === "up") {
100
+ if (historyIndex === null) {
101
+ draftRef.current = value;
102
+ const newIndex = history.length - 1;
103
+ setHistoryIndex(newIndex);
104
+ setValue(history[newIndex]);
105
+ }
106
+ else if (historyIndex > 0) {
107
+ const newIndex = historyIndex - 1;
108
+ setHistoryIndex(newIndex);
109
+ setValue(history[newIndex]);
110
+ }
111
+ }
112
+ else {
113
+ if (historyIndex !== null && historyIndex < history.length - 1) {
114
+ const newIndex = historyIndex + 1;
115
+ setHistoryIndex(newIndex);
116
+ setValue(history[newIndex]);
117
+ }
118
+ else if (historyIndex !== null) {
119
+ setHistoryIndex(null);
120
+ setValue(draftRef.current);
121
+ }
122
+ }
123
+ phaseRef.current = "cycling";
124
+ directionRef.current = direction;
125
+ };
126
+ return { handleKeyDown, record, reset };
127
+ }
@@ -55,6 +55,38 @@ export type Theme = {
55
55
  colorBorderMediumRgb?: string;
56
56
  colorBorderLight?: string;
57
57
  colorBorderLightRgb?: string;
58
+ colorIndigoBackground?: string;
59
+ colorIndigoText?: string;
60
+ colorEmeraldBackground?: string;
61
+ colorEmeraldText?: string;
62
+ colorAmberBackground?: string;
63
+ colorAmberText?: string;
64
+ colorPinkBackground?: string;
65
+ colorPinkText?: string;
66
+ colorSkyBackground?: string;
67
+ colorSkyText?: string;
68
+ colorOrangeBackground?: string;
69
+ colorOrangeText?: string;
70
+ colorSlateBackground?: string;
71
+ colorSlateText?: string;
72
+ colorTealBackground?: string;
73
+ colorTealText?: string;
74
+ colorLimeBackground?: string;
75
+ colorLimeText?: string;
76
+ colorPurpleBackground?: string;
77
+ colorPurpleText?: string;
78
+ colorFuchsiaBackground?: string;
79
+ colorFuchsiaText?: string;
80
+ colorRedBackground?: string;
81
+ colorRedText?: string;
82
+ colorChart1?: string;
83
+ colorChart2?: string;
84
+ colorChart3?: string;
85
+ colorChart4?: string;
86
+ colorChart5?: string;
87
+ colorChart6?: string;
88
+ colorChart7?: string;
89
+ colorChart8?: string;
58
90
  };
59
91
  export declare const toRgba: (hex: string, alpha: number) => string;
60
92
  export declare const toRgb: (hex: string) => string;
@@ -42,6 +42,45 @@ const colorDarkerShade = "#1c1d22";
42
42
  const colorFullShade = "#0b0c0e";
43
43
  // Border colors
44
44
  const colorBorderLightShade = "#e3e4f3";
45
+ // Categorical colors
46
+ // Each hue pairs a tinted background with a saturated foreground for text and icons.
47
+ const colorIndigoBackground = "#eef2ff";
48
+ const colorIndigoText = "#4f46e5";
49
+ const colorEmeraldBackground = "#ecfdf5";
50
+ const colorEmeraldText = "#059669";
51
+ const colorAmberBackground = "#fef3c7";
52
+ const colorAmberText = "#b45309";
53
+ const colorPinkBackground = "#fce7f3";
54
+ const colorPinkText = "#be185d";
55
+ const colorSkyBackground = "#e0f2fe";
56
+ const colorSkyText = "#0369a1";
57
+ const colorOrangeBackground = "#ffedd5";
58
+ const colorOrangeText = "#c2410c";
59
+ const colorSlateBackground = "#f1f5f9";
60
+ const colorSlateText = "#475569";
61
+ const colorTealBackground = "#ccfbf1";
62
+ const colorTealText = "#0f766e";
63
+ const colorLimeBackground = "#ecfccb";
64
+ const colorLimeText = "#4d7c0f";
65
+ const colorPurpleBackground = "#f3e8ff";
66
+ const colorPurpleText = "#7e22ce";
67
+ const colorFuchsiaBackground = "#fae8ff";
68
+ const colorFuchsiaText = "#a21caf";
69
+ const colorRedBackground = "#fee2e2";
70
+ const colorRedText = "#b91c1c";
71
+ // Chart colors
72
+ // Grounded in the Okabe-Ito colorblind-safe palette, ordered for maximum
73
+ // contrast between adjacent series and anchored to the brand's blue and purple.
74
+ // The light gold and pure black of the original palette are darkened and
75
+ // softened so they read on a light background.
76
+ const colorChart1 = "#0072b2"; // Blue, neighbors the brand primary.
77
+ const colorChart2 = "#e69f00"; // Orange.
78
+ const colorChart3 = "#009e73"; // Teal-green.
79
+ const colorChart4 = "#cc79a7"; // Mauve, echoes the brand accent.
80
+ const colorChart5 = "#56b4e9"; // Sky.
81
+ const colorChart6 = "#d55e00"; // Vermillion.
82
+ const colorChart7 = "#9a8200"; // Deep gold.
83
+ const colorChart8 = "#3f4551"; // Dark slate.
45
84
  export const LIGHT_THEME = {
46
85
  // Font
47
86
  fontFamily,
@@ -104,7 +143,41 @@ export const LIGHT_THEME = {
104
143
  colorBorderMedium: colorMediumShade,
105
144
  colorBorderMediumRgb: toRgb(colorMediumShade),
106
145
  colorBorderLight: colorBorderLightShade,
107
- colorBorderLightRgb: toRgb(colorBorderLightShade)
146
+ colorBorderLightRgb: toRgb(colorBorderLightShade),
147
+ // Categorical colors
148
+ colorIndigoBackground,
149
+ colorIndigoText,
150
+ colorEmeraldBackground,
151
+ colorEmeraldText,
152
+ colorAmberBackground,
153
+ colorAmberText,
154
+ colorPinkBackground,
155
+ colorPinkText,
156
+ colorSkyBackground,
157
+ colorSkyText,
158
+ colorOrangeBackground,
159
+ colorOrangeText,
160
+ colorSlateBackground,
161
+ colorSlateText,
162
+ colorTealBackground,
163
+ colorTealText,
164
+ colorLimeBackground,
165
+ colorLimeText,
166
+ colorPurpleBackground,
167
+ colorPurpleText,
168
+ colorFuchsiaBackground,
169
+ colorFuchsiaText,
170
+ colorRedBackground,
171
+ colorRedText,
172
+ // Chart colors
173
+ colorChart1,
174
+ colorChart2,
175
+ colorChart3,
176
+ colorChart4,
177
+ colorChart5,
178
+ colorChart6,
179
+ colorChart7,
180
+ colorChart8
108
181
  };
109
182
  export const DARK_THEME = {
110
183
  // Special colors
@@ -129,7 +202,13 @@ export const DARK_THEME = {
129
202
  colorText: colorLightShade,
130
203
  colorLabel: colorLightShade,
131
204
  // Border color
132
- colorBorderLight: "#323338"
205
+ colorBorderLight: "#323338",
206
+ // Chart colors
207
+ // Lighten the dark anchors so series stay legible on a dark background; the
208
+ // mid-toned hues fall through to their light-theme values.
209
+ colorChart1: "#4aa3e0",
210
+ colorChart7: "#d4b400",
211
+ colorChart8: "#aab3c2" // Light slate.
133
212
  };
134
213
  export const toStyle = (theme) => {
135
214
  const vars = {
@@ -194,7 +273,41 @@ export const toStyle = (theme) => {
194
273
  "--vui-color-border-medium": theme.colorBorderMedium,
195
274
  "--vui-color-border-medium-rgb": theme.colorBorderMediumRgb,
196
275
  "--vui-color-border-light": theme.colorBorderLight,
197
- "--vui-color-border-light-rgb": theme.colorBorderLightRgb
276
+ "--vui-color-border-light-rgb": theme.colorBorderLightRgb,
277
+ // Categorical colors
278
+ "--vui-color-indigo-background": theme.colorIndigoBackground,
279
+ "--vui-color-indigo-text": theme.colorIndigoText,
280
+ "--vui-color-emerald-background": theme.colorEmeraldBackground,
281
+ "--vui-color-emerald-text": theme.colorEmeraldText,
282
+ "--vui-color-amber-background": theme.colorAmberBackground,
283
+ "--vui-color-amber-text": theme.colorAmberText,
284
+ "--vui-color-pink-background": theme.colorPinkBackground,
285
+ "--vui-color-pink-text": theme.colorPinkText,
286
+ "--vui-color-sky-background": theme.colorSkyBackground,
287
+ "--vui-color-sky-text": theme.colorSkyText,
288
+ "--vui-color-orange-background": theme.colorOrangeBackground,
289
+ "--vui-color-orange-text": theme.colorOrangeText,
290
+ "--vui-color-slate-background": theme.colorSlateBackground,
291
+ "--vui-color-slate-text": theme.colorSlateText,
292
+ "--vui-color-teal-background": theme.colorTealBackground,
293
+ "--vui-color-teal-text": theme.colorTealText,
294
+ "--vui-color-lime-background": theme.colorLimeBackground,
295
+ "--vui-color-lime-text": theme.colorLimeText,
296
+ "--vui-color-purple-background": theme.colorPurpleBackground,
297
+ "--vui-color-purple-text": theme.colorPurpleText,
298
+ "--vui-color-fuchsia-background": theme.colorFuchsiaBackground,
299
+ "--vui-color-fuchsia-text": theme.colorFuchsiaText,
300
+ "--vui-color-red-background": theme.colorRedBackground,
301
+ "--vui-color-red-text": theme.colorRedText,
302
+ // Chart colors
303
+ "--vui-chart-1": theme.colorChart1,
304
+ "--vui-chart-2": theme.colorChart2,
305
+ "--vui-chart-3": theme.colorChart3,
306
+ "--vui-chart-4": theme.colorChart4,
307
+ "--vui-chart-5": theme.colorChart5,
308
+ "--vui-chart-6": theme.colorChart6,
309
+ "--vui-chart-7": theme.colorChart7,
310
+ "--vui-chart-8": theme.colorChart8
198
311
  };
199
312
  // Remove undefined values.
200
313
  return Object.fromEntries(Object.entries(vars).filter(([_, v]) => v !== undefined));
@@ -0,0 +1,8 @@
1
+ import { ReactNode, RefObject } from "react";
2
+ type Props = {
3
+ onFilesDropped: (files: File[]) => void;
4
+ scopeRef?: RefObject<HTMLElement>;
5
+ message?: ReactNode;
6
+ };
7
+ export declare const VuiFileDropTarget: ({ onFilesDropped, scopeRef, message }: Props) => import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { BiUpload } from "react-icons/bi";
5
+ import { VuiScreenBlock } from "../screenBlock/ScreenBlock";
6
+ import { VuiFlexContainer } from "../flex/FlexContainer";
7
+ import { VuiFlexItem } from "../flex/FlexItem";
8
+ import { VuiIcon } from "../icon/Icon";
9
+ import { VuiText } from "../typography/Text";
10
+ const defaultMessage = (_jsxs(VuiFlexContainer, Object.assign({ direction: "column", alignItems: "center", justifyContent: "center" }, { children: [_jsx(VuiFlexItem, { children: _jsx(VuiIcon, Object.assign({ size: "xxxl", color: "empty" }, { children: _jsx(BiUpload, {}) })) }), _jsx(VuiFlexItem, { children: _jsx(VuiText, Object.assign({ align: "center", size: "l" }, { children: _jsx("p", { children: "Drop files to add to upload" }) })) })] })));
11
+ export const VuiFileDropTarget = ({ onFilesDropped, scopeRef, message = defaultMessage }) => {
12
+ const [isDragging, setIsDragging] = useState(false);
13
+ const dragCounterRef = useRef(0);
14
+ useEffect(() => {
15
+ var _a;
16
+ const target = (_a = scopeRef === null || scopeRef === void 0 ? void 0 : scopeRef.current) !== null && _a !== void 0 ? _a : document;
17
+ // Reset whenever the target changes (e.g. when scopeRef attaches or the parent
18
+ // swaps between scoped and global modes) so a stale counter doesn't suppress events.
19
+ dragCounterRef.current = 0;
20
+ setIsDragging(false);
21
+ const handleDragEnter = (e) => {
22
+ var _a;
23
+ e.preventDefault();
24
+ dragCounterRef.current += 1;
25
+ // Only show the overlay for drags that carry files, not e.g. selected text.
26
+ if (dragCounterRef.current === 1 && ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.items) && e.dataTransfer.items.length > 0) {
27
+ setIsDragging(true);
28
+ }
29
+ };
30
+ const handleDragLeave = (e) => {
31
+ e.preventDefault();
32
+ dragCounterRef.current -= 1;
33
+ if (dragCounterRef.current === 0) {
34
+ setIsDragging(false);
35
+ }
36
+ };
37
+ const handleDragOver = (e) => {
38
+ e.preventDefault();
39
+ };
40
+ const handleDrop = (e) => {
41
+ var _a;
42
+ e.preventDefault();
43
+ setIsDragging(false);
44
+ dragCounterRef.current = 0;
45
+ const files = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files;
46
+ if (files && files.length > 0) {
47
+ onFilesDropped(Array.from(files));
48
+ }
49
+ };
50
+ target.addEventListener("dragenter", handleDragEnter);
51
+ target.addEventListener("dragleave", handleDragLeave);
52
+ target.addEventListener("dragover", handleDragOver);
53
+ target.addEventListener("drop", handleDrop);
54
+ return () => {
55
+ target.removeEventListener("dragenter", handleDragEnter);
56
+ target.removeEventListener("dragleave", handleDragLeave);
57
+ target.removeEventListener("dragover", handleDragOver);
58
+ target.removeEventListener("drop", handleDrop);
59
+ };
60
+ }, [scopeRef, onFilesDropped]);
61
+ if (!isDragging)
62
+ return null;
63
+ // Scoped mode: overlay covers only the parent element.
64
+ if (scopeRef === null || scopeRef === void 0 ? void 0 : scopeRef.current) {
65
+ return createPortal(_jsx("div", Object.assign({ className: "vuiFileDropTarget__scopedOverlay" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__message" }, { children: message })) })), scopeRef.current);
66
+ }
67
+ // Global mode: full-screen overlay.
68
+ return (_jsx(VuiScreenBlock, Object.assign({ color: "primary" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__messageContainer" }, { children: _jsx("div", Object.assign({ className: "vuiFileDropTarget__message" }, { children: message })) })) })));
69
+ };
@@ -0,0 +1,34 @@
1
+ .vuiFileDropTarget__messageContainer {
2
+ position: fixed;
3
+ left: 0;
4
+ top: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ display: flex;
8
+ justify-content: center;
9
+ align-items: center;
10
+ }
11
+
12
+ .vuiFileDropTarget__message {
13
+ padding: $sizeL;
14
+ max-width: 400px;
15
+ width: 100%;
16
+ pointer-events: none;
17
+
18
+ p,
19
+ span {
20
+ color: var(--vui-color-empty-shade) !important;
21
+ }
22
+ }
23
+
24
+ // Scoped mode: portal-mounted overlay covering only the parent element (which
25
+ // must be non-statically positioned).
26
+ .vuiFileDropTarget__scopedOverlay {
27
+ position: absolute;
28
+ inset: 0;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ background-color: rgba(var(--vui-color-primary-shade-rgb), 0.6);
33
+ z-index: 10;
34
+ }