@neuronection/assistant-ui 0.7.1 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @neuronection/assistant-ui
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`0421e0e`](https://github.com/neuronection/assistant-ui/commit/0421e0e02ed994182ac3d2e79c23f786aff87b36) Thanks [@constLiakos](https://github.com/constLiakos)! - README: family showcase — neuronection.com, the three Assistant apps
8
+ (career / study / health-assistant.io) front and center; complete component
9
+ index incl. PanelModal, Textarea and the dark-mode / z-token theming notes.
10
+ Docs: correct CSS import order in theming.md, TW3 translate-collision +
11
+ z-index notes in adoption.md, positioning/z-index/audit-import rules in
12
+ adding-a-component.md, AGENTS.md gotchas updated.
13
+
14
+ ## 0.8.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [`8f92588`](https://github.com/neuronection/assistant-ui/commit/8f92588f6b294c8661fd56d57991c394a1ac9dc1) Thanks [@constLiakos](https://github.com/constLiakos)! - New `TimePicker` (+ standalone `TimePickerContent`): clock-face time picker
19
+ with a 12h AM/PM UI over a 24-hour `HH:MM` value, editable HH:MM fields
20
+ (typing 13–23 or 0 normalizes the period), keyboard-navigable dial, and
21
+ default/unstyled trigger variants. New `TimeList`: chip-based editor for
22
+ `HH:MM` lists with per-chip pickers, remove-on-hover and a max cap.
23
+ Ported from health-assistant (i18n → label props, Radix popover, tokens).
24
+
25
+ ### Patch Changes
26
+
27
+ - [`077def0`](https://github.com/neuronection/assistant-ui/commit/077def0835ab7d962b7dec04fd4e2cd26ff08bc3) Thanks [@constLiakos](https://github.com/constLiakos)! - `AiButton`: new `showLabel` (icon-only trigger), `closeOnSubmit`
28
+ (auto-close after submit) and controlled `open`/`onOpenChange` props
29
+ (close-on-success form-fill flows).
30
+
3
31
  ## 0.7.1
4
32
 
5
33
  ### Patch Changes
package/README.md CHANGED
@@ -1,14 +1,25 @@
1
1
  # assistant-ui
2
2
 
3
- Shared React component library for the **Assistant family** of apps
4
- ([career-assistant](https://github.com/neuronection/career-assistant),
5
- [study-assistant](https://github.com/neuronection/study-assistant),
6
- [health-assistant](https://github.com/health-assistant-io/health-assistant)).
3
+ Shared React component library for the **Assistant family** three sibling
4
+ apps that AI-help people with career, study, and health. One package, three
5
+ apps: family look by default, app flavor via CSS-variable design tokens.
6
+ Works in React 18 & 19 and alongside Tailwind 3 or 4 — the library ships
7
+ **precompiled CSS**, so your app's Tailwind never compiles library classes.
7
8
 
8
- One package, three apps: family look by default, app flavor via CSS-variable
9
- design tokens. Works in React 18 & 19 and alongside Tailwind 3 or 4 — the
10
- library ships **precompiled CSS**, so your app's Tailwind never compiles
11
- library classes.
9
+ **[neuronection.com](https://neuronection.com)** the family hub.
10
+
11
+ | App | What it is |
12
+ |---|---|
13
+ | [**career-assistant**](https://github.com/neuronection/career-assistant) | Job discovery, AI matching and university pathways for students |
14
+ | [**study-assistant**](https://github.com/neuronection/study-assistant) | Study workspace: notes, materials, chat with math & code rendering |
15
+ | [**health-assistant**](https://health-assistant.io) | Universal health data platform — self-hosted, privacy-first, [open source](https://github.com/health-assistant-io/health-assistant) |
16
+
17
+ The family shares its UI DNA here: **40+ modules, 260+ tests** (keyboard-nav
18
+ + axe per component), built on Radix, token-themed so every app looks like a
19
+ sibling without sharing a stack. Patterns the apps actually use — provider &
20
+ model settings blocks, AI affordances (ask/fill/act), date & clock pickers,
21
+ async comboboxes, file attachment surfaces — live in the package, not
22
+ copy-pasted in three repos.
12
23
 
13
24
  ## Install
14
25
 
@@ -30,8 +41,8 @@ import { Button, ConfirmationModal, Input } from '@neuronection/assistant-ui'
30
41
  ## Theming
31
42
 
32
43
  All visuals flow through `--as-*` CSS variables. The defaults are the family
33
- look (extracted from study-assistant); each app overrides identity tokens in a
34
- small `theme.css`:
44
+ look (extracted from study-assistant); each app overrides identity tokens in
45
+ a small `theme.css`:
35
46
 
36
47
  ```css
37
48
  :root {
@@ -40,28 +51,10 @@ small `theme.css`:
40
51
  }
41
52
  ```
42
53
 
43
- See [`themes/`](./themes) for per-app starting points and
44
- `ThemeScope` for scoping tokens to a subtree. Override **tokens**, not
45
- internal class names class names are not API.
46
-
47
- ## Peer requirements
48
-
49
- | Package | Range |
50
- |---|---|
51
- | react / react-dom | `>=18 <20` |
52
- | lucide-react | `>=0.460 <2` |
53
-
54
- Tailwind is **not** required — but if your app uses Tailwind 3 or 4, both work
55
- without configuration. Do **not** add this package to your Tailwind
56
- `content`/`@source`.
57
-
58
- ## Documentation
59
-
60
- - [Local development](./docs/local-development.md) — live-test library changes in the family apps without publishing
61
- - [Adopting in an app](./docs/adoption.md) — the install/switchover recipe each family app follows
62
- - [Theming](./docs/theming.md) — tokens, app themes, styling hooks
63
- - [Publishing](./docs/publishing.md) — the Changesets release flow and CI setup
64
- - [Adding a component](./docs/adding-a-component.md) — rules, checklist, required companions
54
+ Dark mode is the same story: remap the tokens under `.dark` (health-assistant
55
+ ships a class-based dark theme this way) no component rewrites. Overlay
56
+ stacking is token-driven too (`--as-z-modal`, `--as-z-popover`) so library
57
+ dialogs clear your app chrome. Full guide: [docs/theming.md](./docs/theming.md).
65
58
 
66
59
  ## Components
67
60
 
@@ -74,25 +67,25 @@ files in the package's `dist/`. Visual reference:
74
67
  | Module | Exports |
75
68
  |---|---|
76
69
  | `button` / `badge` / `card` | `Button`, `buttonVariants` · `Badge`, `badgeVariants` · `Card`, `CardHeader/Title/Description/Content/Footer` |
77
- | `modal` / `confirmation-modal` / `form-modal` | `Modal` + parts, `modalContentVariants` · `ConfirmationModal` · `FormModal` (form-in-modal shell) |
70
+ | `modal` / `panel-modal` | `Modal` + parts (compound Radix dialog) · `PanelModal` (header/body/footer shell, full-screen on mobile) |
71
+ | `confirmation-modal` / `form-modal` | `ConfirmationModal` · `FormModal` (form-in-modal shell: `headerActions`, reject button, busy states) |
78
72
  | `popover` / `popover-button` | compound Radix `Popover` + parts · `PopoverButton` (self-contained trigger + panel, hover-open, lazy children, `closeSignal`) |
79
73
  | `menu` / `context-menu` | `Menu`, `MenuTrigger/Content/Item/Separator/Label`, `ActionMenu` (items-driven) · `ContextMenu` (coordinate-anchored, `{x, y, items, onClose}`) |
80
- | `combobox` | `Combobox`, `ComboboxMulti` — async mode (`onSearchChange` + `loading`), grouping, full keyboard nav |
74
+ | `combobox` | `Combobox`, `ComboboxMulti` — async mode (`onSearchChange` + `loading`), grouping, capability badges, full keyboard nav |
81
75
  | `tooltip` | `Tooltip` + parts, `InfoTooltip` |
82
76
  | `wizard` | `Wizard` (steps config + `renderStep`, per-step validation gates, modal/drawer variants), `Stepper` (dots/labels) |
83
- | `input` / `search-input` / `expandable-search` | `Input` (label/hint/error wiring) · `SearchInput` · `ExpandableSearch` |
77
+ | `input` / `textarea` / `search-input` / `expandable-search` | `Input` (label/hint/error wiring) · `Textarea` · `SearchInput` · `ExpandableSearch` |
84
78
  | `chip-input` / `chip-list` | `ChipInput` (Enter/comma commit, paste-splits, Backspace-removes) · `ChipList` (variant pills, clickable, removable) |
85
- | `date-picker` | `DatePicker` (popover calendar: days/months/years views, min/max, `allowClear`, arrow-key day grid) |
79
+ | `time-picker` / `time-list` | `TimePicker` (clock face, 24h value, 12h UI, editable fields) · `TimeList` (time chips with per-chip pickers) |
80
+ | `date-picker` | `DatePicker` (popover calendar: days/months/years views, min/max, `allowClear`, arrow-key day grid, `unstyled` variant) |
86
81
  | `range-bar` / `scale-slider` | `RangeBar` (low–high band + value dot) · `ScaleSlider` (+ `scaleColorForValue`) |
87
- | `table` | `Table` (headers/rows, built-in empty state) |
82
+ | `table` | `Table` (headers/rows, opt-in empty state) |
88
83
  | `check-indicator` / `selection-bar` / `view-toggle` | `CheckIndicator` (tri-state) · `SelectionBar` (bulk-select bar) · `ViewToggle` (grid/list) |
89
84
  | `error-banner` / `undo-notice` / `empty-state` / `spinner` | presentational feedback: alert banner with `action` slot · undo toast · `EmptyState` · `Spinner` |
90
- | `info-button` / `field-label` | `InfoButton` (info popover) · `FieldLabel` (label + info affordance) |
85
+ | `info-button` / `field-label` / `copy-button` / `breadcrumbs` | `InfoButton` · `FieldLabel` · `CopyButton` (clipboard + copied state) · `Breadcrumbs` (SPA links via `linkComponent`) |
86
+ | `ai-button` / `ai-actions-dropdown` / `ai-magic-fill` | AI affordances: ask-with-suggestions (controlled open, icon-only mode) · action menu + custom prompt · describe-in-words fill modal — API calls stay app-side |
87
+ | `settings-shell` / `provider-form` / `connection-test-row` | `SettingsShell` (controlled two-pane nav) · `ProviderForm` (write-only API-key field) · `ConnectionTestRow` (idle/testing/ok/fail + latency) |
91
88
  | `model-picker` / `task-assignment-picker` | `ModelPicker` (grouped provider→model combobox, capability chips) · `TaskAssignmentPicker` (task → model mapping rows) |
92
- | `settings-shell` / `provider-form` / `connection-test-row` | `SettingsShell` (controlled two-pane nav) · `ProviderForm` (write-only key field) · `ConnectionTestRow` (idle/testing/ok/fail) |
93
- | `copy-button` / `breadcrumbs` | `CopyButton` (clipboard + copied state) · `Breadcrumbs` (SPA links via `linkComponent`) |
94
- | `ai-magic-fill` | `AiMagicFill` (describe-in-words → structured data prompt modal; app applies the data) |
95
- | `ai-button` / `ai-actions-dropdown` | `AiButton` (suggestions + prompt + response slot) · `AiActionsDropdown` (action list + custom prompt) |
96
89
  | `upload-dropzone` / `file-card` / `file-queue` | file attachment surface: dropzone (block/row) · card (status, include toggle) · queue (summary + reorder) |
97
90
  | `marquee` | `useMarquee`, `MarqueeSurface`, `MarqueeBand` (rubber-band selection) |
98
91
  | `portal` / `theme-scope` / `tokens` | `Portal` · `ThemeScope` · token name lists + types |
@@ -102,6 +95,14 @@ where it makes sense, refs forwarded everywhere, English label props with
102
95
  defaults (translate at call sites), icons as props. No fetching, no stores,
103
96
  no router — data in, events out.
104
97
 
98
+ ## Documentation
99
+
100
+ - [Adopting in an app](./docs/adoption.md) — the install/switchover recipe each family app follows (incl. the TW3 cascade notes)
101
+ - [Theming](./docs/theming.md) — tokens, app themes, dark mode, styling hooks
102
+ - [Adding a component](./docs/adding-a-component.md) — rules, checklist, required companions
103
+ - [Local development](./docs/local-development.md) — live-test library changes in the family apps without publishing
104
+ - [Publishing](./docs/publishing.md) — the Changesets release flow and CI setup
105
+
105
106
  ## Development
106
107
 
107
108
  ```bash
@@ -111,16 +112,6 @@ pnpm test --watch
111
112
  pnpm verify # lint + typecheck + test + build
112
113
  ```
113
114
 
114
- Testing library changes inside a real app (no publishing needed) — full
115
- guide in [docs/local-development.md](./docs/local-development.md):
116
-
117
- ```bash
118
- pnpm watch # library: rebuild TS + CSS on change
119
- node scripts/dev-link.mjs link <app-dir> # wire app (pnpm override / npm file:)
120
- # ...run the app dev server; reload picks up library changes
121
- node scripts/dev-link.mjs unlink <app-dir> # restore before committing in the app
122
- ```
123
-
124
115
  Releases happen via [Changesets](./CONTRIBUTING.md) — CI publishes, never a
125
116
  laptop.
126
117
 
@@ -12,6 +12,14 @@ interface AiButtonProps {
12
12
  loadingLabel?: string;
13
13
  placeholder?: string;
14
14
  disabled?: boolean;
15
+ /** Controlled open state (defaults to internal). */
16
+ open?: boolean;
17
+ defaultOpen?: boolean;
18
+ onOpenChange?: (open: boolean) => void;
19
+ /** Show the label text next to the icon (default true; icon-only when false). */
20
+ showLabel?: boolean;
21
+ /** Close the panel after a submit (form-fill flows; Q&A keeps it open). */
22
+ closeOnSubmit?: boolean;
15
23
  side?: 'top' | 'bottom' | 'left' | 'right';
16
24
  align?: 'start' | 'center' | 'end';
17
25
  className?: string;
package/dist/ai-button.js CHANGED
@@ -1,3 +1,3 @@
1
- export { AiButton } from './chunk-L56KGPGS.js';
1
+ export { AiButton } from './chunk-3VCFRIIK.js';
2
2
  //# sourceMappingURL=ai-button.js.map
3
3
  //# sourceMappingURL=ai-button.js.map
@@ -17,18 +17,30 @@ var AiButton = React.forwardRef(
17
17
  loadingLabel = "Thinking\u2026",
18
18
  placeholder,
19
19
  disabled = false,
20
+ open: openProp,
21
+ defaultOpen = false,
22
+ onOpenChange,
23
+ showLabel = true,
24
+ closeOnSubmit = false,
20
25
  side = "bottom",
21
26
  align = "end",
22
27
  className,
23
28
  panelClassName
24
29
  }, ref) {
25
- const [open, setOpen] = React.useState(false);
30
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
31
+ const isControlled = openProp !== void 0;
32
+ const open = isControlled ? openProp : uncontrolledOpen;
26
33
  const [prompt, setPrompt] = React.useState("");
34
+ const setOpen = (next) => {
35
+ if (!isControlled) setUncontrolledOpen(next);
36
+ onOpenChange?.(next);
37
+ };
27
38
  const send = (value) => {
28
39
  const trimmed = value.trim();
29
40
  if (!trimmed || loading) return;
30
41
  onSubmit(trimmed);
31
42
  setPrompt("");
43
+ if (closeOnSubmit) setOpen(false);
32
44
  };
33
45
  return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
34
46
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
@@ -44,7 +56,7 @@ var AiButton = React.forwardRef(
44
56
  ),
45
57
  children: [
46
58
  /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5", "aria-hidden": true }),
47
- label
59
+ showLabel ? label : null
48
60
  ]
49
61
  }
50
62
  ) }),
@@ -111,5 +123,5 @@ var AiButton = React.forwardRef(
111
123
  );
112
124
 
113
125
  export { AiButton };
114
- //# sourceMappingURL=chunk-L56KGPGS.js.map
115
- //# sourceMappingURL=chunk-L56KGPGS.js.map
126
+ //# sourceMappingURL=chunk-3VCFRIIK.js.map
127
+ //# sourceMappingURL=chunk-3VCFRIIK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ai-button/AiButton.tsx"],"names":["AiButton"],"mappings":"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","file":"chunk-3VCFRIIK.js","sourcesContent":["import * as React from 'react'\nimport { Send, Sparkles } from 'lucide-react'\nimport { cn } from '../../lib/utils'\nimport { Popover, PopoverContent, PopoverTrigger } from '../popover'\n\nexport interface AiButtonProps {\n onSubmit: (prompt: string) => void\n suggestions?: string[]\n onResponse?: React.ReactNode\n loading?: boolean\n error?: string | null\n label?: string\n promptLabel?: string\n submitLabel?: string\n loadingLabel?: string\n placeholder?: string\n disabled?: boolean\n /** Controlled open state (defaults to internal). */\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Show the label text next to the icon (default true; icon-only when false). */\n showLabel?: boolean\n /** Close the panel after a submit (form-fill flows; Q&A keeps it open). */\n closeOnSubmit?: boolean\n side?: 'top' | 'bottom' | 'left' | 'right'\n align?: 'start' | 'center' | 'end'\n className?: string\n panelClassName?: string\n}\n\nexport const AiButton = React.forwardRef<HTMLButtonElement, AiButtonProps>(\n function AiButton(\n {\n onSubmit,\n suggestions = [],\n onResponse,\n loading = false,\n error,\n label = 'Ask AI',\n promptLabel = 'Ask a question',\n submitLabel = 'Send',\n loadingLabel = 'Thinking…',\n placeholder,\n disabled = false,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n showLabel = true,\n closeOnSubmit = false,\n side = 'bottom',\n align = 'end',\n className,\n panelClassName,\n },\n ref,\n ) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = openProp !== undefined\n const open = isControlled ? openProp : uncontrolledOpen\n const [prompt, setPrompt] = React.useState('')\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next)\n onOpenChange?.(next)\n }\n\n const send = (value: string) => {\n const trimmed = value.trim()\n if (!trimmed || loading) return\n onSubmit(trimmed)\n setPrompt('')\n if (closeOnSubmit) setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <span className=\"relative inline-flex\">\n <PopoverTrigger asChild disabled={disabled}>\n <button\n ref={ref}\n type=\"button\"\n data-as=\"ai-button\"\n aria-label={label}\n className={cn(\n 'inline-flex cursor-pointer items-center gap-1 rounded-full bg-[color-mix(in_srgb,var(--as-ai)_12%,transparent)] px-2.5 py-1 text-xs font-medium text-[var(--as-ai)] transition-[filter] hover:brightness-95 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n >\n <Sparkles className=\"size-3.5\" aria-hidden />\n {showLabel ? label : null}\n </button>\n </PopoverTrigger>\n <PopoverContent\n side={side}\n align={align}\n aria-label={label}\n className={cn('w-72 p-3', panelClassName)}\n >\n <form\n data-as=\"ai-button-panel\"\n onSubmit={(event) => {\n event.preventDefault()\n send(prompt)\n }}\n >\n {suggestions.length > 0 ? (\n <div className=\"mb-2 flex flex-wrap gap-1\">\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n disabled={loading}\n onClick={() => send(suggestion)}\n className=\"cursor-pointer rounded-[var(--as-radius-sm)] bg-[var(--as-secondary)] px-2 py-1 text-xs transition-colors hover:bg-[color-mix(in_srgb,var(--as-ai)_14%,var(--as-secondary))] disabled:pointer-events-none disabled:opacity-50\"\n >\n {suggestion}\n </button>\n ))}\n </div>\n ) : null}\n <div className=\"mb-2 flex items-center gap-2\">\n <input\n type=\"text\"\n aria-label={promptLabel}\n placeholder={placeholder ?? promptLabel}\n value={prompt}\n onChange={(event) => setPrompt(event.target.value)}\n className=\"h-8 w-full rounded-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 text-sm outline-none placeholder:text-[var(--as-muted-fg)] focus-visible:border-[var(--as-focus-ring)]\"\n />\n <button\n type=\"submit\"\n aria-label={submitLabel}\n disabled={loading || !prompt.trim()}\n className=\"shrink-0 cursor-pointer rounded-[var(--as-radius-sm)] bg-[var(--as-ai)] p-1.5 text-[var(--as-ai-fg)] transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40\"\n >\n <Send className=\"size-3.5\" aria-hidden />\n </button>\n </div>\n {loading ? (\n <p className=\"text-sm text-[var(--as-muted-fg)]\" role=\"status\">\n {loadingLabel}\n </p>\n ) : null}\n {error ? (\n <p role=\"alert\" className=\"text-sm text-[var(--as-danger)]\">\n {error}\n </p>\n ) : null}\n {onResponse}\n </form>\n </PopoverContent>\n </span>\n </Popover>\n )\n },\n)\n"]}