@robr0/design-system 0.6.0 → 0.8.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 (48) hide show
  1. package/LICENSE +53 -2
  2. package/README.md +36 -5
  3. package/components/AgentPlan/AgentPlan.css +231 -0
  4. package/components/AgentPlan/AgentPlan.d.ts +41 -0
  5. package/components/AgentPlan/AgentPlan.js +99 -0
  6. package/components/AgentStatus/AgentStatus.css +26 -11
  7. package/components/Card/Card.css +9 -0
  8. package/components/Card/Card.d.ts +6 -0
  9. package/components/Card/Card.js +2 -1
  10. package/components/Composer/Composer.css +18 -0
  11. package/components/Composer/Composer.d.ts +3 -0
  12. package/components/Composer/Composer.js +12 -1
  13. package/components/DataTable/DataTable.css +105 -0
  14. package/components/DataTable/DataTable.d.ts +81 -0
  15. package/components/DataTable/DataTable.js +235 -0
  16. package/components/EventCalendar/EventCalendar.css +328 -0
  17. package/components/EventCalendar/EventCalendar.d.ts +51 -0
  18. package/components/EventCalendar/EventCalendar.js +213 -0
  19. package/components/ModelPicker/ModelPicker.css +238 -0
  20. package/components/ModelPicker/ModelPicker.d.ts +60 -0
  21. package/components/ModelPicker/ModelPicker.js +217 -0
  22. package/components/NotificationCenter/NotificationCenter.css +262 -0
  23. package/components/NotificationCenter/NotificationCenter.d.ts +72 -0
  24. package/components/NotificationCenter/NotificationCenter.js +128 -0
  25. package/components/Prose/Prose.css +18 -1
  26. package/components/Reasoning/Reasoning.css +19 -4
  27. package/components/ShaderField/ShaderField.css +24 -0
  28. package/components/ShaderField/ShaderField.d.ts +68 -0
  29. package/components/ShaderField/ShaderField.js +54 -0
  30. package/components/ShaderField/field.glsl.d.ts +39 -0
  31. package/components/ShaderField/field.glsl.js +178 -0
  32. package/components/ShaderField/useShaderField.d.ts +103 -0
  33. package/components/ShaderField/useShaderField.js +409 -0
  34. package/components/Timeline/Timeline.css +17 -0
  35. package/components/Timeline/Timeline.d.ts +2 -0
  36. package/components/Timeline/Timeline.js +1 -0
  37. package/components/registry.json +48 -0
  38. package/components/registry.json.d.ts +48 -0
  39. package/components/registry.json.js +1 -1
  40. package/index.d.ts +6 -0
  41. package/index.js +19 -0
  42. package/package.json +5 -1
  43. package/tokens/registry.json +1 -0
  44. package/tokens/registry.json.d.ts +1 -0
  45. package/tokens/registry.json.js +1 -1
  46. package/tokens/tokens-dark.css +1 -1
  47. package/tokens/tokens-light.css +2 -2
  48. package/tokens/tokens-motion.css +2 -0
