@nucleux/md3-button-group 0.0.0-stage → 0.7.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,39 @@
1
- # Temporary Holding Version
1
+ # @nucleux/md3-button-group
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
+ Material 3 button group — 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
+
8
+ ## Exports
9
+
10
+ - **`Md3ButtonGroup`** — root (`variant="standard" | "connected"`, `size="xs" | "sm" | "md" | "lg" | "xl"`, `shape="round" | "square"`, `buttonVariant`, `selectionMode="none" | "single" | "multiple"`, `selectionRequired`, `value` / `defaultValue` / `onValueChange`)
11
+ - **`Md3ButtonGroupItem`** — a button in the group (`value`, `variant`, `icon`); toggles with `aria-pressed` when the group is selectable
12
+ - Types: `Md3ButtonGroupProps`, `Md3ButtonGroupItemProps`, `Md3ButtonGroupVariant`, `Md3ButtonGroupSize`, `Md3ButtonGroupShape`, `Md3ButtonGroupSelectionMode`, `Md3ButtonGroupItemVariant`
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @nucleux/md3-button-group @nucleux/tokens
18
+ ```
19
+
20
+ `react` / `react-dom` are peer dependencies.
21
+
22
+ ## Usage
23
+
24
+ ```tsx
25
+ import { Md3ButtonGroup, Md3ButtonGroupItem } from "@nucleux/md3-button-group";
26
+
27
+ <Md3ButtonGroup aria-label="Time range" variant="connected" selectionMode="single" defaultValue="week">
28
+ <Md3ButtonGroupItem value="day">Day</Md3ButtonGroupItem>
29
+ <Md3ButtonGroupItem value="week">Week</Md3ButtonGroupItem>
30
+ <Md3ButtonGroupItem value="month">Month</Md3ButtonGroupItem>
31
+ </Md3ButtonGroup>
32
+ ```
33
+
34
+ Requires the theme once in your app: `import "@nucleux/tokens/styles.css";`
35
+ and the Tailwind preset from `@nucleux/tokens/preset`.
36
+
37
+ ## License
38
+
39
+ MIT © Nucleux
package/dist/index.cjs ADDED
@@ -0,0 +1,225 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+ var utils = require('@nucleux/utils');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
27
+
28
+ // src/index.tsx
29
+ var ButtonGroupContext = React__namespace.createContext(null);
30
+ var asArray = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
31
+ var gaps = {
32
+ xs: "gap-[18px]",
33
+ sm: "gap-3",
34
+ md: "gap-2",
35
+ lg: "gap-2",
36
+ xl: "gap-2"
37
+ };
38
+ var Md3ButtonGroup = React__namespace.forwardRef(
39
+ ({
40
+ variant = "standard",
41
+ size = "sm",
42
+ shape = "round",
43
+ buttonVariant = "filled",
44
+ selectionMode = "none",
45
+ selectionRequired = false,
46
+ value,
47
+ defaultValue,
48
+ onValueChange,
49
+ className,
50
+ children,
51
+ ...props
52
+ }, ref) => {
53
+ const multiple = selectionMode === "multiple";
54
+ const isControlled = value !== void 0;
55
+ const [internal, setInternal] = React__namespace.useState(asArray(defaultValue));
56
+ const current = isControlled ? asArray(value) : internal;
57
+ const toggle = (v) => {
58
+ let next;
59
+ if (multiple) next = current.includes(v) ? current.filter((x) => x !== v) : [...current, v];
60
+ else next = current[0] === v ? [] : [v];
61
+ if (selectionRequired && next.length === 0) return;
62
+ if (!isControlled) setInternal(next);
63
+ onValueChange?.(multiple ? next : next[0] ?? "");
64
+ };
65
+ return /* @__PURE__ */ jsxRuntime.jsx(
66
+ "div",
67
+ {
68
+ ref,
69
+ role: "group",
70
+ className: utils.cn(
71
+ "inline-flex items-center",
72
+ variant === "connected" ? "gap-0.5" : gaps[size],
73
+ className
74
+ ),
75
+ ...props,
76
+ children: /* @__PURE__ */ jsxRuntime.jsx(
77
+ ButtonGroupContext.Provider,
78
+ {
79
+ value: {
80
+ variant,
81
+ size,
82
+ shape,
83
+ buttonVariant,
84
+ selectable: selectionMode !== "none",
85
+ value: current,
86
+ toggle
87
+ },
88
+ children
89
+ }
90
+ )
91
+ }
92
+ );
93
+ }
94
+ );
95
+ Md3ButtonGroup.displayName = "Md3ButtonGroup";
96
+ var sizes = {
97
+ xs: { base: "h-8 gap-1 text-sm [&_svg]:size-5", label: "px-3", labelPressed: "motion-safe:active:px-4", iconOnly: "w-8", iconOnlyPressed: "motion-safe:active:w-[37px]" },
98
+ sm: { base: "h-10 gap-2 text-sm [&_svg]:size-5", label: "px-4", labelPressed: "motion-safe:active:px-5", iconOnly: "w-10", iconOnlyPressed: "motion-safe:active:w-[46px]" },
99
+ md: { base: "h-14 gap-2 text-base [&_svg]:size-6", label: "px-6", labelPressed: "motion-safe:active:px-8", iconOnly: "w-14", iconOnlyPressed: "motion-safe:active:w-16" },
100
+ lg: { base: "h-24 gap-3 text-2xl [&_svg]:size-8", label: "px-12", labelPressed: "motion-safe:active:px-14", iconOnly: "w-24", iconOnlyPressed: "motion-safe:active:w-[110px]" },
101
+ xl: { base: "h-[136px] gap-4 text-[32px] [&_svg]:size-10", label: "px-16", labelPressed: "motion-safe:active:px-[72px]", iconOnly: "w-[136px]", iconOnlyPressed: "motion-safe:active:w-[156px]" }
102
+ };
103
+ var squareRadius = {
104
+ xs: "rounded-md-md",
105
+ sm: "rounded-md-md",
106
+ md: "rounded-md-lg",
107
+ lg: "rounded-md-xl",
108
+ xl: "rounded-md-xl"
109
+ };
110
+ var pressedRadius = {
111
+ xs: "active:rounded-md-sm",
112
+ sm: "active:rounded-md-sm",
113
+ md: "active:rounded-md-md",
114
+ lg: "active:rounded-md-lg",
115
+ xl: "active:rounded-md-lg"
116
+ };
117
+ var innerRadius = {
118
+ xs: "rounded-md-xs",
119
+ sm: "rounded-md-sm",
120
+ md: "rounded-md-sm",
121
+ lg: "rounded-md-lg",
122
+ xl: "rounded-[20px]"
123
+ };
124
+ var outerRadius = {
125
+ round: {
126
+ xs: "first:rounded-l-full last:rounded-r-full",
127
+ sm: "first:rounded-l-full last:rounded-r-full",
128
+ md: "first:rounded-l-full last:rounded-r-full",
129
+ lg: "first:rounded-l-full last:rounded-r-full",
130
+ xl: "first:rounded-l-full last:rounded-r-full"
131
+ },
132
+ square: {
133
+ xs: "first:rounded-l-md-md last:rounded-r-md-md",
134
+ sm: "first:rounded-l-md-md last:rounded-r-md-md",
135
+ md: "first:rounded-l-md-lg last:rounded-r-md-lg",
136
+ lg: "first:rounded-l-md-xl last:rounded-r-md-xl",
137
+ xl: "first:rounded-l-md-xl last:rounded-r-md-xl"
138
+ }
139
+ };
140
+ var actionColors = {
141
+ filled: "bg-md-primary text-md-on-primary hover:shadow-md-1",
142
+ tonal: "bg-md-secondary-container text-md-on-secondary-container hover:shadow-md-1",
143
+ elevated: "bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2",
144
+ outlined: "border border-md-outline bg-transparent text-md-primary",
145
+ text: "bg-transparent text-md-primary"
146
+ };
147
+ var toggleColors = {
148
+ filled: ["bg-md-surface-container text-md-on-surface-variant", "bg-md-primary text-md-on-primary"],
149
+ tonal: [
150
+ "bg-md-secondary-container text-md-on-secondary-container",
151
+ "bg-md-secondary text-md-on-secondary"
152
+ ],
153
+ elevated: [
154
+ "bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2",
155
+ "bg-md-primary text-md-on-primary shadow-md-1 hover:shadow-md-2"
156
+ ],
157
+ outlined: [
158
+ "border border-md-outline-variant bg-transparent text-md-on-surface-variant",
159
+ "border border-md-inverse-surface bg-md-inverse-surface text-md-inverse-on-surface"
160
+ ],
161
+ text: ["bg-transparent text-md-primary", "bg-md-secondary-container text-md-on-secondary-container"]
162
+ };
163
+ var Md3ButtonGroupItem = React__namespace.forwardRef(
164
+ ({ value, variant, icon, className, children, onClick, type = "button", ...props }, ref) => {
165
+ const ctx = React__namespace.useContext(ButtonGroupContext);
166
+ if (!ctx) throw new Error("Md3ButtonGroupItem must be used within a Md3ButtonGroup");
167
+ const toggleable = ctx.selectable && value !== void 0;
168
+ const selected = toggleable && ctx.value.includes(value);
169
+ const iconOnly = icon != null && (children == null || children === false);
170
+ const s = sizes[ctx.size];
171
+ const colorVariant = variant ?? ctx.buttonVariant;
172
+ let shapeClasses;
173
+ if (ctx.variant === "connected") {
174
+ shapeClasses = selected ? "rounded-full" : utils.cn(innerRadius[ctx.size], outerRadius[ctx.shape][ctx.size]);
175
+ } else {
176
+ const round = ctx.shape === "round" !== selected;
177
+ shapeClasses = utils.cn(round ? "rounded-full" : squareRadius[ctx.size], pressedRadius[ctx.size]);
178
+ }
179
+ return /* @__PURE__ */ jsxRuntime.jsxs(
180
+ "button",
181
+ {
182
+ ref,
183
+ type,
184
+ "aria-pressed": toggleable ? selected : props["aria-pressed"],
185
+ "data-state": toggleable ? selected ? "on" : "off" : void 0,
186
+ onClick: (e) => {
187
+ onClick?.(e);
188
+ if (!e.defaultPrevented && toggleable) ctx.toggle(value);
189
+ },
190
+ className: utils.cn(
191
+ // Layout + typography
192
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium",
193
+ s.base,
194
+ iconOnly ? s.iconOnly : s.label,
195
+ // Standard groups widen the pressed button (no movement under reduced motion)
196
+ ctx.variant === "standard" && (iconOnly ? s.iconOnlyPressed : s.labelPressed),
197
+ ctx.variant === "connected" && "flex-1",
198
+ // Shape morph + colour change
199
+ "transition-[border-radius,padding,width,background-color,color,box-shadow] duration-200 ease-out",
200
+ "motion-reduce:transition-[background-color,color,box-shadow]",
201
+ shapeClasses,
202
+ toggleable ? toggleColors[colorVariant][selected ? 1 : 0] : actionColors[colorVariant],
203
+ // State layer (Material 3): on-color overlay at 8% hover / 12% focus+press
204
+ "before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity before:content-['']",
205
+ "hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]",
206
+ // Focus ring + disabled
207
+ "outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-md-primary focus-visible:ring-offset-2 focus-visible:ring-offset-md-surface",
208
+ "disabled:pointer-events-none disabled:opacity-[0.38] disabled:shadow-none",
209
+ className
210
+ ),
211
+ ...props,
212
+ children: [
213
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex shrink-0", children: icon }),
214
+ !iconOnly && children != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative", children })
215
+ ]
216
+ }
217
+ );
218
+ }
219
+ );
220
+ Md3ButtonGroupItem.displayName = "Md3ButtonGroupItem";
221
+
222
+ exports.Md3ButtonGroup = Md3ButtonGroup;
223
+ exports.Md3ButtonGroupItem = Md3ButtonGroupItem;
224
+ //# sourceMappingURL=index.cjs.map
225
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["React","jsx","cn","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@nucleux/utils\";\n\nexport type Md3ButtonGroupVariant = \"standard\" | \"connected\";\nexport type Md3ButtonGroupSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type Md3ButtonGroupShape = \"round\" | \"square\";\nexport type Md3ButtonGroupSelectionMode = \"none\" | \"single\" | \"multiple\";\nexport type Md3ButtonGroupItemVariant = \"filled\" | \"tonal\" | \"elevated\" | \"outlined\" | \"text\";\n\ninterface ButtonGroupContextValue {\n variant: Md3ButtonGroupVariant;\n size: Md3ButtonGroupSize;\n shape: Md3ButtonGroupShape;\n buttonVariant: Md3ButtonGroupItemVariant;\n selectable: boolean;\n value: string[];\n toggle: (v: string) => void;\n}\nconst ButtonGroupContext = React.createContext<ButtonGroupContextValue | null>(null);\n\nexport interface Md3ButtonGroupProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\" | \"defaultValue\"> {\n /** `standard` keeps each button's own shape; `connected` joins them with 2px gaps and small inner corners. */\n variant?: Md3ButtonGroupVariant;\n /** Button size applied to every item: XS 32px, S 40px, M 56px, L 96px, XL 136px tall. */\n size?: Md3ButtonGroupSize;\n /** Default shape of the buttons. Selected toggle buttons morph to the other shape. */\n shape?: Md3ButtonGroupShape;\n /** Colour style applied to items that don't set their own `variant`. */\n buttonVariant?: Md3ButtonGroupItemVariant;\n /** `none` for plain action buttons, `single`/`multiple` to make items with a `value` toggle. */\n selectionMode?: Md3ButtonGroupSelectionMode;\n /** When true, the last selected item can't be deselected. */\n selectionRequired?: boolean;\n value?: string | string[];\n defaultValue?: string | string[];\n /** Called with a string in `single` mode (\"\" when cleared) and a string array in `multiple` mode. */\n onValueChange?: (value: string | string[]) => void;\n}\n\nconst asArray = (v: string | string[] | undefined) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);\n\nconst gaps: Record<Md3ButtonGroupSize, string> = {\n xs: \"gap-[18px]\",\n sm: \"gap-3\",\n md: \"gap-2\",\n lg: \"gap-2\",\n xl: \"gap-2\",\n};\n\n/**\n * A Material 3 Expressive button group: an invisible container that spaces related\n * buttons and adjusts their shape. The `standard` variant keeps each button's shape\n * and widens a button while it is pressed; the `connected` variant (the successor to\n * segmented buttons) joins buttons with 2px gaps, small inner corners and fully\n * rounded outer corners. Set `selectionMode` to turn items with a `value` into\n * single- or multi-select toggle buttons whose shape morphs when selected.\n * Compose with Md3ButtonGroupItem.\n */\nexport const Md3ButtonGroup = React.forwardRef<HTMLDivElement, Md3ButtonGroupProps>(\n (\n {\n variant = \"standard\",\n size = \"sm\",\n shape = \"round\",\n buttonVariant = \"filled\",\n selectionMode = \"none\",\n selectionRequired = false,\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const multiple = selectionMode === \"multiple\";\n const isControlled = value !== undefined;\n const [internal, setInternal] = React.useState<string[]>(asArray(defaultValue));\n const current = isControlled ? asArray(value) : internal;\n\n const toggle = (v: string) => {\n let next: string[];\n if (multiple) next = current.includes(v) ? current.filter((x) => x !== v) : [...current, v];\n else next = current[0] === v ? [] : [v];\n if (selectionRequired && next.length === 0) return;\n if (!isControlled) setInternal(next);\n onValueChange?.(multiple ? next : (next[0] ?? \"\"));\n };\n\n return (\n <div\n ref={ref}\n role=\"group\"\n className={cn(\n \"inline-flex items-center\",\n variant === \"connected\" ? \"gap-0.5\" : gaps[size],\n className,\n )}\n {...props}\n >\n <ButtonGroupContext.Provider\n value={{\n variant,\n size,\n shape,\n buttonVariant,\n selectable: selectionMode !== \"none\",\n value: current,\n toggle,\n }}\n >\n {children}\n </ButtonGroupContext.Provider>\n </div>\n );\n },\n);\nMd3ButtonGroup.displayName = \"Md3ButtonGroup\";\n\nexport interface Md3ButtonGroupItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Identifies the item for selection. Items without a value act as plain buttons. */\n value?: string;\n /** Colour style; defaults to the group's `buttonVariant`. */\n variant?: Md3ButtonGroupItemVariant;\n /** Leading icon. Pass an icon with no children (and an `aria-label`) for an icon button. */\n icon?: React.ReactNode;\n}\n\nconst sizes: Record<\n Md3ButtonGroupSize,\n { base: string; label: string; labelPressed: string; iconOnly: string; iconOnlyPressed: string }\n> = {\n xs: { base: \"h-8 gap-1 text-sm [&_svg]:size-5\", label: \"px-3\", labelPressed: \"motion-safe:active:px-4\", iconOnly: \"w-8\", iconOnlyPressed: \"motion-safe:active:w-[37px]\" },\n sm: { base: \"h-10 gap-2 text-sm [&_svg]:size-5\", label: \"px-4\", labelPressed: \"motion-safe:active:px-5\", iconOnly: \"w-10\", iconOnlyPressed: \"motion-safe:active:w-[46px]\" },\n md: { base: \"h-14 gap-2 text-base [&_svg]:size-6\", label: \"px-6\", labelPressed: \"motion-safe:active:px-8\", iconOnly: \"w-14\", iconOnlyPressed: \"motion-safe:active:w-16\" },\n lg: { base: \"h-24 gap-3 text-2xl [&_svg]:size-8\", label: \"px-12\", labelPressed: \"motion-safe:active:px-14\", iconOnly: \"w-24\", iconOnlyPressed: \"motion-safe:active:w-[110px]\" },\n xl: { base: \"h-[136px] gap-4 text-[32px] [&_svg]:size-10\", label: \"px-16\", labelPressed: \"motion-safe:active:px-[72px]\", iconOnly: \"w-[136px]\", iconOnlyPressed: \"motion-safe:active:w-[156px]\" },\n};\n\nconst squareRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"rounded-md-md\",\n sm: \"rounded-md-md\",\n md: \"rounded-md-lg\",\n lg: \"rounded-md-xl\",\n xl: \"rounded-md-xl\",\n};\n\nconst pressedRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"active:rounded-md-sm\",\n sm: \"active:rounded-md-sm\",\n md: \"active:rounded-md-md\",\n lg: \"active:rounded-md-lg\",\n xl: \"active:rounded-md-lg\",\n};\n\nconst innerRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"rounded-md-xs\",\n sm: \"rounded-md-sm\",\n md: \"rounded-md-sm\",\n lg: \"rounded-md-lg\",\n xl: \"rounded-[20px]\",\n};\n\nconst outerRadius: Record<Md3ButtonGroupShape, Record<Md3ButtonGroupSize, string>> = {\n round: {\n xs: \"first:rounded-l-full last:rounded-r-full\",\n sm: \"first:rounded-l-full last:rounded-r-full\",\n md: \"first:rounded-l-full last:rounded-r-full\",\n lg: \"first:rounded-l-full last:rounded-r-full\",\n xl: \"first:rounded-l-full last:rounded-r-full\",\n },\n square: {\n xs: \"first:rounded-l-md-md last:rounded-r-md-md\",\n sm: \"first:rounded-l-md-md last:rounded-r-md-md\",\n md: \"first:rounded-l-md-lg last:rounded-r-md-lg\",\n lg: \"first:rounded-l-md-xl last:rounded-r-md-xl\",\n xl: \"first:rounded-l-md-xl last:rounded-r-md-xl\",\n },\n};\n\n/** Colours for plain action buttons (same as Md3Button). */\nconst actionColors: Record<Md3ButtonGroupItemVariant, string> = {\n filled: \"bg-md-primary text-md-on-primary hover:shadow-md-1\",\n tonal: \"bg-md-secondary-container text-md-on-secondary-container hover:shadow-md-1\",\n elevated: \"bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2\",\n outlined: \"border border-md-outline bg-transparent text-md-primary\",\n text: \"bg-transparent text-md-primary\",\n};\n\n/** Colours for toggle buttons: [unselected, selected]. */\nconst toggleColors: Record<Md3ButtonGroupItemVariant, [string, string]> = {\n filled: [\"bg-md-surface-container text-md-on-surface-variant\", \"bg-md-primary text-md-on-primary\"],\n tonal: [\n \"bg-md-secondary-container text-md-on-secondary-container\",\n \"bg-md-secondary text-md-on-secondary\",\n ],\n elevated: [\n \"bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2\",\n \"bg-md-primary text-md-on-primary shadow-md-1 hover:shadow-md-2\",\n ],\n outlined: [\n \"border border-md-outline-variant bg-transparent text-md-on-surface-variant\",\n \"border border-md-inverse-surface bg-md-inverse-surface text-md-inverse-on-surface\",\n ],\n text: [\"bg-transparent text-md-primary\", \"bg-md-secondary-container text-md-on-secondary-container\"],\n};\n\n/**\n * A button inside a {@link Md3ButtonGroup}. Takes its size and shape from the group;\n * when the group is selectable and the item has a `value`, it is a toggle button\n * exposing `aria-pressed`. Icon-only items (an `icon` and no children) need an\n * `aria-label`.\n */\nexport const Md3ButtonGroupItem = React.forwardRef<HTMLButtonElement, Md3ButtonGroupItemProps>(\n ({ value, variant, icon, className, children, onClick, type = \"button\", ...props }, ref) => {\n const ctx = React.useContext(ButtonGroupContext);\n if (!ctx) throw new Error(\"Md3ButtonGroupItem must be used within a Md3ButtonGroup\");\n const toggleable = ctx.selectable && value !== undefined;\n const selected = toggleable && ctx.value.includes(value);\n const iconOnly = icon != null && (children == null || children === false);\n const s = sizes[ctx.size];\n const colorVariant = variant ?? ctx.buttonVariant;\n\n let shapeClasses: string;\n if (ctx.variant === \"connected\") {\n shapeClasses = selected ? \"rounded-full\" : cn(innerRadius[ctx.size], outerRadius[ctx.shape][ctx.size]);\n } else {\n // Selected toggle buttons morph between round and square.\n const round = (ctx.shape === \"round\") !== selected;\n shapeClasses = cn(round ? \"rounded-full\" : squareRadius[ctx.size], pressedRadius[ctx.size]);\n }\n\n return (\n <button\n ref={ref}\n type={type}\n aria-pressed={toggleable ? selected : props[\"aria-pressed\"]}\n data-state={toggleable ? (selected ? \"on\" : \"off\") : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented && toggleable) ctx.toggle(value);\n }}\n className={cn(\n // Layout + typography\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium\",\n s.base,\n iconOnly ? s.iconOnly : s.label,\n // Standard groups widen the pressed button (no movement under reduced motion)\n ctx.variant === \"standard\" && (iconOnly ? s.iconOnlyPressed : s.labelPressed),\n ctx.variant === \"connected\" && \"flex-1\",\n // Shape morph + colour change\n \"transition-[border-radius,padding,width,background-color,color,box-shadow] duration-200 ease-out\",\n \"motion-reduce:transition-[background-color,color,box-shadow]\",\n shapeClasses,\n toggleable ? toggleColors[colorVariant][selected ? 1 : 0] : actionColors[colorVariant],\n // State layer (Material 3): on-color overlay at 8% hover / 12% focus+press\n \"before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity before:content-['']\",\n \"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]\",\n // Focus ring + disabled\n \"outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-md-primary focus-visible:ring-offset-2 focus-visible:ring-offset-md-surface\",\n \"disabled:pointer-events-none disabled:opacity-[0.38] disabled:shadow-none\",\n className,\n )}\n {...props}\n >\n {icon && <span className=\"relative inline-flex shrink-0\">{icon}</span>}\n {!iconOnly && children != null && <span className=\"relative\">{children}</span>}\n </button>\n );\n },\n);\nMd3ButtonGroupItem.displayName = \"Md3ButtonGroupItem\";\n"]}
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+
3
+ type Md3ButtonGroupVariant = "standard" | "connected";
4
+ type Md3ButtonGroupSize = "xs" | "sm" | "md" | "lg" | "xl";
5
+ type Md3ButtonGroupShape = "round" | "square";
6
+ type Md3ButtonGroupSelectionMode = "none" | "single" | "multiple";
7
+ type Md3ButtonGroupItemVariant = "filled" | "tonal" | "elevated" | "outlined" | "text";
8
+ interface Md3ButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
9
+ /** `standard` keeps each button's own shape; `connected` joins them with 2px gaps and small inner corners. */
10
+ variant?: Md3ButtonGroupVariant;
11
+ /** Button size applied to every item: XS 32px, S 40px, M 56px, L 96px, XL 136px tall. */
12
+ size?: Md3ButtonGroupSize;
13
+ /** Default shape of the buttons. Selected toggle buttons morph to the other shape. */
14
+ shape?: Md3ButtonGroupShape;
15
+ /** Colour style applied to items that don't set their own `variant`. */
16
+ buttonVariant?: Md3ButtonGroupItemVariant;
17
+ /** `none` for plain action buttons, `single`/`multiple` to make items with a `value` toggle. */
18
+ selectionMode?: Md3ButtonGroupSelectionMode;
19
+ /** When true, the last selected item can't be deselected. */
20
+ selectionRequired?: boolean;
21
+ value?: string | string[];
22
+ defaultValue?: string | string[];
23
+ /** Called with a string in `single` mode ("" when cleared) and a string array in `multiple` mode. */
24
+ onValueChange?: (value: string | string[]) => void;
25
+ }
26
+ /**
27
+ * A Material 3 Expressive button group: an invisible container that spaces related
28
+ * buttons and adjusts their shape. The `standard` variant keeps each button's shape
29
+ * and widens a button while it is pressed; the `connected` variant (the successor to
30
+ * segmented buttons) joins buttons with 2px gaps, small inner corners and fully
31
+ * rounded outer corners. Set `selectionMode` to turn items with a `value` into
32
+ * single- or multi-select toggle buttons whose shape morphs when selected.
33
+ * Compose with Md3ButtonGroupItem.
34
+ */
35
+ declare const Md3ButtonGroup: React.ForwardRefExoticComponent<Md3ButtonGroupProps & React.RefAttributes<HTMLDivElement>>;
36
+ interface Md3ButtonGroupItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
37
+ /** Identifies the item for selection. Items without a value act as plain buttons. */
38
+ value?: string;
39
+ /** Colour style; defaults to the group's `buttonVariant`. */
40
+ variant?: Md3ButtonGroupItemVariant;
41
+ /** Leading icon. Pass an icon with no children (and an `aria-label`) for an icon button. */
42
+ icon?: React.ReactNode;
43
+ }
44
+ /**
45
+ * A button inside a {@link Md3ButtonGroup}. Takes its size and shape from the group;
46
+ * when the group is selectable and the item has a `value`, it is a toggle button
47
+ * exposing `aria-pressed`. Icon-only items (an `icon` and no children) need an
48
+ * `aria-label`.
49
+ */
50
+ declare const Md3ButtonGroupItem: React.ForwardRefExoticComponent<Md3ButtonGroupItemProps & React.RefAttributes<HTMLButtonElement>>;
51
+
52
+ export { Md3ButtonGroup, Md3ButtonGroupItem, type Md3ButtonGroupItemProps, type Md3ButtonGroupItemVariant, type Md3ButtonGroupProps, type Md3ButtonGroupSelectionMode, type Md3ButtonGroupShape, type Md3ButtonGroupSize, type Md3ButtonGroupVariant };
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+
3
+ type Md3ButtonGroupVariant = "standard" | "connected";
4
+ type Md3ButtonGroupSize = "xs" | "sm" | "md" | "lg" | "xl";
5
+ type Md3ButtonGroupShape = "round" | "square";
6
+ type Md3ButtonGroupSelectionMode = "none" | "single" | "multiple";
7
+ type Md3ButtonGroupItemVariant = "filled" | "tonal" | "elevated" | "outlined" | "text";
8
+ interface Md3ButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
9
+ /** `standard` keeps each button's own shape; `connected` joins them with 2px gaps and small inner corners. */
10
+ variant?: Md3ButtonGroupVariant;
11
+ /** Button size applied to every item: XS 32px, S 40px, M 56px, L 96px, XL 136px tall. */
12
+ size?: Md3ButtonGroupSize;
13
+ /** Default shape of the buttons. Selected toggle buttons morph to the other shape. */
14
+ shape?: Md3ButtonGroupShape;
15
+ /** Colour style applied to items that don't set their own `variant`. */
16
+ buttonVariant?: Md3ButtonGroupItemVariant;
17
+ /** `none` for plain action buttons, `single`/`multiple` to make items with a `value` toggle. */
18
+ selectionMode?: Md3ButtonGroupSelectionMode;
19
+ /** When true, the last selected item can't be deselected. */
20
+ selectionRequired?: boolean;
21
+ value?: string | string[];
22
+ defaultValue?: string | string[];
23
+ /** Called with a string in `single` mode ("" when cleared) and a string array in `multiple` mode. */
24
+ onValueChange?: (value: string | string[]) => void;
25
+ }
26
+ /**
27
+ * A Material 3 Expressive button group: an invisible container that spaces related
28
+ * buttons and adjusts their shape. The `standard` variant keeps each button's shape
29
+ * and widens a button while it is pressed; the `connected` variant (the successor to
30
+ * segmented buttons) joins buttons with 2px gaps, small inner corners and fully
31
+ * rounded outer corners. Set `selectionMode` to turn items with a `value` into
32
+ * single- or multi-select toggle buttons whose shape morphs when selected.
33
+ * Compose with Md3ButtonGroupItem.
34
+ */
35
+ declare const Md3ButtonGroup: React.ForwardRefExoticComponent<Md3ButtonGroupProps & React.RefAttributes<HTMLDivElement>>;
36
+ interface Md3ButtonGroupItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
37
+ /** Identifies the item for selection. Items without a value act as plain buttons. */
38
+ value?: string;
39
+ /** Colour style; defaults to the group's `buttonVariant`. */
40
+ variant?: Md3ButtonGroupItemVariant;
41
+ /** Leading icon. Pass an icon with no children (and an `aria-label`) for an icon button. */
42
+ icon?: React.ReactNode;
43
+ }
44
+ /**
45
+ * A button inside a {@link Md3ButtonGroup}. Takes its size and shape from the group;
46
+ * when the group is selectable and the item has a `value`, it is a toggle button
47
+ * exposing `aria-pressed`. Icon-only items (an `icon` and no children) need an
48
+ * `aria-label`.
49
+ */
50
+ declare const Md3ButtonGroupItem: React.ForwardRefExoticComponent<Md3ButtonGroupItemProps & React.RefAttributes<HTMLButtonElement>>;
51
+
52
+ export { Md3ButtonGroup, Md3ButtonGroupItem, type Md3ButtonGroupItemProps, type Md3ButtonGroupItemVariant, type Md3ButtonGroupProps, type Md3ButtonGroupSelectionMode, type Md3ButtonGroupShape, type Md3ButtonGroupSize, type Md3ButtonGroupVariant };
package/dist/index.js ADDED
@@ -0,0 +1,202 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { cn } from '@nucleux/utils';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/index.tsx
7
+ var ButtonGroupContext = React.createContext(null);
8
+ var asArray = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
9
+ var gaps = {
10
+ xs: "gap-[18px]",
11
+ sm: "gap-3",
12
+ md: "gap-2",
13
+ lg: "gap-2",
14
+ xl: "gap-2"
15
+ };
16
+ var Md3ButtonGroup = React.forwardRef(
17
+ ({
18
+ variant = "standard",
19
+ size = "sm",
20
+ shape = "round",
21
+ buttonVariant = "filled",
22
+ selectionMode = "none",
23
+ selectionRequired = false,
24
+ value,
25
+ defaultValue,
26
+ onValueChange,
27
+ className,
28
+ children,
29
+ ...props
30
+ }, ref) => {
31
+ const multiple = selectionMode === "multiple";
32
+ const isControlled = value !== void 0;
33
+ const [internal, setInternal] = React.useState(asArray(defaultValue));
34
+ const current = isControlled ? asArray(value) : internal;
35
+ const toggle = (v) => {
36
+ let next;
37
+ if (multiple) next = current.includes(v) ? current.filter((x) => x !== v) : [...current, v];
38
+ else next = current[0] === v ? [] : [v];
39
+ if (selectionRequired && next.length === 0) return;
40
+ if (!isControlled) setInternal(next);
41
+ onValueChange?.(multiple ? next : next[0] ?? "");
42
+ };
43
+ return /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ ref,
47
+ role: "group",
48
+ className: cn(
49
+ "inline-flex items-center",
50
+ variant === "connected" ? "gap-0.5" : gaps[size],
51
+ className
52
+ ),
53
+ ...props,
54
+ children: /* @__PURE__ */ jsx(
55
+ ButtonGroupContext.Provider,
56
+ {
57
+ value: {
58
+ variant,
59
+ size,
60
+ shape,
61
+ buttonVariant,
62
+ selectable: selectionMode !== "none",
63
+ value: current,
64
+ toggle
65
+ },
66
+ children
67
+ }
68
+ )
69
+ }
70
+ );
71
+ }
72
+ );
73
+ Md3ButtonGroup.displayName = "Md3ButtonGroup";
74
+ var sizes = {
75
+ xs: { base: "h-8 gap-1 text-sm [&_svg]:size-5", label: "px-3", labelPressed: "motion-safe:active:px-4", iconOnly: "w-8", iconOnlyPressed: "motion-safe:active:w-[37px]" },
76
+ sm: { base: "h-10 gap-2 text-sm [&_svg]:size-5", label: "px-4", labelPressed: "motion-safe:active:px-5", iconOnly: "w-10", iconOnlyPressed: "motion-safe:active:w-[46px]" },
77
+ md: { base: "h-14 gap-2 text-base [&_svg]:size-6", label: "px-6", labelPressed: "motion-safe:active:px-8", iconOnly: "w-14", iconOnlyPressed: "motion-safe:active:w-16" },
78
+ lg: { base: "h-24 gap-3 text-2xl [&_svg]:size-8", label: "px-12", labelPressed: "motion-safe:active:px-14", iconOnly: "w-24", iconOnlyPressed: "motion-safe:active:w-[110px]" },
79
+ xl: { base: "h-[136px] gap-4 text-[32px] [&_svg]:size-10", label: "px-16", labelPressed: "motion-safe:active:px-[72px]", iconOnly: "w-[136px]", iconOnlyPressed: "motion-safe:active:w-[156px]" }
80
+ };
81
+ var squareRadius = {
82
+ xs: "rounded-md-md",
83
+ sm: "rounded-md-md",
84
+ md: "rounded-md-lg",
85
+ lg: "rounded-md-xl",
86
+ xl: "rounded-md-xl"
87
+ };
88
+ var pressedRadius = {
89
+ xs: "active:rounded-md-sm",
90
+ sm: "active:rounded-md-sm",
91
+ md: "active:rounded-md-md",
92
+ lg: "active:rounded-md-lg",
93
+ xl: "active:rounded-md-lg"
94
+ };
95
+ var innerRadius = {
96
+ xs: "rounded-md-xs",
97
+ sm: "rounded-md-sm",
98
+ md: "rounded-md-sm",
99
+ lg: "rounded-md-lg",
100
+ xl: "rounded-[20px]"
101
+ };
102
+ var outerRadius = {
103
+ round: {
104
+ xs: "first:rounded-l-full last:rounded-r-full",
105
+ sm: "first:rounded-l-full last:rounded-r-full",
106
+ md: "first:rounded-l-full last:rounded-r-full",
107
+ lg: "first:rounded-l-full last:rounded-r-full",
108
+ xl: "first:rounded-l-full last:rounded-r-full"
109
+ },
110
+ square: {
111
+ xs: "first:rounded-l-md-md last:rounded-r-md-md",
112
+ sm: "first:rounded-l-md-md last:rounded-r-md-md",
113
+ md: "first:rounded-l-md-lg last:rounded-r-md-lg",
114
+ lg: "first:rounded-l-md-xl last:rounded-r-md-xl",
115
+ xl: "first:rounded-l-md-xl last:rounded-r-md-xl"
116
+ }
117
+ };
118
+ var actionColors = {
119
+ filled: "bg-md-primary text-md-on-primary hover:shadow-md-1",
120
+ tonal: "bg-md-secondary-container text-md-on-secondary-container hover:shadow-md-1",
121
+ elevated: "bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2",
122
+ outlined: "border border-md-outline bg-transparent text-md-primary",
123
+ text: "bg-transparent text-md-primary"
124
+ };
125
+ var toggleColors = {
126
+ filled: ["bg-md-surface-container text-md-on-surface-variant", "bg-md-primary text-md-on-primary"],
127
+ tonal: [
128
+ "bg-md-secondary-container text-md-on-secondary-container",
129
+ "bg-md-secondary text-md-on-secondary"
130
+ ],
131
+ elevated: [
132
+ "bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2",
133
+ "bg-md-primary text-md-on-primary shadow-md-1 hover:shadow-md-2"
134
+ ],
135
+ outlined: [
136
+ "border border-md-outline-variant bg-transparent text-md-on-surface-variant",
137
+ "border border-md-inverse-surface bg-md-inverse-surface text-md-inverse-on-surface"
138
+ ],
139
+ text: ["bg-transparent text-md-primary", "bg-md-secondary-container text-md-on-secondary-container"]
140
+ };
141
+ var Md3ButtonGroupItem = React.forwardRef(
142
+ ({ value, variant, icon, className, children, onClick, type = "button", ...props }, ref) => {
143
+ const ctx = React.useContext(ButtonGroupContext);
144
+ if (!ctx) throw new Error("Md3ButtonGroupItem must be used within a Md3ButtonGroup");
145
+ const toggleable = ctx.selectable && value !== void 0;
146
+ const selected = toggleable && ctx.value.includes(value);
147
+ const iconOnly = icon != null && (children == null || children === false);
148
+ const s = sizes[ctx.size];
149
+ const colorVariant = variant ?? ctx.buttonVariant;
150
+ let shapeClasses;
151
+ if (ctx.variant === "connected") {
152
+ shapeClasses = selected ? "rounded-full" : cn(innerRadius[ctx.size], outerRadius[ctx.shape][ctx.size]);
153
+ } else {
154
+ const round = ctx.shape === "round" !== selected;
155
+ shapeClasses = cn(round ? "rounded-full" : squareRadius[ctx.size], pressedRadius[ctx.size]);
156
+ }
157
+ return /* @__PURE__ */ jsxs(
158
+ "button",
159
+ {
160
+ ref,
161
+ type,
162
+ "aria-pressed": toggleable ? selected : props["aria-pressed"],
163
+ "data-state": toggleable ? selected ? "on" : "off" : void 0,
164
+ onClick: (e) => {
165
+ onClick?.(e);
166
+ if (!e.defaultPrevented && toggleable) ctx.toggle(value);
167
+ },
168
+ className: cn(
169
+ // Layout + typography
170
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium",
171
+ s.base,
172
+ iconOnly ? s.iconOnly : s.label,
173
+ // Standard groups widen the pressed button (no movement under reduced motion)
174
+ ctx.variant === "standard" && (iconOnly ? s.iconOnlyPressed : s.labelPressed),
175
+ ctx.variant === "connected" && "flex-1",
176
+ // Shape morph + colour change
177
+ "transition-[border-radius,padding,width,background-color,color,box-shadow] duration-200 ease-out",
178
+ "motion-reduce:transition-[background-color,color,box-shadow]",
179
+ shapeClasses,
180
+ toggleable ? toggleColors[colorVariant][selected ? 1 : 0] : actionColors[colorVariant],
181
+ // State layer (Material 3): on-color overlay at 8% hover / 12% focus+press
182
+ "before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity before:content-['']",
183
+ "hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]",
184
+ // Focus ring + disabled
185
+ "outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-md-primary focus-visible:ring-offset-2 focus-visible:ring-offset-md-surface",
186
+ "disabled:pointer-events-none disabled:opacity-[0.38] disabled:shadow-none",
187
+ className
188
+ ),
189
+ ...props,
190
+ children: [
191
+ icon && /* @__PURE__ */ jsx("span", { className: "relative inline-flex shrink-0", children: icon }),
192
+ !iconOnly && children != null && /* @__PURE__ */ jsx("span", { className: "relative", children })
193
+ ]
194
+ }
195
+ );
196
+ }
197
+ );
198
+ Md3ButtonGroupItem.displayName = "Md3ButtonGroupItem";
199
+
200
+ export { Md3ButtonGroup, Md3ButtonGroupItem };
201
+ //# sourceMappingURL=index.js.map
202
+ //# 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 { cn } from \"@nucleux/utils\";\n\nexport type Md3ButtonGroupVariant = \"standard\" | \"connected\";\nexport type Md3ButtonGroupSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type Md3ButtonGroupShape = \"round\" | \"square\";\nexport type Md3ButtonGroupSelectionMode = \"none\" | \"single\" | \"multiple\";\nexport type Md3ButtonGroupItemVariant = \"filled\" | \"tonal\" | \"elevated\" | \"outlined\" | \"text\";\n\ninterface ButtonGroupContextValue {\n variant: Md3ButtonGroupVariant;\n size: Md3ButtonGroupSize;\n shape: Md3ButtonGroupShape;\n buttonVariant: Md3ButtonGroupItemVariant;\n selectable: boolean;\n value: string[];\n toggle: (v: string) => void;\n}\nconst ButtonGroupContext = React.createContext<ButtonGroupContextValue | null>(null);\n\nexport interface Md3ButtonGroupProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\" | \"defaultValue\"> {\n /** `standard` keeps each button's own shape; `connected` joins them with 2px gaps and small inner corners. */\n variant?: Md3ButtonGroupVariant;\n /** Button size applied to every item: XS 32px, S 40px, M 56px, L 96px, XL 136px tall. */\n size?: Md3ButtonGroupSize;\n /** Default shape of the buttons. Selected toggle buttons morph to the other shape. */\n shape?: Md3ButtonGroupShape;\n /** Colour style applied to items that don't set their own `variant`. */\n buttonVariant?: Md3ButtonGroupItemVariant;\n /** `none` for plain action buttons, `single`/`multiple` to make items with a `value` toggle. */\n selectionMode?: Md3ButtonGroupSelectionMode;\n /** When true, the last selected item can't be deselected. */\n selectionRequired?: boolean;\n value?: string | string[];\n defaultValue?: string | string[];\n /** Called with a string in `single` mode (\"\" when cleared) and a string array in `multiple` mode. */\n onValueChange?: (value: string | string[]) => void;\n}\n\nconst asArray = (v: string | string[] | undefined) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);\n\nconst gaps: Record<Md3ButtonGroupSize, string> = {\n xs: \"gap-[18px]\",\n sm: \"gap-3\",\n md: \"gap-2\",\n lg: \"gap-2\",\n xl: \"gap-2\",\n};\n\n/**\n * A Material 3 Expressive button group: an invisible container that spaces related\n * buttons and adjusts their shape. The `standard` variant keeps each button's shape\n * and widens a button while it is pressed; the `connected` variant (the successor to\n * segmented buttons) joins buttons with 2px gaps, small inner corners and fully\n * rounded outer corners. Set `selectionMode` to turn items with a `value` into\n * single- or multi-select toggle buttons whose shape morphs when selected.\n * Compose with Md3ButtonGroupItem.\n */\nexport const Md3ButtonGroup = React.forwardRef<HTMLDivElement, Md3ButtonGroupProps>(\n (\n {\n variant = \"standard\",\n size = \"sm\",\n shape = \"round\",\n buttonVariant = \"filled\",\n selectionMode = \"none\",\n selectionRequired = false,\n value,\n defaultValue,\n onValueChange,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const multiple = selectionMode === \"multiple\";\n const isControlled = value !== undefined;\n const [internal, setInternal] = React.useState<string[]>(asArray(defaultValue));\n const current = isControlled ? asArray(value) : internal;\n\n const toggle = (v: string) => {\n let next: string[];\n if (multiple) next = current.includes(v) ? current.filter((x) => x !== v) : [...current, v];\n else next = current[0] === v ? [] : [v];\n if (selectionRequired && next.length === 0) return;\n if (!isControlled) setInternal(next);\n onValueChange?.(multiple ? next : (next[0] ?? \"\"));\n };\n\n return (\n <div\n ref={ref}\n role=\"group\"\n className={cn(\n \"inline-flex items-center\",\n variant === \"connected\" ? \"gap-0.5\" : gaps[size],\n className,\n )}\n {...props}\n >\n <ButtonGroupContext.Provider\n value={{\n variant,\n size,\n shape,\n buttonVariant,\n selectable: selectionMode !== \"none\",\n value: current,\n toggle,\n }}\n >\n {children}\n </ButtonGroupContext.Provider>\n </div>\n );\n },\n);\nMd3ButtonGroup.displayName = \"Md3ButtonGroup\";\n\nexport interface Md3ButtonGroupItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Identifies the item for selection. Items without a value act as plain buttons. */\n value?: string;\n /** Colour style; defaults to the group's `buttonVariant`. */\n variant?: Md3ButtonGroupItemVariant;\n /** Leading icon. Pass an icon with no children (and an `aria-label`) for an icon button. */\n icon?: React.ReactNode;\n}\n\nconst sizes: Record<\n Md3ButtonGroupSize,\n { base: string; label: string; labelPressed: string; iconOnly: string; iconOnlyPressed: string }\n> = {\n xs: { base: \"h-8 gap-1 text-sm [&_svg]:size-5\", label: \"px-3\", labelPressed: \"motion-safe:active:px-4\", iconOnly: \"w-8\", iconOnlyPressed: \"motion-safe:active:w-[37px]\" },\n sm: { base: \"h-10 gap-2 text-sm [&_svg]:size-5\", label: \"px-4\", labelPressed: \"motion-safe:active:px-5\", iconOnly: \"w-10\", iconOnlyPressed: \"motion-safe:active:w-[46px]\" },\n md: { base: \"h-14 gap-2 text-base [&_svg]:size-6\", label: \"px-6\", labelPressed: \"motion-safe:active:px-8\", iconOnly: \"w-14\", iconOnlyPressed: \"motion-safe:active:w-16\" },\n lg: { base: \"h-24 gap-3 text-2xl [&_svg]:size-8\", label: \"px-12\", labelPressed: \"motion-safe:active:px-14\", iconOnly: \"w-24\", iconOnlyPressed: \"motion-safe:active:w-[110px]\" },\n xl: { base: \"h-[136px] gap-4 text-[32px] [&_svg]:size-10\", label: \"px-16\", labelPressed: \"motion-safe:active:px-[72px]\", iconOnly: \"w-[136px]\", iconOnlyPressed: \"motion-safe:active:w-[156px]\" },\n};\n\nconst squareRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"rounded-md-md\",\n sm: \"rounded-md-md\",\n md: \"rounded-md-lg\",\n lg: \"rounded-md-xl\",\n xl: \"rounded-md-xl\",\n};\n\nconst pressedRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"active:rounded-md-sm\",\n sm: \"active:rounded-md-sm\",\n md: \"active:rounded-md-md\",\n lg: \"active:rounded-md-lg\",\n xl: \"active:rounded-md-lg\",\n};\n\nconst innerRadius: Record<Md3ButtonGroupSize, string> = {\n xs: \"rounded-md-xs\",\n sm: \"rounded-md-sm\",\n md: \"rounded-md-sm\",\n lg: \"rounded-md-lg\",\n xl: \"rounded-[20px]\",\n};\n\nconst outerRadius: Record<Md3ButtonGroupShape, Record<Md3ButtonGroupSize, string>> = {\n round: {\n xs: \"first:rounded-l-full last:rounded-r-full\",\n sm: \"first:rounded-l-full last:rounded-r-full\",\n md: \"first:rounded-l-full last:rounded-r-full\",\n lg: \"first:rounded-l-full last:rounded-r-full\",\n xl: \"first:rounded-l-full last:rounded-r-full\",\n },\n square: {\n xs: \"first:rounded-l-md-md last:rounded-r-md-md\",\n sm: \"first:rounded-l-md-md last:rounded-r-md-md\",\n md: \"first:rounded-l-md-lg last:rounded-r-md-lg\",\n lg: \"first:rounded-l-md-xl last:rounded-r-md-xl\",\n xl: \"first:rounded-l-md-xl last:rounded-r-md-xl\",\n },\n};\n\n/** Colours for plain action buttons (same as Md3Button). */\nconst actionColors: Record<Md3ButtonGroupItemVariant, string> = {\n filled: \"bg-md-primary text-md-on-primary hover:shadow-md-1\",\n tonal: \"bg-md-secondary-container text-md-on-secondary-container hover:shadow-md-1\",\n elevated: \"bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2\",\n outlined: \"border border-md-outline bg-transparent text-md-primary\",\n text: \"bg-transparent text-md-primary\",\n};\n\n/** Colours for toggle buttons: [unselected, selected]. */\nconst toggleColors: Record<Md3ButtonGroupItemVariant, [string, string]> = {\n filled: [\"bg-md-surface-container text-md-on-surface-variant\", \"bg-md-primary text-md-on-primary\"],\n tonal: [\n \"bg-md-secondary-container text-md-on-secondary-container\",\n \"bg-md-secondary text-md-on-secondary\",\n ],\n elevated: [\n \"bg-md-surface-container-low text-md-primary shadow-md-1 hover:shadow-md-2\",\n \"bg-md-primary text-md-on-primary shadow-md-1 hover:shadow-md-2\",\n ],\n outlined: [\n \"border border-md-outline-variant bg-transparent text-md-on-surface-variant\",\n \"border border-md-inverse-surface bg-md-inverse-surface text-md-inverse-on-surface\",\n ],\n text: [\"bg-transparent text-md-primary\", \"bg-md-secondary-container text-md-on-secondary-container\"],\n};\n\n/**\n * A button inside a {@link Md3ButtonGroup}. Takes its size and shape from the group;\n * when the group is selectable and the item has a `value`, it is a toggle button\n * exposing `aria-pressed`. Icon-only items (an `icon` and no children) need an\n * `aria-label`.\n */\nexport const Md3ButtonGroupItem = React.forwardRef<HTMLButtonElement, Md3ButtonGroupItemProps>(\n ({ value, variant, icon, className, children, onClick, type = \"button\", ...props }, ref) => {\n const ctx = React.useContext(ButtonGroupContext);\n if (!ctx) throw new Error(\"Md3ButtonGroupItem must be used within a Md3ButtonGroup\");\n const toggleable = ctx.selectable && value !== undefined;\n const selected = toggleable && ctx.value.includes(value);\n const iconOnly = icon != null && (children == null || children === false);\n const s = sizes[ctx.size];\n const colorVariant = variant ?? ctx.buttonVariant;\n\n let shapeClasses: string;\n if (ctx.variant === \"connected\") {\n shapeClasses = selected ? \"rounded-full\" : cn(innerRadius[ctx.size], outerRadius[ctx.shape][ctx.size]);\n } else {\n // Selected toggle buttons morph between round and square.\n const round = (ctx.shape === \"round\") !== selected;\n shapeClasses = cn(round ? \"rounded-full\" : squareRadius[ctx.size], pressedRadius[ctx.size]);\n }\n\n return (\n <button\n ref={ref}\n type={type}\n aria-pressed={toggleable ? selected : props[\"aria-pressed\"]}\n data-state={toggleable ? (selected ? \"on\" : \"off\") : undefined}\n onClick={(e) => {\n onClick?.(e);\n if (!e.defaultPrevented && toggleable) ctx.toggle(value);\n }}\n className={cn(\n // Layout + typography\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium\",\n s.base,\n iconOnly ? s.iconOnly : s.label,\n // Standard groups widen the pressed button (no movement under reduced motion)\n ctx.variant === \"standard\" && (iconOnly ? s.iconOnlyPressed : s.labelPressed),\n ctx.variant === \"connected\" && \"flex-1\",\n // Shape morph + colour change\n \"transition-[border-radius,padding,width,background-color,color,box-shadow] duration-200 ease-out\",\n \"motion-reduce:transition-[background-color,color,box-shadow]\",\n shapeClasses,\n toggleable ? toggleColors[colorVariant][selected ? 1 : 0] : actionColors[colorVariant],\n // State layer (Material 3): on-color overlay at 8% hover / 12% focus+press\n \"before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity before:content-['']\",\n \"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]\",\n // Focus ring + disabled\n \"outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-md-primary focus-visible:ring-offset-2 focus-visible:ring-offset-md-surface\",\n \"disabled:pointer-events-none disabled:opacity-[0.38] disabled:shadow-none\",\n className,\n )}\n {...props}\n >\n {icon && <span className=\"relative inline-flex shrink-0\">{icon}</span>}\n {!iconOnly && children != null && <span className=\"relative\">{children}</span>}\n </button>\n );\n },\n);\nMd3ButtonGroupItem.displayName = \"Md3ButtonGroupItem\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@nucleux/md3-button-group",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.7.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Material 3 button group — Nucleux agentic UI for React.",
7
+ "keywords": [
8
+ "nucleux",
9
+ "react",
10
+ "ui",
11
+ "components",
12
+ "agentic",
13
+ "ai",
14
+ "tailwind",
15
+ "md3",
16
+ "material",
17
+ "button-group",
18
+ "md3-button-group"
19
+ ],
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "peerDependencies": {
35
+ "react": ">=18",
36
+ "react-dom": ">=18"
37
+ },
38
+ "dependencies": {
39
+ "@nucleux/utils": "0.7.0"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.3.5",
43
+ "typescript": "^5.7.2",
44
+ "@types/react": "^18.3.18",
45
+ "react": "^18.3.1",
46
+ "lucide-react": "^0.469.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/intelligaia-open-labs/nucleux.git",
55
+ "directory": "packages/md3-button-group"
56
+ },
57
+ "homepage": "https://github.com/intelligaia-open-labs/nucleux#readme",
58
+ "scripts": {
59
+ "build": "tsup"
60
+ }
6
61
  }