@nucleux/personality 0.0.0-stage → 0.6.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nucleux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,37 @@
1
- # Temporary Holding Version
1
+ # @nucleux/personality
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Personality — the voice and tone the assistant speaks in — Nucleux agentic UI for React.
4
+
5
+ Part of **[Nucleux](https://github.com/intelligaia-open-labs/nucleux)** — agentic UI components for React. Install a single component, or the full kit via [`@nucleux/react`](https://www.npmjs.com/package/@nucleux/react).
6
+
7
+ ## Exports
8
+
9
+ - **`Personality`** — the `tone-matrix`: a radio group of tones with roving focus (arrow keys, Home/End). `options` (`ToneOption`: `value`, `label`, `icon`, `description`), `value` / `defaultValue` / `onValueChange`, `label`; **`defaultTones`**
10
+ - **`PersonalityVoiceCard`** — the `voice-card`: `quote` + `tags`
11
+ - **`PersonalitySampleDialog`** — the `sample-dialog`: `turns` (`from` user/agent, `text`, `fixed`) built on Bubble; `fixed` lines carry a lock and "Same in every personality"
12
+
13
+ Tone changes how the agent writes, never what it may do: keep permission, caveat and refusal strings outside it.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pnpm add @nucleux/personality @nucleux/tokens
19
+ ```
20
+
21
+ `react` / `react-dom` are peer dependencies.
22
+
23
+ ## Usage
24
+
25
+ ```tsx
26
+ import { Personality, PersonalityVoiceCard, PersonalitySampleDialog } from "@nucleux/personality";
27
+
28
+ <PersonalityVoiceCard quote="Here's what I found. Want me to dig in?" tags={["calm", "proactive"]} />
29
+ <Personality value={tone} onValueChange={setTone} />
30
+ ```
31
+
32
+ Requires the theme once in your app: `import "@nucleux/tokens/styles.css";`
33
+ and the Tailwind preset from `@nucleux/tokens/preset`.
34
+
35
+ ## License
36
+
37
+ MIT © Nucleux
package/dist/index.cjs ADDED
@@ -0,0 +1,154 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+ var lucideReact = require('lucide-react');
6
+ var utils = require('@nucleux/utils');
7
+ var bubble = require('@nucleux/bubble');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
+
30
+ // src/index.tsx
31
+ var defaultTones = [
32
+ { value: "professional", label: "Professional" },
33
+ { value: "friendly", label: "Friendly" },
34
+ { value: "direct", label: "Direct" },
35
+ { value: "playful", label: "Playful" }
36
+ ];
37
+ var Personality = React__namespace.forwardRef(
38
+ ({ options = defaultTones, value, defaultValue, onValueChange, label = "Tone", className, ...props }, ref) => {
39
+ const [internal, setInternal] = React__namespace.useState(defaultValue);
40
+ const current = value !== void 0 ? value : internal;
41
+ const refs = React__namespace.useRef([]);
42
+ const descId = React__namespace.useId();
43
+ const select = (next) => {
44
+ if (value === void 0) setInternal(next);
45
+ onValueChange?.(next);
46
+ };
47
+ const selectedIndex = options.findIndex((o) => o.value === current);
48
+ const tabStop = selectedIndex >= 0 ? selectedIndex : 0;
49
+ const onKeyDown = (e, index) => {
50
+ const last = options.length - 1;
51
+ let next = null;
52
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") next = index === last ? 0 : index + 1;
53
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = index === 0 ? last : index - 1;
54
+ else if (e.key === "Home") next = 0;
55
+ else if (e.key === "End") next = last;
56
+ if (next === null) return;
57
+ e.preventDefault();
58
+ refs.current[next]?.focus();
59
+ select(options[next].value);
60
+ };
61
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, "data-variant": "tone-matrix", className: utils.cn("flex flex-col gap-2", className), ...props, children: [
62
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
63
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": typeof label === "string" ? label : "Tone", className: "flex flex-wrap gap-1.5", children: options.map((option, index) => {
64
+ const active = current === option.value;
65
+ return /* @__PURE__ */ jsxRuntime.jsxs(
66
+ "button",
67
+ {
68
+ ref: (el) => {
69
+ refs.current[index] = el;
70
+ },
71
+ type: "button",
72
+ role: "radio",
73
+ "aria-checked": active,
74
+ "aria-describedby": option.description ? `${descId}-${index}` : void 0,
75
+ tabIndex: index === tabStop ? 0 : -1,
76
+ onClick: () => select(option.value),
77
+ onKeyDown: (e) => onKeyDown(e, index),
78
+ className: utils.cn(
79
+ "inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5",
80
+ active ? "border-brand bg-brand-muted text-foreground" : "border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground"
81
+ ),
82
+ children: [
83
+ option.icon,
84
+ option.label,
85
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("span", { id: `${descId}-${index}`, className: "sr-only", children: option.description })
86
+ ]
87
+ },
88
+ option.value
89
+ );
90
+ }) })
91
+ ] });
92
+ }
93
+ );
94
+ Personality.displayName = "Personality";
95
+ var PersonalityVoiceCard = React__namespace.forwardRef(
96
+ ({ quote, tags = [], label = "Voice", className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
97
+ "figure",
98
+ {
99
+ ref,
100
+ "aria-label": label,
101
+ "data-variant": "voice-card",
102
+ className: utils.cn("m-0 rounded-xl border border-border bg-background p-4", className),
103
+ ...props,
104
+ children: [
105
+ /* @__PURE__ */ jsxRuntime.jsxs("blockquote", { className: "m-0 text-sm italic leading-5 text-foreground/80", children: [
106
+ "\u201C",
107
+ quote,
108
+ "\u201D"
109
+ ] }),
110
+ tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { className: "mt-3", children: [
111
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Voice: " }),
112
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-wrap gap-1.5", children: tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsx(
113
+ "li",
114
+ {
115
+ className: "rounded-full border border-border bg-muted px-2.5 py-0.5 text-[11px] text-muted-foreground",
116
+ children: tag
117
+ },
118
+ tag
119
+ )) })
120
+ ] })
121
+ ]
122
+ }
123
+ )
124
+ );
125
+ PersonalityVoiceCard.displayName = "PersonalityVoiceCard";
126
+ var PersonalitySampleDialog = React__namespace.forwardRef(
127
+ ({ turns, agentName = "Assistant", label = "Sample dialog", className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
128
+ "div",
129
+ {
130
+ ref,
131
+ role: "group",
132
+ "aria-label": label,
133
+ "data-variant": "sample-dialog",
134
+ className: utils.cn("flex flex-col gap-1.5", className),
135
+ ...props,
136
+ children: turns.map((turn, i) => /* @__PURE__ */ jsxRuntime.jsx(bubble.Bubble, { variant: turn.from === "user" ? "secondary" : "outline", align: turn.from === "user" ? "end" : "start", children: /* @__PURE__ */ jsxRuntime.jsxs(bubble.BubbleContent, { className: "text-xs leading-[18px]", children: [
137
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: turn.from === "user" ? "You said: " : `${agentName} said: ` }),
138
+ turn.text,
139
+ turn.fixed && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-1 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
140
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lock, { "aria-hidden": "true", className: "size-3" }),
141
+ "Same in every personality"
142
+ ] })
143
+ ] }) }, i))
144
+ }
145
+ )
146
+ );
147
+ PersonalitySampleDialog.displayName = "PersonalitySampleDialog";
148
+
149
+ exports.Personality = Personality;
150
+ exports.PersonalitySampleDialog = PersonalitySampleDialog;
151
+ exports.PersonalityVoiceCard = PersonalityVoiceCard;
152
+ exports.defaultTones = defaultTones;
153
+ //# sourceMappingURL=index.cjs.map
154
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["React","jsxs","cn","jsx","Bubble","BubbleContent","Lock"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { Lock } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Bubble, BubbleContent } from \"@nucleux/bubble\";\n\nexport interface ToneOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label. */\n label: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** One-line description of the writing style, used as the option's accessible description. */\n description?: string;\n}\n\n/** The default set of output tones. */\nexport const defaultTones: ToneOption[] = [\n { value: \"professional\", label: \"Professional\" },\n { value: \"friendly\", label: \"Friendly\" },\n { value: \"direct\", label: \"Direct\" },\n { value: \"playful\", label: \"Playful\" },\n];\n\nexport interface PersonalityProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Tone options. Defaults to {@link defaultTones}. */\n options?: ToneOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a tone is chosen. */\n onValueChange?: (value: string) => void;\n /** Group label. Defaults to `\"Tone\"`. */\n label?: React.ReactNode;\n}\n\n/**\n * The `tone-matrix`: a small set of tones the person can nudge the agent\n * into for a session — more formal, more casual. A radio group with\n * roving focus (arrow keys move and select, Home / End jump). Tone changes\n * how the agent writes, never what it may do: keep permission, caveat and\n * refusal strings outside it. Pair with `PersonalityVoiceCard` and\n * `PersonalitySampleDialog` to preview a voice.\n */\nexport const Personality = React.forwardRef<HTMLDivElement, PersonalityProps>(\n ({ options = defaultTones, value, defaultValue, onValueChange, label = \"Tone\", className, ...props }, ref) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue);\n const current = value !== undefined ? value : internal;\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const descId = React.useId();\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n const selectedIndex = options.findIndex((o) => o.value === current);\n const tabStop = selectedIndex >= 0 ? selectedIndex : 0;\n\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\n const last = options.length - 1;\n let next: number | null = null;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = index === last ? 0 : index + 1;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = index === 0 ? last : index - 1;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = last;\n if (next === null) return;\n e.preventDefault();\n refs.current[next]?.focus();\n select(options[next]!.value);\n };\n\n return (\n <div ref={ref} data-variant=\"tone-matrix\" className={cn(\"flex flex-col gap-2\", className)} {...props}>\n {label && <span className=\"text-xs font-medium text-muted-foreground\">{label}</span>}\n <div role=\"radiogroup\" aria-label={typeof label === \"string\" ? label : \"Tone\"} className=\"flex flex-wrap gap-1.5\">\n {options.map((option, index) => {\n const active = current === option.value;\n return (\n <button\n key={option.value}\n ref={(el) => {\n refs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-describedby={option.description ? `${descId}-${index}` : undefined}\n tabIndex={index === tabStop ? 0 : -1}\n onClick={() => select(option.value)}\n onKeyDown={(e) => onKeyDown(e, index)}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5\",\n active\n ? \"border-brand bg-brand-muted text-foreground\"\n : \"border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground\",\n )}\n >\n {option.icon}\n {option.label}\n {option.description && (\n <span id={`${descId}-${index}`} className=\"sr-only\">\n {option.description}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n);\nPersonality.displayName = \"Personality\";\n\nexport interface PersonalityVoiceCardProps extends React.HTMLAttributes<HTMLElement> {\n /** A sample utterance in the agent's voice. */\n quote: React.ReactNode;\n /** Voice tags — \"calm\", \"proactive\", \"never breathless\". */\n tags?: string[];\n /** Accessible name of the card. Default \"Voice\". */\n label?: string;\n}\n\n/**\n * The `voice-card`: one sample utterance plus voice tags, showing the\n * agent's tone at a glance — for greetings and onboarding.\n */\nexport const PersonalityVoiceCard = React.forwardRef<HTMLElement, PersonalityVoiceCardProps>(\n ({ quote, tags = [], label = \"Voice\", className, ...props }, ref) => (\n <figure\n ref={ref}\n aria-label={label}\n data-variant=\"voice-card\"\n className={cn(\"m-0 rounded-xl border border-border bg-background p-4\", className)}\n {...props}\n >\n <blockquote className=\"m-0 text-sm italic leading-5 text-foreground/80\">“{quote}”</blockquote>\n {tags.length > 0 && (\n <figcaption className=\"mt-3\">\n <span className=\"sr-only\">Voice: </span>\n <ul className=\"flex flex-wrap gap-1.5\">\n {tags.map((tag) => (\n <li\n key={tag}\n className=\"rounded-full border border-border bg-muted px-2.5 py-0.5 text-[11px] text-muted-foreground\"\n >\n {tag}\n </li>\n ))}\n </ul>\n </figcaption>\n )}\n </figure>\n ),\n);\nPersonalityVoiceCard.displayName = \"PersonalityVoiceCard\";\n\nexport interface PersonalitySampleTurn {\n /** Who speaks. */\n from: \"user\" | \"agent\";\n /** What they say. */\n text: React.ReactNode;\n /**\n * The line is fixed — a permission request, caveat or refusal that stays\n * the same in every personality. Marked with a lock and \"Same in every personality\".\n */\n fixed?: boolean;\n}\n\nexport interface PersonalitySampleDialogProps extends React.HTMLAttributes<HTMLDivElement> {\n /** The exchange, in order. */\n turns: PersonalitySampleTurn[];\n /** Agent name for screen readers (\"Aria said\"). Default \"Assistant\". */\n agentName?: string;\n /** Accessible name of the sample. Default \"Sample dialog\". */\n label?: string;\n}\n\n/**\n * The `sample-dialog`: a short exchange that demonstrates the voice in a\n * real interaction — the team keeps it as a regression test, and a\n * personality picker can preview it before applying. Lines marked `fixed`\n * carry a lock: they read the same in every personality.\n */\nexport const PersonalitySampleDialog = React.forwardRef<HTMLDivElement, PersonalitySampleDialogProps>(\n ({ turns, agentName = \"Assistant\", label = \"Sample dialog\", className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n aria-label={label}\n data-variant=\"sample-dialog\"\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n {turns.map((turn, i) => (\n <Bubble key={i} variant={turn.from === \"user\" ? \"secondary\" : \"outline\"} align={turn.from === \"user\" ? \"end\" : \"start\"}>\n <BubbleContent className=\"text-xs leading-[18px]\">\n <span className=\"sr-only\">{turn.from === \"user\" ? \"You said: \" : `${agentName} said: `}</span>\n {turn.text}\n {turn.fixed && (\n <span className=\"mt-1 flex items-center gap-1 text-[11px] text-muted-foreground\">\n <Lock aria-hidden=\"true\" className=\"size-3\" />\n Same in every personality\n </span>\n )}\n </BubbleContent>\n </Bubble>\n ))}\n </div>\n ),\n);\nPersonalitySampleDialog.displayName = \"PersonalitySampleDialog\";\n"]}
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+
3
+ interface ToneOption {
4
+ /** Stable value passed to `onValueChange`. */
5
+ value: string;
6
+ /** Display label. */
7
+ label: React.ReactNode;
8
+ /** Optional leading icon. */
9
+ icon?: React.ReactNode;
10
+ /** One-line description of the writing style, used as the option's accessible description. */
11
+ description?: string;
12
+ }
13
+ /** The default set of output tones. */
14
+ declare const defaultTones: ToneOption[];
15
+ interface PersonalityProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
16
+ /** Tone options. Defaults to {@link defaultTones}. */
17
+ options?: ToneOption[];
18
+ /** Selected value (controlled). */
19
+ value?: string;
20
+ /** Initial value (uncontrolled). */
21
+ defaultValue?: string;
22
+ /** Called when a tone is chosen. */
23
+ onValueChange?: (value: string) => void;
24
+ /** Group label. Defaults to `"Tone"`. */
25
+ label?: React.ReactNode;
26
+ }
27
+ /**
28
+ * The `tone-matrix`: a small set of tones the person can nudge the agent
29
+ * into for a session — more formal, more casual. A radio group with
30
+ * roving focus (arrow keys move and select, Home / End jump). Tone changes
31
+ * how the agent writes, never what it may do: keep permission, caveat and
32
+ * refusal strings outside it. Pair with `PersonalityVoiceCard` and
33
+ * `PersonalitySampleDialog` to preview a voice.
34
+ */
35
+ declare const Personality: React.ForwardRefExoticComponent<PersonalityProps & React.RefAttributes<HTMLDivElement>>;
36
+ interface PersonalityVoiceCardProps extends React.HTMLAttributes<HTMLElement> {
37
+ /** A sample utterance in the agent's voice. */
38
+ quote: React.ReactNode;
39
+ /** Voice tags — "calm", "proactive", "never breathless". */
40
+ tags?: string[];
41
+ /** Accessible name of the card. Default "Voice". */
42
+ label?: string;
43
+ }
44
+ /**
45
+ * The `voice-card`: one sample utterance plus voice tags, showing the
46
+ * agent's tone at a glance — for greetings and onboarding.
47
+ */
48
+ declare const PersonalityVoiceCard: React.ForwardRefExoticComponent<PersonalityVoiceCardProps & React.RefAttributes<HTMLElement>>;
49
+ interface PersonalitySampleTurn {
50
+ /** Who speaks. */
51
+ from: "user" | "agent";
52
+ /** What they say. */
53
+ text: React.ReactNode;
54
+ /**
55
+ * The line is fixed — a permission request, caveat or refusal that stays
56
+ * the same in every personality. Marked with a lock and "Same in every personality".
57
+ */
58
+ fixed?: boolean;
59
+ }
60
+ interface PersonalitySampleDialogProps extends React.HTMLAttributes<HTMLDivElement> {
61
+ /** The exchange, in order. */
62
+ turns: PersonalitySampleTurn[];
63
+ /** Agent name for screen readers ("Aria said"). Default "Assistant". */
64
+ agentName?: string;
65
+ /** Accessible name of the sample. Default "Sample dialog". */
66
+ label?: string;
67
+ }
68
+ /**
69
+ * The `sample-dialog`: a short exchange that demonstrates the voice in a
70
+ * real interaction — the team keeps it as a regression test, and a
71
+ * personality picker can preview it before applying. Lines marked `fixed`
72
+ * carry a lock: they read the same in every personality.
73
+ */
74
+ declare const PersonalitySampleDialog: React.ForwardRefExoticComponent<PersonalitySampleDialogProps & React.RefAttributes<HTMLDivElement>>;
75
+
76
+ export { Personality, type PersonalityProps, PersonalitySampleDialog, type PersonalitySampleDialogProps, type PersonalitySampleTurn, PersonalityVoiceCard, type PersonalityVoiceCardProps, type ToneOption, defaultTones };
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+
3
+ interface ToneOption {
4
+ /** Stable value passed to `onValueChange`. */
5
+ value: string;
6
+ /** Display label. */
7
+ label: React.ReactNode;
8
+ /** Optional leading icon. */
9
+ icon?: React.ReactNode;
10
+ /** One-line description of the writing style, used as the option's accessible description. */
11
+ description?: string;
12
+ }
13
+ /** The default set of output tones. */
14
+ declare const defaultTones: ToneOption[];
15
+ interface PersonalityProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
16
+ /** Tone options. Defaults to {@link defaultTones}. */
17
+ options?: ToneOption[];
18
+ /** Selected value (controlled). */
19
+ value?: string;
20
+ /** Initial value (uncontrolled). */
21
+ defaultValue?: string;
22
+ /** Called when a tone is chosen. */
23
+ onValueChange?: (value: string) => void;
24
+ /** Group label. Defaults to `"Tone"`. */
25
+ label?: React.ReactNode;
26
+ }
27
+ /**
28
+ * The `tone-matrix`: a small set of tones the person can nudge the agent
29
+ * into for a session — more formal, more casual. A radio group with
30
+ * roving focus (arrow keys move and select, Home / End jump). Tone changes
31
+ * how the agent writes, never what it may do: keep permission, caveat and
32
+ * refusal strings outside it. Pair with `PersonalityVoiceCard` and
33
+ * `PersonalitySampleDialog` to preview a voice.
34
+ */
35
+ declare const Personality: React.ForwardRefExoticComponent<PersonalityProps & React.RefAttributes<HTMLDivElement>>;
36
+ interface PersonalityVoiceCardProps extends React.HTMLAttributes<HTMLElement> {
37
+ /** A sample utterance in the agent's voice. */
38
+ quote: React.ReactNode;
39
+ /** Voice tags — "calm", "proactive", "never breathless". */
40
+ tags?: string[];
41
+ /** Accessible name of the card. Default "Voice". */
42
+ label?: string;
43
+ }
44
+ /**
45
+ * The `voice-card`: one sample utterance plus voice tags, showing the
46
+ * agent's tone at a glance — for greetings and onboarding.
47
+ */
48
+ declare const PersonalityVoiceCard: React.ForwardRefExoticComponent<PersonalityVoiceCardProps & React.RefAttributes<HTMLElement>>;
49
+ interface PersonalitySampleTurn {
50
+ /** Who speaks. */
51
+ from: "user" | "agent";
52
+ /** What they say. */
53
+ text: React.ReactNode;
54
+ /**
55
+ * The line is fixed — a permission request, caveat or refusal that stays
56
+ * the same in every personality. Marked with a lock and "Same in every personality".
57
+ */
58
+ fixed?: boolean;
59
+ }
60
+ interface PersonalitySampleDialogProps extends React.HTMLAttributes<HTMLDivElement> {
61
+ /** The exchange, in order. */
62
+ turns: PersonalitySampleTurn[];
63
+ /** Agent name for screen readers ("Aria said"). Default "Assistant". */
64
+ agentName?: string;
65
+ /** Accessible name of the sample. Default "Sample dialog". */
66
+ label?: string;
67
+ }
68
+ /**
69
+ * The `sample-dialog`: a short exchange that demonstrates the voice in a
70
+ * real interaction — the team keeps it as a regression test, and a
71
+ * personality picker can preview it before applying. Lines marked `fixed`
72
+ * carry a lock: they read the same in every personality.
73
+ */
74
+ declare const PersonalitySampleDialog: React.ForwardRefExoticComponent<PersonalitySampleDialogProps & React.RefAttributes<HTMLDivElement>>;
75
+
76
+ export { Personality, type PersonalityProps, PersonalitySampleDialog, type PersonalitySampleDialogProps, type PersonalitySampleTurn, PersonalityVoiceCard, type PersonalityVoiceCardProps, type ToneOption, defaultTones };
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { Lock } from 'lucide-react';
4
+ import { cn } from '@nucleux/utils';
5
+ import { Bubble, BubbleContent } from '@nucleux/bubble';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ // src/index.tsx
9
+ var defaultTones = [
10
+ { value: "professional", label: "Professional" },
11
+ { value: "friendly", label: "Friendly" },
12
+ { value: "direct", label: "Direct" },
13
+ { value: "playful", label: "Playful" }
14
+ ];
15
+ var Personality = React.forwardRef(
16
+ ({ options = defaultTones, value, defaultValue, onValueChange, label = "Tone", className, ...props }, ref) => {
17
+ const [internal, setInternal] = React.useState(defaultValue);
18
+ const current = value !== void 0 ? value : internal;
19
+ const refs = React.useRef([]);
20
+ const descId = React.useId();
21
+ const select = (next) => {
22
+ if (value === void 0) setInternal(next);
23
+ onValueChange?.(next);
24
+ };
25
+ const selectedIndex = options.findIndex((o) => o.value === current);
26
+ const tabStop = selectedIndex >= 0 ? selectedIndex : 0;
27
+ const onKeyDown = (e, index) => {
28
+ const last = options.length - 1;
29
+ let next = null;
30
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") next = index === last ? 0 : index + 1;
31
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = index === 0 ? last : index - 1;
32
+ else if (e.key === "Home") next = 0;
33
+ else if (e.key === "End") next = last;
34
+ if (next === null) return;
35
+ e.preventDefault();
36
+ refs.current[next]?.focus();
37
+ select(options[next].value);
38
+ };
39
+ return /* @__PURE__ */ jsxs("div", { ref, "data-variant": "tone-matrix", className: cn("flex flex-col gap-2", className), ...props, children: [
40
+ label && /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
41
+ /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": typeof label === "string" ? label : "Tone", className: "flex flex-wrap gap-1.5", children: options.map((option, index) => {
42
+ const active = current === option.value;
43
+ return /* @__PURE__ */ jsxs(
44
+ "button",
45
+ {
46
+ ref: (el) => {
47
+ refs.current[index] = el;
48
+ },
49
+ type: "button",
50
+ role: "radio",
51
+ "aria-checked": active,
52
+ "aria-describedby": option.description ? `${descId}-${index}` : void 0,
53
+ tabIndex: index === tabStop ? 0 : -1,
54
+ onClick: () => select(option.value),
55
+ onKeyDown: (e) => onKeyDown(e, index),
56
+ className: cn(
57
+ "inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5",
58
+ active ? "border-brand bg-brand-muted text-foreground" : "border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground"
59
+ ),
60
+ children: [
61
+ option.icon,
62
+ option.label,
63
+ option.description && /* @__PURE__ */ jsx("span", { id: `${descId}-${index}`, className: "sr-only", children: option.description })
64
+ ]
65
+ },
66
+ option.value
67
+ );
68
+ }) })
69
+ ] });
70
+ }
71
+ );
72
+ Personality.displayName = "Personality";
73
+ var PersonalityVoiceCard = React.forwardRef(
74
+ ({ quote, tags = [], label = "Voice", className, ...props }, ref) => /* @__PURE__ */ jsxs(
75
+ "figure",
76
+ {
77
+ ref,
78
+ "aria-label": label,
79
+ "data-variant": "voice-card",
80
+ className: cn("m-0 rounded-xl border border-border bg-background p-4", className),
81
+ ...props,
82
+ children: [
83
+ /* @__PURE__ */ jsxs("blockquote", { className: "m-0 text-sm italic leading-5 text-foreground/80", children: [
84
+ "\u201C",
85
+ quote,
86
+ "\u201D"
87
+ ] }),
88
+ tags.length > 0 && /* @__PURE__ */ jsxs("figcaption", { className: "mt-3", children: [
89
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Voice: " }),
90
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-1.5", children: tags.map((tag) => /* @__PURE__ */ jsx(
91
+ "li",
92
+ {
93
+ className: "rounded-full border border-border bg-muted px-2.5 py-0.5 text-[11px] text-muted-foreground",
94
+ children: tag
95
+ },
96
+ tag
97
+ )) })
98
+ ] })
99
+ ]
100
+ }
101
+ )
102
+ );
103
+ PersonalityVoiceCard.displayName = "PersonalityVoiceCard";
104
+ var PersonalitySampleDialog = React.forwardRef(
105
+ ({ turns, agentName = "Assistant", label = "Sample dialog", className, ...props }, ref) => /* @__PURE__ */ jsx(
106
+ "div",
107
+ {
108
+ ref,
109
+ role: "group",
110
+ "aria-label": label,
111
+ "data-variant": "sample-dialog",
112
+ className: cn("flex flex-col gap-1.5", className),
113
+ ...props,
114
+ children: turns.map((turn, i) => /* @__PURE__ */ jsx(Bubble, { variant: turn.from === "user" ? "secondary" : "outline", align: turn.from === "user" ? "end" : "start", children: /* @__PURE__ */ jsxs(BubbleContent, { className: "text-xs leading-[18px]", children: [
115
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: turn.from === "user" ? "You said: " : `${agentName} said: ` }),
116
+ turn.text,
117
+ turn.fixed && /* @__PURE__ */ jsxs("span", { className: "mt-1 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
118
+ /* @__PURE__ */ jsx(Lock, { "aria-hidden": "true", className: "size-3" }),
119
+ "Same in every personality"
120
+ ] })
121
+ ] }) }, i))
122
+ }
123
+ )
124
+ );
125
+ PersonalitySampleDialog.displayName = "PersonalitySampleDialog";
126
+
127
+ export { Personality, PersonalitySampleDialog, PersonalityVoiceCard, defaultTones };
128
+ //# sourceMappingURL=index.js.map
129
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { Lock } from \"lucide-react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Bubble, BubbleContent } from \"@nucleux/bubble\";\n\nexport interface ToneOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label. */\n label: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** One-line description of the writing style, used as the option's accessible description. */\n description?: string;\n}\n\n/** The default set of output tones. */\nexport const defaultTones: ToneOption[] = [\n { value: \"professional\", label: \"Professional\" },\n { value: \"friendly\", label: \"Friendly\" },\n { value: \"direct\", label: \"Direct\" },\n { value: \"playful\", label: \"Playful\" },\n];\n\nexport interface PersonalityProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Tone options. Defaults to {@link defaultTones}. */\n options?: ToneOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a tone is chosen. */\n onValueChange?: (value: string) => void;\n /** Group label. Defaults to `\"Tone\"`. */\n label?: React.ReactNode;\n}\n\n/**\n * The `tone-matrix`: a small set of tones the person can nudge the agent\n * into for a session — more formal, more casual. A radio group with\n * roving focus (arrow keys move and select, Home / End jump). Tone changes\n * how the agent writes, never what it may do: keep permission, caveat and\n * refusal strings outside it. Pair with `PersonalityVoiceCard` and\n * `PersonalitySampleDialog` to preview a voice.\n */\nexport const Personality = React.forwardRef<HTMLDivElement, PersonalityProps>(\n ({ options = defaultTones, value, defaultValue, onValueChange, label = \"Tone\", className, ...props }, ref) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue);\n const current = value !== undefined ? value : internal;\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const descId = React.useId();\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n const selectedIndex = options.findIndex((o) => o.value === current);\n const tabStop = selectedIndex >= 0 ? selectedIndex : 0;\n\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\n const last = options.length - 1;\n let next: number | null = null;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = index === last ? 0 : index + 1;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = index === 0 ? last : index - 1;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = last;\n if (next === null) return;\n e.preventDefault();\n refs.current[next]?.focus();\n select(options[next]!.value);\n };\n\n return (\n <div ref={ref} data-variant=\"tone-matrix\" className={cn(\"flex flex-col gap-2\", className)} {...props}>\n {label && <span className=\"text-xs font-medium text-muted-foreground\">{label}</span>}\n <div role=\"radiogroup\" aria-label={typeof label === \"string\" ? label : \"Tone\"} className=\"flex flex-wrap gap-1.5\">\n {options.map((option, index) => {\n const active = current === option.value;\n return (\n <button\n key={option.value}\n ref={(el) => {\n refs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-describedby={option.description ? `${descId}-${index}` : undefined}\n tabIndex={index === tabStop ? 0 : -1}\n onClick={() => select(option.value)}\n onKeyDown={(e) => onKeyDown(e, index)}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5\",\n active\n ? \"border-brand bg-brand-muted text-foreground\"\n : \"border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground\",\n )}\n >\n {option.icon}\n {option.label}\n {option.description && (\n <span id={`${descId}-${index}`} className=\"sr-only\">\n {option.description}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n);\nPersonality.displayName = \"Personality\";\n\nexport interface PersonalityVoiceCardProps extends React.HTMLAttributes<HTMLElement> {\n /** A sample utterance in the agent's voice. */\n quote: React.ReactNode;\n /** Voice tags — \"calm\", \"proactive\", \"never breathless\". */\n tags?: string[];\n /** Accessible name of the card. Default \"Voice\". */\n label?: string;\n}\n\n/**\n * The `voice-card`: one sample utterance plus voice tags, showing the\n * agent's tone at a glance — for greetings and onboarding.\n */\nexport const PersonalityVoiceCard = React.forwardRef<HTMLElement, PersonalityVoiceCardProps>(\n ({ quote, tags = [], label = \"Voice\", className, ...props }, ref) => (\n <figure\n ref={ref}\n aria-label={label}\n data-variant=\"voice-card\"\n className={cn(\"m-0 rounded-xl border border-border bg-background p-4\", className)}\n {...props}\n >\n <blockquote className=\"m-0 text-sm italic leading-5 text-foreground/80\">“{quote}”</blockquote>\n {tags.length > 0 && (\n <figcaption className=\"mt-3\">\n <span className=\"sr-only\">Voice: </span>\n <ul className=\"flex flex-wrap gap-1.5\">\n {tags.map((tag) => (\n <li\n key={tag}\n className=\"rounded-full border border-border bg-muted px-2.5 py-0.5 text-[11px] text-muted-foreground\"\n >\n {tag}\n </li>\n ))}\n </ul>\n </figcaption>\n )}\n </figure>\n ),\n);\nPersonalityVoiceCard.displayName = \"PersonalityVoiceCard\";\n\nexport interface PersonalitySampleTurn {\n /** Who speaks. */\n from: \"user\" | \"agent\";\n /** What they say. */\n text: React.ReactNode;\n /**\n * The line is fixed — a permission request, caveat or refusal that stays\n * the same in every personality. Marked with a lock and \"Same in every personality\".\n */\n fixed?: boolean;\n}\n\nexport interface PersonalitySampleDialogProps extends React.HTMLAttributes<HTMLDivElement> {\n /** The exchange, in order. */\n turns: PersonalitySampleTurn[];\n /** Agent name for screen readers (\"Aria said\"). Default \"Assistant\". */\n agentName?: string;\n /** Accessible name of the sample. Default \"Sample dialog\". */\n label?: string;\n}\n\n/**\n * The `sample-dialog`: a short exchange that demonstrates the voice in a\n * real interaction — the team keeps it as a regression test, and a\n * personality picker can preview it before applying. Lines marked `fixed`\n * carry a lock: they read the same in every personality.\n */\nexport const PersonalitySampleDialog = React.forwardRef<HTMLDivElement, PersonalitySampleDialogProps>(\n ({ turns, agentName = \"Assistant\", label = \"Sample dialog\", className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n aria-label={label}\n data-variant=\"sample-dialog\"\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n {turns.map((turn, i) => (\n <Bubble key={i} variant={turn.from === \"user\" ? \"secondary\" : \"outline\"} align={turn.from === \"user\" ? \"end\" : \"start\"}>\n <BubbleContent className=\"text-xs leading-[18px]\">\n <span className=\"sr-only\">{turn.from === \"user\" ? \"You said: \" : `${agentName} said: `}</span>\n {turn.text}\n {turn.fixed && (\n <span className=\"mt-1 flex items-center gap-1 text-[11px] text-muted-foreground\">\n <Lock aria-hidden=\"true\" className=\"size-3\" />\n Same in every personality\n </span>\n )}\n </BubbleContent>\n </Bubble>\n ))}\n </div>\n ),\n);\nPersonalitySampleDialog.displayName = \"PersonalitySampleDialog\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@nucleux/personality",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.6.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Personality — the voice and tone the assistant speaks in — Nucleux agentic UI for React.",
7
+ "keywords": [
8
+ "nucleux",
9
+ "react",
10
+ "ui",
11
+ "components",
12
+ "agentic",
13
+ "ai",
14
+ "tailwind",
15
+ "personality"
16
+ ],
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "peerDependencies": {
32
+ "react": ">=18",
33
+ "react-dom": ">=18"
34
+ },
35
+ "dependencies": {
36
+ "@nucleux/bubble": "0.6.0",
37
+ "@nucleux/utils": "0.6.0",
38
+ "lucide-react": "^0.469.0"
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.3.5",
42
+ "typescript": "^5.7.2",
43
+ "@types/react": "^18.3.18",
44
+ "react": "^18.3.1"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "license": "MIT",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/intelligaia-open-labs/nucleux.git",
53
+ "directory": "packages/personality"
54
+ },
55
+ "homepage": "https://github.com/intelligaia-open-labs/nucleux#readme",
56
+ "scripts": {
57
+ "build": "tsup"
58
+ }
6
59
  }