@@ -0,0 +1,238 @@
1
+ /* ============================================
2
+ MODEL PICKER COMPONENT
3
+ A quiet pill trigger showing the current
4
+ model, and a floating panel of models with
5
+ descriptions — plus an optional effort row.
6
+
7
+ Sized for Composer's action bar: the trigger
8
+ reads as chrome, not as a call to action, so
9
+ the send button keeps the only teal.
10
+ ============================================ */
11
+
12
+ .ds-model-picker {
13
+ position: relative;
14
+ display: inline-flex;
15
+ }
16
+
17
+ /* ============================================
18
+ TRIGGER
19
+ ============================================ */
20
+
21
+ .ds-model-picker__trigger {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: var(--gap-xxs);
25
+ padding: var(--padding-xxs) var(--padding-sm);
26
+ background-color: var(--color-action-passive-bg);
27
+ border: none;
28
+ border-radius: var(--radius-full);
29
+ cursor: pointer;
30
+ color: var(--color-text-secondary);
31
+ font-family: var(--font-paragraph-sm-em-family);
32
+ font-size: var(--font-paragraph-sm-em-size);
33
+ font-weight: var(--font-paragraph-sm-em-weight);
34
+ line-height: var(--font-paragraph-sm-em-line-height);
35
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
36
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
37
+ }
38
+
39
+ .ds-model-picker__trigger:hover {
40
+ background-color: var(--color-action-passive-bg-hover);
41
+ }
42
+
43
+ .ds-model-picker__trigger:focus-visible {
44
+ outline: 2px solid var(--color-action-primary-bg);
45
+ outline-offset: 2px;
46
+ }
47
+
48
+ .ds-model-picker__trigger:disabled {
49
+ opacity: 0.4;
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ .ds-model-picker__trigger-label {
54
+ white-space: nowrap;
55
+ }
56
+
57
+ .ds-model-picker__chevron {
58
+ --icon-size: var(--icon-size-sm);
59
+
60
+ color: var(--color-icon-secondary);
61
+ transition: transform var(--motion-duration-slow) var(--motion-ease-standard);
62
+ }
63
+
64
+ .ds-model-picker--open .ds-model-picker__chevron {
65
+ transform: rotate(180deg);
66
+ }
67
+
68
+ /* ============================================
69
+ PANEL
70
+ ============================================ */
71
+
72
+ .ds-model-picker__panel {
73
+ position: absolute;
74
+ left: 0;
75
+ z-index: 10;
76
+ min-width: 260px;
77
+ display: flex;
78
+ flex-direction: column;
79
+ background-color: var(--color-bg-container-primary);
80
+ border: var(--border-xs) solid var(--color-bg-container-border);
81
+ border-radius: var(--radius-md);
82
+ box-shadow: var(--shadow-floating);
83
+ overflow: hidden;
84
+ }
85
+
86
+ .ds-model-picker--bottom .ds-model-picker__panel {
87
+ top: calc(100% + var(--gap-xxs));
88
+ }
89
+
90
+ .ds-model-picker--top .ds-model-picker__panel {
91
+ bottom: calc(100% + var(--gap-xxs));
92
+ }
93
+
94
+ /* ============================================
95
+ MODEL LIST
96
+ ============================================ */
97
+
98
+ .ds-model-picker__list {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: var(--gap-xxs);
102
+ margin: 0;
103
+ padding: var(--padding-xxs);
104
+ list-style: none;
105
+ }
106
+
107
+ .ds-model-picker__option {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: var(--gap-sm);
111
+ padding: var(--padding-xxs) var(--padding-sm);
112
+ border-radius: var(--radius-sm);
113
+ cursor: pointer;
114
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
115
+ }
116
+
117
+ .ds-model-picker__option:hover,
118
+ .ds-model-picker__option--focused {
119
+ background-color: var(--color-bg-container-secondary);
120
+ }
121
+
122
+ .ds-model-picker__option--disabled {
123
+ opacity: 0.4;
124
+ cursor: not-allowed;
125
+ }
126
+
127
+ .ds-model-picker__option--disabled:hover {
128
+ background-color: transparent;
129
+ }
130
+
131
+ .ds-model-picker__option-main {
132
+ display: flex;
133
+ flex-direction: column;
134
+ min-width: 0;
135
+ flex: 1 1 auto;
136
+ }
137
+
138
+ .ds-model-picker__option-label {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: var(--gap-xs);
142
+ color: var(--color-text-primary);
143
+ font-family: var(--font-paragraph-sm-em-family);
144
+ font-size: var(--font-paragraph-sm-em-size);
145
+ font-weight: var(--font-paragraph-sm-em-weight);
146
+ line-height: var(--font-paragraph-sm-em-line-height);
147
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
148
+ }
149
+
150
+ .ds-model-picker__option-badge {
151
+ padding: 0 var(--padding-xs);
152
+ background-color: var(--color-status-info-bg);
153
+ border-radius: var(--radius-full);
154
+ color: var(--color-status-info-text);
155
+ font-family: var(--font-paragraph-sm-em-family);
156
+ font-size: var(--font-paragraph-sm-em-size);
157
+ font-weight: var(--font-paragraph-sm-em-weight);
158
+ line-height: var(--font-paragraph-sm-em-line-height);
159
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
160
+ }
161
+
162
+ .ds-model-picker__option-description {
163
+ color: var(--color-text-tertiary);
164
+ font-family: var(--font-paragraph-sm-family);
165
+ font-size: var(--font-paragraph-sm-size);
166
+ font-weight: var(--font-paragraph-sm-weight);
167
+ line-height: var(--font-paragraph-sm-line-height);
168
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
169
+ }
170
+
171
+ .ds-model-picker__check {
172
+ --icon-size: var(--icon-size-sm);
173
+
174
+ flex: none;
175
+ color: var(--color-action-primary-text-tertiary);
176
+ }
177
+
178
+ /* ============================================
179
+ EFFORT ROW
180
+ ============================================ */
181
+
182
+ .ds-model-picker__effort {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: var(--gap-sm);
186
+ padding: var(--padding-xs) var(--padding-sm);
187
+ border-top: var(--border-xs) solid var(--color-divider);
188
+ }
189
+
190
+ .ds-model-picker__effort-label {
191
+ flex: 1 1 auto;
192
+ color: var(--color-text-secondary);
193
+ font-family: var(--font-paragraph-sm-em-family);
194
+ font-size: var(--font-paragraph-sm-em-size);
195
+ font-weight: var(--font-paragraph-sm-em-weight);
196
+ line-height: var(--font-paragraph-sm-em-line-height);
197
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
198
+ }
199
+
200
+ .ds-model-picker__effort-options {
201
+ display: inline-flex;
202
+ gap: var(--gap-xxs);
203
+ padding: var(--padding-xxxs);
204
+ background-color: var(--color-bg-container-secondary);
205
+ border-radius: var(--radius-full);
206
+ }
207
+
208
+ .ds-model-picker__effort-option {
209
+ padding: var(--padding-xxxs) var(--padding-sm);
210
+ background: none;
211
+ border: none;
212
+ border-radius: var(--radius-full);
213
+ cursor: pointer;
214
+ color: var(--color-text-secondary);
215
+ font-family: var(--font-paragraph-sm-em-family);
216
+ font-size: var(--font-paragraph-sm-em-size);
217
+ font-weight: var(--font-paragraph-sm-em-weight);
218
+ line-height: var(--font-paragraph-sm-em-line-height);
219
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
220
+ transition:
221
+ background-color var(--motion-duration-fast) var(--motion-ease-standard),
222
+ color var(--motion-duration-fast) var(--motion-ease-standard);
223
+ }
224
+
225
+ .ds-model-picker__effort-option:hover {
226
+ color: var(--color-text-primary);
227
+ }
228
+
229
+ .ds-model-picker__effort-option:focus-visible {
230
+ outline: 2px solid var(--color-action-primary-bg);
231
+ outline-offset: -2px;
232
+ }
233
+
234
+ .ds-model-picker__effort-option--selected {
235
+ background-color: var(--color-bg-container-primary);
236
+ color: var(--color-text-primary);
237
+ box-shadow: var(--shadow-floating);
238
+ }
@@ -0,0 +1,60 @@
1
+ import { default as React } from 'react';
2
+ export interface ModelPickerModel {
3
+ /** Display name, e.g. "Fable 5". */
4
+ label: string;
5
+ /** Stable identifier reported through `onValueChange`. */
6
+ value: string;
7
+ /** One line on what the model is good at or how fast it is. */
8
+ description?: string;
9
+ /** Small trailing tag, e.g. "New". */
10
+ badge?: string;
11
+ /** Whether this model can be chosen. */
12
+ disabled?: boolean;
13
+ }
14
+ export interface ModelPickerEffortOption {
15
+ /** Display label, e.g. "Medium". */
16
+ label: string;
17
+ /** Stable identifier reported through `onEffortChange`. */
18
+ value: string;
19
+ }
20
+ /** Props owned by ModelPicker itself — everything else falls through to the root element. */
21
+ type ModelPickerOwnProps = {
22
+ /** The models on offer, in display order. */
23
+ models: ModelPickerModel[];
24
+ /** Selected model value for controlled use. Pair with `onValueChange`. */
25
+ value?: string;
26
+ /** Initially selected model value for uncontrolled use. Defaults to the first model. */
27
+ defaultValue?: string;
28
+ /** Fires with the newly selected model's value. */
29
+ onValueChange?: (value: string) => void;
30
+ /**
31
+ * Selected effort level for controlled use. Setting this (or `defaultEffort`)
32
+ * is what makes the effort row appear at all.
33
+ */
34
+ effort?: string;
35
+ /** Initially selected effort level for uncontrolled use. */
36
+ defaultEffort?: string;
37
+ /** Fires with the newly selected effort value. */
38
+ onEffortChange?: (effort: string) => void;
39
+ /** The effort levels on offer. Defaults to low, medium and high. */
40
+ effortOptions?: ModelPickerEffortOption[];
41
+ /** Which side of the trigger the panel opens on. In a composer pinned to the bottom of the screen, use `top`. */
42
+ placement?: 'bottom' | 'top';
43
+ /** Whether the whole control is disabled. */
44
+ disabled?: boolean;
45
+ /** Additional CSS classes */
46
+ className?: string;
47
+ };
48
+ export interface ModelPickerProps extends ModelPickerOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof ModelPickerOwnProps> {
49
+ }
50
+ /**
51
+ * ModelPicker is the model selector for a chat surface: a quiet pill trigger
52
+ * showing the current model, and a floating panel listing every model with a
53
+ * one-line description — plus, when an effort value is provided, a row of
54
+ * effort levels below a divider.
55
+ *
56
+ * Built for Composer's `actions` slot, but freestanding anywhere. Forwards a
57
+ * ref to the root element and spreads unrecognised props onto it.
58
+ */
59
+ export declare const ModelPicker: React.ForwardRefExoticComponent<ModelPickerProps & React.RefAttributes<HTMLDivElement>>;
60
+ export {};
@@ -0,0 +1,217 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { useId, useRef, useState, useCallback, useEffect } from "react";
3
+ import "./ModelPicker.css";
4
+ import "../../fonts/material-symbols.css";
5
+ const DEFAULT_EFFORT_OPTIONS = [
6
+ { label: "Low", value: "low" },
7
+ { label: "Medium", value: "medium" },
8
+ { label: "High", value: "high" }
9
+ ];
10
+ const ModelPicker = React.forwardRef(
11
+ ({
12
+ models,
13
+ value,
14
+ defaultValue,
15
+ onValueChange,
16
+ effort,
17
+ defaultEffort,
18
+ onEffortChange,
19
+ effortOptions = DEFAULT_EFFORT_OPTIONS,
20
+ placement = "bottom",
21
+ disabled = false,
22
+ className = "",
23
+ ...rest
24
+ }, ref) => {
25
+ const baseClass = "ds-model-picker";
26
+ const id = useId();
27
+ const listboxId = `${id}-listbox`;
28
+ const rootRef = useRef(null);
29
+ const listRef = useRef(null);
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [focusedIndex, setFocusedIndex] = useState(-1);
32
+ const isValueControlled = value !== void 0;
33
+ const [uncontrolledValue, setUncontrolledValue] = useState(
34
+ defaultValue ?? models[0]?.value
35
+ );
36
+ const currentValue = isValueControlled ? value : uncontrolledValue;
37
+ const isEffortControlled = effort !== void 0;
38
+ const [uncontrolledEffort, setUncontrolledEffort] = useState(defaultEffort);
39
+ const currentEffort = isEffortControlled ? effort : uncontrolledEffort;
40
+ const hasEffort = currentEffort !== void 0;
41
+ const setRootRef = (node) => {
42
+ rootRef.current = node;
43
+ if (typeof ref === "function") ref(node);
44
+ else if (ref) ref.current = node;
45
+ };
46
+ const selectedModel = models.find((m) => m.value === currentValue);
47
+ const selectModel = (modelValue) => {
48
+ if (!isValueControlled) setUncontrolledValue(modelValue);
49
+ onValueChange?.(modelValue);
50
+ setIsOpen(false);
51
+ };
52
+ const selectEffort = (effortValue) => {
53
+ if (!isEffortControlled) setUncontrolledEffort(effortValue);
54
+ onEffortChange?.(effortValue);
55
+ };
56
+ const handleClickOutside = useCallback((e) => {
57
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
58
+ setIsOpen(false);
59
+ }
60
+ }, []);
61
+ useEffect(() => {
62
+ document.addEventListener("mousedown", handleClickOutside);
63
+ return () => document.removeEventListener("mousedown", handleClickOutside);
64
+ }, [handleClickOutside]);
65
+ const handleTriggerKeyDown = (e) => {
66
+ if (disabled) return;
67
+ switch (e.key) {
68
+ case "Enter":
69
+ case " ":
70
+ e.preventDefault();
71
+ if (isOpen && focusedIndex >= 0 && !models[focusedIndex]?.disabled) {
72
+ selectModel(models[focusedIndex].value);
73
+ } else {
74
+ setIsOpen((prev) => !prev);
75
+ setFocusedIndex(-1);
76
+ }
77
+ break;
78
+ case "ArrowDown":
79
+ e.preventDefault();
80
+ if (!isOpen) {
81
+ setIsOpen(true);
82
+ } else {
83
+ setFocusedIndex((prev) => {
84
+ let next = prev + 1;
85
+ while (next < models.length && models[next].disabled) next++;
86
+ return next < models.length ? next : prev;
87
+ });
88
+ }
89
+ break;
90
+ case "ArrowUp":
91
+ e.preventDefault();
92
+ if (isOpen) {
93
+ setFocusedIndex((prev) => {
94
+ let next = prev - 1;
95
+ while (next >= 0 && models[next].disabled) next--;
96
+ return next >= 0 ? next : prev;
97
+ });
98
+ }
99
+ break;
100
+ case "Escape":
101
+ setIsOpen(false);
102
+ break;
103
+ }
104
+ };
105
+ useEffect(() => {
106
+ if (isOpen && focusedIndex >= 0 && listRef.current) {
107
+ const focusedEl = listRef.current.children[focusedIndex];
108
+ focusedEl?.scrollIntoView({ block: "nearest" });
109
+ }
110
+ }, [focusedIndex, isOpen]);
111
+ const classes = [
112
+ baseClass,
113
+ `${baseClass}--${placement}`,
114
+ isOpen ? `${baseClass}--open` : "",
115
+ disabled ? `${baseClass}--disabled` : "",
116
+ className
117
+ ].filter(Boolean).join(" ");
118
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref: setRootRef, className: classes, children: [
119
+ /* @__PURE__ */ jsxs(
120
+ "button",
121
+ {
122
+ type: "button",
123
+ className: `${baseClass}__trigger`,
124
+ disabled,
125
+ "aria-expanded": isOpen,
126
+ "aria-haspopup": "listbox",
127
+ "aria-controls": isOpen ? listboxId : void 0,
128
+ "aria-label": rest["aria-label"] ?? "Choose a model",
129
+ onClick: () => {
130
+ setIsOpen((prev) => !prev);
131
+ setFocusedIndex(-1);
132
+ },
133
+ onKeyDown: handleTriggerKeyDown,
134
+ children: [
135
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__trigger-label`, children: selectedModel ? selectedModel.label : "Choose a model" }),
136
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
137
+ ]
138
+ }
139
+ ),
140
+ isOpen && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__panel`, children: [
141
+ /* @__PURE__ */ jsx(
142
+ "ul",
143
+ {
144
+ className: `${baseClass}__list`,
145
+ role: "listbox",
146
+ id: listboxId,
147
+ ref: listRef,
148
+ "aria-label": "Model",
149
+ children: models.map((model, index) => /* @__PURE__ */ jsxs(
150
+ "li",
151
+ {
152
+ className: [
153
+ `${baseClass}__option`,
154
+ model.value === currentValue ? `${baseClass}__option--selected` : "",
155
+ model.disabled ? `${baseClass}__option--disabled` : "",
156
+ index === focusedIndex ? `${baseClass}__option--focused` : ""
157
+ ].filter(Boolean).join(" "),
158
+ role: "option",
159
+ "aria-selected": model.value === currentValue,
160
+ "aria-disabled": model.disabled,
161
+ onClick: () => !model.disabled && selectModel(model.value),
162
+ children: [
163
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__option-main`, children: [
164
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__option-label`, children: [
165
+ model.label,
166
+ model.badge && /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-badge`, children: model.badge })
167
+ ] }),
168
+ model.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__option-description`, children: model.description })
169
+ ] }),
170
+ model.value === currentValue && /* @__PURE__ */ jsx(
171
+ "span",
172
+ {
173
+ className: `${baseClass}__check material-symbols-rounded`,
174
+ "aria-hidden": "true",
175
+ children: "check"
176
+ }
177
+ )
178
+ ]
179
+ },
180
+ model.value
181
+ ))
182
+ }
183
+ ),
184
+ hasEffort && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__effort`, children: [
185
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__effort-label`, id: `${id}-effort-label`, children: "Effort" }),
186
+ /* @__PURE__ */ jsx(
187
+ "div",
188
+ {
189
+ className: `${baseClass}__effort-options`,
190
+ role: "radiogroup",
191
+ "aria-labelledby": `${id}-effort-label`,
192
+ children: effortOptions.map((option) => /* @__PURE__ */ jsx(
193
+ "button",
194
+ {
195
+ type: "button",
196
+ className: [
197
+ `${baseClass}__effort-option`,
198
+ option.value === currentEffort ? `${baseClass}__effort-option--selected` : ""
199
+ ].filter(Boolean).join(" "),
200
+ role: "radio",
201
+ "aria-checked": option.value === currentEffort,
202
+ onClick: () => selectEffort(option.value),
203
+ children: option.label
204
+ },
205
+ option.value
206
+ ))
207
+ }
208
+ )
209
+ ] })
210
+ ] })
211
+ ] });
212
+ }
213
+ );
214
+ ModelPicker.displayName = "ModelPicker";
215
+ export {
216
+ ModelPicker
217
+ };