@behivetech/atoms.button 0.0.0-stage → 0.4.2

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/README.md CHANGED
@@ -1,3 +1,55 @@
1
- # Temporary Holding Version
1
+ # Button
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
+ A flexible button component with variants, sizes, loading and disabled states.
4
+
5
+ ## Variants
6
+
7
+ ```jsx
8
+ <div style={{ display: "flex", gap: "8px" }}>
9
+ <Button variant="primary">Primary</Button>
10
+ <Button variant="secondary">Secondary</Button>
11
+ <Button variant="ghost">Ghost</Button>
12
+ <Button variant="danger">Danger</Button>
13
+ </div>
14
+ ```
15
+
16
+ ## Sizes
17
+
18
+ ```jsx
19
+ <div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
20
+ <Button size="sm">Small</Button>
21
+ <Button size="md">Medium</Button>
22
+ <Button size="lg">Large</Button>
23
+ </div>
24
+ ```
25
+
26
+ ## States
27
+
28
+ ```jsx
29
+ <div style={{ display: "flex", gap: "8px" }}>
30
+ <Button loading>Loading</Button>
31
+ <Button disabled>Disabled</Button>
32
+ </div>
33
+ ```
34
+
35
+ ## Icon-only
36
+
37
+ For buttons whose content is a single icon (glyph, `<svg>`, etc.) with no visible label, pass `iconOnly` and provide an `aria-label` for accessibility.
38
+
39
+ ```jsx
40
+ <Button iconOnly aria-label="Delete" variant="danger">
41
+ ✕
42
+ </Button>
43
+ ```
44
+
45
+ ## Props
46
+
47
+ | Prop | Type | Default | Description |
48
+ | ----------- | ----------------------------------------- | --------- | -------------------------------------------------------------------------- |
49
+ | `variant` | `primary \| secondary \| ghost \| danger` | `primary` | Visual style |
50
+ | `size` | `sm \| md \| lg` | `md` | Button size |
51
+ | `loading` | `boolean` | `false` | Shows spinner, disables interaction |
52
+ | `iconOnly` | `boolean` | `false` | Square, padding-free layout for icon-only content — pair with `aria-label` |
53
+ | `disabled` | `boolean` | `false` | Disables the button |
54
+ | `children` | `ReactNode` | — | Button label |
55
+ | `className` | `string` | — | Additional class names |
@@ -0,0 +1,17 @@
1
+ import { Button } from "./button.js";
2
+
3
+ export const PrimaryButton = () => <Button variant="primary">Primary</Button>;
4
+ export const SecondaryButton = () => (
5
+ <Button variant="secondary">Secondary</Button>
6
+ );
7
+ export const GhostButton = () => <Button variant="ghost">Ghost</Button>;
8
+ export const DangerButton = () => <Button variant="danger">Danger</Button>;
9
+ export const SmallButton = () => <Button size="sm">Small</Button>;
10
+ export const LargeButton = () => <Button size="lg">Large</Button>;
11
+ export const LoadingButton = () => <Button loading>Loading</Button>;
12
+ export const DisabledButton = () => <Button disabled>Disabled</Button>;
13
+ export const IconOnlyButton = () => (
14
+ <Button iconOnly aria-label="Delete" variant="danger">
15
+ ✕
16
+ </Button>
17
+ );
@@ -0,0 +1,263 @@
1
+ // Material Design 3 button.
2
+ //
3
+ // The public API is unchanged — `variant` × `size`, plus `loading`, `iconOnly`
4
+ // and `asChild`. What changed is that every value now comes from an
5
+ // `--md-sys-*` token, so the component re-themes with the palette instead of
6
+ // carrying a brand of its own.
7
+ //
8
+ // Variant names map onto MD3's button types rather than being renamed, because
9
+ // the prop is part of this package's published API:
10
+ //
11
+ // primary -> Filled secondary -> Outlined
12
+ // ghost -> Text danger -> Filled, on the error role
13
+ //
14
+ // Each color falls back to the pre-MD3 `--color-*` name, which
15
+ // `cms.base-styles` aliases to the matching MD3 role. That keeps this working
16
+ // against an older base-styles instead of rendering unstyled.
17
+ //
18
+ // **Structure.** Modifiers belong to the root only — a child never carries
19
+ // `--sm`. Inside a modifier block, `&.BHT__Button` is the root element
20
+ // (compound) and `.BHT__Button` without the `&` is a descendant (a child node).
21
+ // Pseudo-class blocks (`&:hover`, `&:disabled`) are states rather than
22
+ // modifiers and stay unwrapped.
23
+
24
+ .BHT__Button {
25
+ // `isolation` keeps the state layer's stacking context local, so a button
26
+ // inside a positioned parent can't have its ::after escape behind a sibling.
27
+ position: relative;
28
+ isolation: isolate;
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ gap: var(--spacing-2, 0.5rem);
33
+ border: 1px solid transparent;
34
+ // MD3 buttons are pill-shaped. This is the single most visible change from
35
+ // the previous styling, which used the 6px `--radius-md`.
36
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
37
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
38
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
39
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
40
+ line-height: var(--md-sys-typescale-label-large-line-height, 1.25rem);
41
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
42
+ cursor: pointer;
43
+ white-space: nowrap;
44
+ text-decoration: none;
45
+ user-select: none;
46
+ transition:
47
+ background var(--md-sys-motion-duration-short-3, 150ms)
48
+ var(--md-sys-motion-easing-standard, ease),
49
+ color var(--md-sys-motion-duration-short-3, 150ms)
50
+ var(--md-sys-motion-easing-standard, ease),
51
+ border-color var(--md-sys-motion-duration-short-3, 150ms)
52
+ var(--md-sys-motion-easing-standard, ease);
53
+
54
+ // --- Children ------------------------------------------------------------
55
+
56
+ &__label {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ }
60
+
61
+ &__spinner {
62
+ display: inline-block;
63
+ width: 1em;
64
+ height: 1em;
65
+ border: 2px solid currentColor;
66
+ border-top-color: transparent;
67
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
68
+ animation: button-spin 0.6s linear infinite;
69
+ }
70
+
71
+ // --- State layer ---------------------------------------------------------
72
+ //
73
+ // **This replaces six hardcoded brand colors and two `filter: brightness()`
74
+ // rules, and it is the whole reason the component re-themes.**
75
+ //
76
+ // MD3 expresses interaction as a translucent veil of the *content* color over
77
+ // the container. `currentColor` is already that content color in every
78
+ // variant — `on-primary` on a filled button, `primary` on an outlined or text
79
+ // one — so one rule serves all four variants and follows the palette for
80
+ // free. The old `rgba(247, 179, 52, 0.1)` was a fixed amber that stayed amber
81
+ // no matter what `--color-primary` was set to, and `filter: brightness()`
82
+ // lightens on a dark surface and washes out on a light one.
83
+ &::after {
84
+ content: "";
85
+ position: absolute;
86
+ inset: 0;
87
+ border-radius: inherit;
88
+ background: currentColor;
89
+ opacity: 0;
90
+ pointer-events: none;
91
+ transition: opacity var(--md-sys-motion-duration-short-2, 100ms)
92
+ var(--md-sys-motion-easing-standard, ease);
93
+ }
94
+
95
+ &:hover:not(:disabled)::after {
96
+ opacity: var(--md-sys-state-hover-opacity, 0.08);
97
+ }
98
+
99
+ &:focus-visible::after {
100
+ opacity: var(--md-sys-state-focus-opacity, 0.1);
101
+ }
102
+
103
+ &:active:not(:disabled)::after {
104
+ opacity: var(--md-sys-state-pressed-opacity, 0.1);
105
+ }
106
+
107
+ // --- Touch target --------------------------------------------------------
108
+ //
109
+ // **The component previously had no minimum target, and `sm` (32px) and the
110
+ // default `md` (40px) both sat under the 48dp floor.** Rather than inflating
111
+ // the visual size — which would reflow every layout already built on these
112
+ // buttons — an invisible pseudo-element extends the *hit area* to 48px while
113
+ // the button keeps its drawn height.
114
+ //
115
+ // Deliberately px, not rem. A touch target is a physical property of the
116
+ // finger, not of the type size, so it must not shrink when a reader sets a
117
+ // smaller font.
118
+ &::before {
119
+ content: "";
120
+ position: absolute;
121
+ left: 0;
122
+ right: 0;
123
+ top: 50%;
124
+ translate: 0 -50%;
125
+ height: 100%;
126
+ min-height: 48px;
127
+ }
128
+
129
+ // --- Focus ---------------------------------------------------------------
130
+ //
131
+ // There was no focus style at all before this: a keyboard user had no idea
132
+ // which control they were on. `:focus-visible` rather than `:focus` so a
133
+ // pointer click doesn't leave a ring behind.
134
+ &:focus-visible {
135
+ outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
136
+ outline-offset: 2px;
137
+ }
138
+
139
+ // --- Size modifiers ------------------------------------------------------
140
+ //
141
+ // Heights are unchanged, so existing layouts keep their rhythm; the 48px
142
+ // floor above is what makes the two smaller ones accessible.
143
+ &--sm {
144
+ &.BHT__Button {
145
+ padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
146
+ min-height: 2rem;
147
+ }
148
+ }
149
+
150
+ &--md {
151
+ &.BHT__Button {
152
+ padding: var(--spacing-2, 0.5rem) var(--spacing-5, 1.25rem);
153
+ min-height: 2.5rem;
154
+ }
155
+ }
156
+
157
+ &--lg {
158
+ &.BHT__Button {
159
+ font-size: var(--md-sys-typescale-title-medium-size, 1rem);
160
+ padding: var(--spacing-3, 0.75rem) var(--spacing-6, 1.5rem);
161
+ min-height: 3rem;
162
+ }
163
+ }
164
+
165
+ // --- Variant modifiers ---------------------------------------------------
166
+
167
+ &--primary {
168
+ &.BHT__Button {
169
+ background: var(--md-sys-color-primary, var(--color-primary));
170
+ color: var(--md-sys-color-on-primary, var(--color-on-primary));
171
+ border-color: transparent;
172
+ }
173
+ }
174
+
175
+ &--secondary {
176
+ &.BHT__Button {
177
+ background: transparent;
178
+ color: var(--md-sys-color-primary, var(--color-primary));
179
+ border-color: var(--md-sys-color-outline, var(--color-border-strong));
180
+ }
181
+ }
182
+
183
+ &--ghost {
184
+ &.BHT__Button {
185
+ background: transparent;
186
+ color: var(--md-sys-color-primary, var(--color-primary));
187
+ border-color: transparent;
188
+ }
189
+ }
190
+
191
+ &--danger {
192
+ &.BHT__Button {
193
+ background: var(--md-sys-color-error, var(--color-error));
194
+ color: var(--md-sys-color-on-error, #ffffff);
195
+ border-color: transparent;
196
+ }
197
+ }
198
+
199
+ // --- Icon-only -----------------------------------------------------------
200
+ //
201
+ // The width rules previously read `.Button--sm` and the label rule
202
+ // `.Button__label`, both missing the `BHT__` the markup actually carries, so
203
+ // every rule in this block matched nothing and icon-only buttons had no
204
+ // width at all.
205
+ &--icon-only {
206
+ &.BHT__Button {
207
+ padding: 0;
208
+
209
+ &--sm {
210
+ width: 2rem;
211
+ }
212
+
213
+ &--md {
214
+ width: 2.5rem;
215
+ }
216
+
217
+ &--lg {
218
+ width: 3rem;
219
+ }
220
+ }
221
+
222
+ .BHT__Button {
223
+ &__label {
224
+ display: inline-flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ }
228
+ }
229
+ }
230
+
231
+ // --- Disabled / loading --------------------------------------------------
232
+ //
233
+ // MD3 expresses disabled as 38% of the content color on a 12% container
234
+ // rather than a blanket opacity, but a flat `opacity` is what the rest of
235
+ // this library uses and changing it here alone would make Button the odd one
236
+ // out. Worth revisiting across the set, not in isolation.
237
+ &--disabled {
238
+ &.BHT__Button {
239
+ opacity: 0.45;
240
+ cursor: not-allowed;
241
+ pointer-events: none;
242
+ }
243
+ }
244
+
245
+ &:disabled {
246
+ opacity: 0.45;
247
+ cursor: not-allowed;
248
+ pointer-events: none;
249
+ }
250
+
251
+ &--loading {
252
+ &.BHT__Button {
253
+ cursor: wait;
254
+ pointer-events: none;
255
+ }
256
+ }
257
+ }
258
+
259
+ @keyframes button-spin {
260
+ to {
261
+ transform: rotate(360deg);
262
+ }
263
+ }
package/button.tsx ADDED
@@ -0,0 +1,71 @@
1
+ import { type ButtonHTMLAttributes } from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { getClassName } from "@bht-component-registry/class-names";
4
+ import styles from "./button.module.scss";
5
+
6
+ export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
7
+ export type ButtonSize = "sm" | "md" | "lg";
8
+
9
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
+ /** Controls the color/style treatment; defaults to primary */
11
+ variant?: ButtonVariant;
12
+ /** Controls the size; defaults to md */
13
+ size?: ButtonSize;
14
+ /** Shows a spinner and disables the button while an action is in flight */
15
+ loading?: boolean;
16
+ /** Renders the button as a square icon-only control with no visible label spacing */
17
+ iconOnly?: boolean;
18
+ /** Additional class names to merge with the component root element */
19
+ className?: string;
20
+ /** Button label/content */
21
+ children?: React.ReactNode;
22
+ /** Renders the button's props onto its single child element (via Radix `Slot`) instead of a `<button>` */
23
+ asChild?: boolean;
24
+ }
25
+
26
+ export const Button = ({
27
+ variant = "primary",
28
+ size = "md",
29
+ loading = false,
30
+ iconOnly = false,
31
+ disabled,
32
+ className,
33
+ children,
34
+ asChild = false,
35
+ ...rest
36
+ }: ButtonProps) => {
37
+ const [rootClass, getChildClass] = getClassName({
38
+ className,
39
+ rootClass: "BHT__Button",
40
+ modifiers: {
41
+ [variant]: true,
42
+ [size]: true,
43
+ loading,
44
+ disabled: !!disabled,
45
+ "icon-only": iconOnly,
46
+ },
47
+ styles,
48
+ });
49
+
50
+ const Comp = (asChild ? Slot : "button") as React.ElementType;
51
+
52
+ return (
53
+ <Comp
54
+ {...rest}
55
+ className={rootClass}
56
+ disabled={disabled || loading}
57
+ aria-busy={loading || undefined}
58
+ >
59
+ {asChild ? (
60
+ children
61
+ ) : (
62
+ <>
63
+ {loading && (
64
+ <span className={getChildClass("spinner")} aria-hidden="true" />
65
+ )}
66
+ <span className={getChildClass("label")}>{children}</span>
67
+ </>
68
+ )}
69
+ </Comp>
70
+ );
71
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
34
+ var require_classnames = __commonJS({
35
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports2, module2) {
36
+ "use strict";
37
+ (function() {
38
+ "use strict";
39
+ var hasOwn = {}.hasOwnProperty;
40
+ function classNames() {
41
+ var classes = "";
42
+ for (var i = 0; i < arguments.length; i++) {
43
+ var arg = arguments[i];
44
+ if (arg) {
45
+ classes = appendClass(classes, parseValue(arg));
46
+ }
47
+ }
48
+ return classes;
49
+ }
50
+ function parseValue(arg) {
51
+ if (typeof arg === "string" || typeof arg === "number") {
52
+ return arg;
53
+ }
54
+ if (typeof arg !== "object") {
55
+ return "";
56
+ }
57
+ if (Array.isArray(arg)) {
58
+ return classNames.apply(null, arg);
59
+ }
60
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
61
+ return arg.toString();
62
+ }
63
+ var classes = "";
64
+ for (var key in arg) {
65
+ if (hasOwn.call(arg, key) && arg[key]) {
66
+ classes = appendClass(classes, key);
67
+ }
68
+ }
69
+ return classes;
70
+ }
71
+ function appendClass(value, newClass) {
72
+ if (!newClass) {
73
+ return value;
74
+ }
75
+ if (value) {
76
+ return value + " " + newClass;
77
+ }
78
+ return value + newClass;
79
+ }
80
+ if (typeof module2 !== "undefined" && module2.exports) {
81
+ classNames.default = classNames;
82
+ module2.exports = classNames;
83
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
84
+ define("classnames", [], function() {
85
+ return classNames;
86
+ });
87
+ } else {
88
+ window.classNames = classNames;
89
+ }
90
+ })();
91
+ }
92
+ });
93
+
94
+ // index.ts
95
+ var index_exports = {};
96
+ __export(index_exports, {
97
+ Button: () => Button
98
+ });
99
+ module.exports = __toCommonJS(index_exports);
100
+
101
+ // button.tsx
102
+ var import_react_slot = require("@radix-ui/react-slot");
103
+
104
+ // ../../../../packages/registry/class-names/dist/index.js
105
+ var import_classnames = __toESM(require_classnames(), 1);
106
+ var getClassName = ({
107
+ className,
108
+ modifiers = {},
109
+ rootClass,
110
+ styles = {}
111
+ }) => {
112
+ function getModifiers() {
113
+ const updatedModifiers = {};
114
+ const modifierKeys = Object.keys(modifiers);
115
+ if (modifierKeys.length) {
116
+ modifierKeys.forEach((modKey) => {
117
+ const classKey = `${rootClass}--${modKey}`;
118
+ const newClassName = styles[classKey] || classKey;
119
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
120
+ });
121
+ }
122
+ return updatedModifiers;
123
+ }
124
+ function getChildClass(childClassName) {
125
+ const classKey = `${rootClass}__${childClassName}`;
126
+ return styles[classKey] || classKey;
127
+ }
128
+ function getRootStyles() {
129
+ return (0, import_classnames.default)(
130
+ className,
131
+ {
132
+ ...getModifiers()
133
+ },
134
+ styles[rootClass] || rootClass
135
+ );
136
+ }
137
+ return [getRootStyles(), getChildClass];
138
+ };
139
+
140
+ // button.module.scss
141
+ var button_module_default = {
142
+ "BHT__Button": "BHT__Button",
143
+ "BHT__Button__label": "BHT__Button__label",
144
+ "BHT__Button__spinner": "BHT__Button__spinner",
145
+ "button-spin": "button-spin",
146
+ "BHT__Button--sm": "BHT__Button--sm",
147
+ "BHT__Button--md": "BHT__Button--md",
148
+ "BHT__Button--lg": "BHT__Button--lg",
149
+ "BHT__Button--primary": "BHT__Button--primary",
150
+ "BHT__Button--secondary": "BHT__Button--secondary",
151
+ "BHT__Button--ghost": "BHT__Button--ghost",
152
+ "BHT__Button--danger": "BHT__Button--danger",
153
+ "BHT__Button--icon-only": "BHT__Button--icon-only",
154
+ "BHT__Button--disabled": "BHT__Button--disabled",
155
+ "BHT__Button--loading": "BHT__Button--loading"
156
+ };
157
+
158
+ // button.tsx
159
+ var import_jsx_runtime = require("react/jsx-runtime");
160
+ var Button = ({
161
+ variant = "primary",
162
+ size = "md",
163
+ loading = false,
164
+ iconOnly = false,
165
+ disabled,
166
+ className,
167
+ children,
168
+ asChild = false,
169
+ ...rest
170
+ }) => {
171
+ const [rootClass, getChildClass] = getClassName({
172
+ className,
173
+ rootClass: "BHT__Button",
174
+ modifiers: {
175
+ [variant]: true,
176
+ [size]: true,
177
+ loading,
178
+ disabled: !!disabled,
179
+ "icon-only": iconOnly
180
+ },
181
+ styles: button_module_default
182
+ });
183
+ const Comp = asChild ? import_react_slot.Slot : "button";
184
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
185
+ Comp,
186
+ {
187
+ ...rest,
188
+ className: rootClass,
189
+ disabled: disabled || loading,
190
+ "aria-busy": loading || void 0,
191
+ children: asChild ? children : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
192
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("spinner"), "aria-hidden": "true" }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("label"), children })
194
+ ] })
195
+ }
196
+ );
197
+ };
198
+ // Annotate the CommonJS export names for ESM import in node:
199
+ 0 && (module.exports = {
200
+ Button
201
+ });
202
+ /*! Bundled license information:
203
+
204
+ classnames/index.js:
205
+ (*!
206
+ Copyright (c) 2018 Jed Watson.
207
+ Licensed under the MIT License (MIT), see
208
+ http://jedwatson.github.io/classnames
209
+ *)
210
+ */
package/dist/index.css ADDED
@@ -0,0 +1,139 @@
1
+ /* esbuild-sass-plugin:css-chunkbutton.module.scss */
2
+ .BHT__Button {
3
+ position: relative;
4
+ isolation: isolate;
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ gap: var(--spacing-2, 0.5rem);
9
+ border: 1px solid transparent;
10
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
11
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
12
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
13
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
14
+ line-height: var(--md-sys-typescale-label-large-line-height, 1.25rem);
15
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
16
+ cursor: pointer;
17
+ white-space: nowrap;
18
+ text-decoration: none;
19
+ user-select: none;
20
+ transition:
21
+ background var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard, ease),
22
+ color var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard, ease),
23
+ border-color var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard, ease);
24
+ }
25
+ .BHT__Button__label {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ }
29
+ .BHT__Button__spinner {
30
+ display: inline-block;
31
+ width: 1em;
32
+ height: 1em;
33
+ border: 2px solid currentColor;
34
+ border-top-color: transparent;
35
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
36
+ animation: button-spin 0.6s linear infinite;
37
+ }
38
+ .BHT__Button::after {
39
+ content: "";
40
+ position: absolute;
41
+ inset: 0;
42
+ border-radius: inherit;
43
+ background: currentColor;
44
+ opacity: 0;
45
+ pointer-events: none;
46
+ transition: opacity var(--md-sys-motion-duration-short-2, 100ms) var(--md-sys-motion-easing-standard, ease);
47
+ }
48
+ .BHT__Button:hover:not(:disabled)::after {
49
+ opacity: var(--md-sys-state-hover-opacity, 0.08);
50
+ }
51
+ .BHT__Button:focus-visible::after {
52
+ opacity: var(--md-sys-state-focus-opacity, 0.1);
53
+ }
54
+ .BHT__Button:active:not(:disabled)::after {
55
+ opacity: var(--md-sys-state-pressed-opacity, 0.1);
56
+ }
57
+ .BHT__Button::before {
58
+ content: "";
59
+ position: absolute;
60
+ left: 0;
61
+ right: 0;
62
+ top: 50%;
63
+ translate: 0 -50%;
64
+ height: 100%;
65
+ min-height: 48px;
66
+ }
67
+ .BHT__Button:focus-visible {
68
+ outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
69
+ outline-offset: 2px;
70
+ }
71
+ .BHT__Button--sm.BHT__Button {
72
+ padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
73
+ min-height: 2rem;
74
+ }
75
+ .BHT__Button--md.BHT__Button {
76
+ padding: var(--spacing-2, 0.5rem) var(--spacing-5, 1.25rem);
77
+ min-height: 2.5rem;
78
+ }
79
+ .BHT__Button--lg.BHT__Button {
80
+ font-size: var(--md-sys-typescale-title-medium-size, 1rem);
81
+ padding: var(--spacing-3, 0.75rem) var(--spacing-6, 1.5rem);
82
+ min-height: 3rem;
83
+ }
84
+ .BHT__Button--primary.BHT__Button {
85
+ background: var(--md-sys-color-primary, var(--color-primary));
86
+ color: var(--md-sys-color-on-primary, var(--color-on-primary));
87
+ border-color: transparent;
88
+ }
89
+ .BHT__Button--secondary.BHT__Button {
90
+ background: transparent;
91
+ color: var(--md-sys-color-primary, var(--color-primary));
92
+ border-color: var(--md-sys-color-outline, var(--color-border-strong));
93
+ }
94
+ .BHT__Button--ghost.BHT__Button {
95
+ background: transparent;
96
+ color: var(--md-sys-color-primary, var(--color-primary));
97
+ border-color: transparent;
98
+ }
99
+ .BHT__Button--danger.BHT__Button {
100
+ background: var(--md-sys-color-error, var(--color-error));
101
+ color: var(--md-sys-color-on-error, #ffffff);
102
+ border-color: transparent;
103
+ }
104
+ .BHT__Button--icon-only.BHT__Button {
105
+ padding: 0;
106
+ }
107
+ .BHT__Button--icon-only.BHT__Button--sm {
108
+ width: 2rem;
109
+ }
110
+ .BHT__Button--icon-only.BHT__Button--md {
111
+ width: 2.5rem;
112
+ }
113
+ .BHT__Button--icon-only.BHT__Button--lg {
114
+ width: 3rem;
115
+ }
116
+ .BHT__Button--icon-only .BHT__Button__label {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ }
121
+ .BHT__Button--disabled.BHT__Button {
122
+ opacity: 0.45;
123
+ cursor: not-allowed;
124
+ pointer-events: none;
125
+ }
126
+ .BHT__Button:disabled {
127
+ opacity: 0.45;
128
+ cursor: not-allowed;
129
+ pointer-events: none;
130
+ }
131
+ .BHT__Button--loading.BHT__Button {
132
+ cursor: wait;
133
+ pointer-events: none;
134
+ }
135
+ @keyframes button-spin {
136
+ to {
137
+ transform: rotate(360deg);
138
+ }
139
+ }
@@ -0,0 +1,24 @@
1
+ import * as react from 'react';
2
+ import { ButtonHTMLAttributes } from 'react';
3
+
4
+ type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
5
+ type ButtonSize = "sm" | "md" | "lg";
6
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ /** Controls the color/style treatment; defaults to primary */
8
+ variant?: ButtonVariant;
9
+ /** Controls the size; defaults to md */
10
+ size?: ButtonSize;
11
+ /** Shows a spinner and disables the button while an action is in flight */
12
+ loading?: boolean;
13
+ /** Renders the button as a square icon-only control with no visible label spacing */
14
+ iconOnly?: boolean;
15
+ /** Additional class names to merge with the component root element */
16
+ className?: string;
17
+ /** Button label/content */
18
+ children?: React.ReactNode;
19
+ /** Renders the button's props onto its single child element (via Radix `Slot`) instead of a `<button>` */
20
+ asChild?: boolean;
21
+ }
22
+ declare const Button: ({ variant, size, loading, iconOnly, disabled, className, children, asChild, ...rest }: ButtonProps) => react.JSX.Element;
23
+
24
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant };
package/dist/index.js ADDED
@@ -0,0 +1,196 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
28
+ var require_classnames = __commonJS({
29
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports, module) {
30
+ "use strict";
31
+ (function() {
32
+ "use strict";
33
+ var hasOwn = {}.hasOwnProperty;
34
+ function classNames() {
35
+ var classes = "";
36
+ for (var i = 0; i < arguments.length; i++) {
37
+ var arg = arguments[i];
38
+ if (arg) {
39
+ classes = appendClass(classes, parseValue(arg));
40
+ }
41
+ }
42
+ return classes;
43
+ }
44
+ function parseValue(arg) {
45
+ if (typeof arg === "string" || typeof arg === "number") {
46
+ return arg;
47
+ }
48
+ if (typeof arg !== "object") {
49
+ return "";
50
+ }
51
+ if (Array.isArray(arg)) {
52
+ return classNames.apply(null, arg);
53
+ }
54
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
55
+ return arg.toString();
56
+ }
57
+ var classes = "";
58
+ for (var key in arg) {
59
+ if (hasOwn.call(arg, key) && arg[key]) {
60
+ classes = appendClass(classes, key);
61
+ }
62
+ }
63
+ return classes;
64
+ }
65
+ function appendClass(value, newClass) {
66
+ if (!newClass) {
67
+ return value;
68
+ }
69
+ if (value) {
70
+ return value + " " + newClass;
71
+ }
72
+ return value + newClass;
73
+ }
74
+ if (typeof module !== "undefined" && module.exports) {
75
+ classNames.default = classNames;
76
+ module.exports = classNames;
77
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
78
+ define("classnames", [], function() {
79
+ return classNames;
80
+ });
81
+ } else {
82
+ window.classNames = classNames;
83
+ }
84
+ })();
85
+ }
86
+ });
87
+
88
+ // button.tsx
89
+ import { Slot } from "@radix-ui/react-slot";
90
+
91
+ // ../../../../packages/registry/class-names/dist/index.js
92
+ var import_classnames = __toESM(require_classnames(), 1);
93
+ var getClassName = ({
94
+ className,
95
+ modifiers = {},
96
+ rootClass,
97
+ styles = {}
98
+ }) => {
99
+ function getModifiers() {
100
+ const updatedModifiers = {};
101
+ const modifierKeys = Object.keys(modifiers);
102
+ if (modifierKeys.length) {
103
+ modifierKeys.forEach((modKey) => {
104
+ const classKey = `${rootClass}--${modKey}`;
105
+ const newClassName = styles[classKey] || classKey;
106
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
107
+ });
108
+ }
109
+ return updatedModifiers;
110
+ }
111
+ function getChildClass(childClassName) {
112
+ const classKey = `${rootClass}__${childClassName}`;
113
+ return styles[classKey] || classKey;
114
+ }
115
+ function getRootStyles() {
116
+ return (0, import_classnames.default)(
117
+ className,
118
+ {
119
+ ...getModifiers()
120
+ },
121
+ styles[rootClass] || rootClass
122
+ );
123
+ }
124
+ return [getRootStyles(), getChildClass];
125
+ };
126
+
127
+ // button.module.scss
128
+ var button_module_default = {
129
+ "BHT__Button": "BHT__Button",
130
+ "BHT__Button__label": "BHT__Button__label",
131
+ "BHT__Button__spinner": "BHT__Button__spinner",
132
+ "button-spin": "button-spin",
133
+ "BHT__Button--sm": "BHT__Button--sm",
134
+ "BHT__Button--md": "BHT__Button--md",
135
+ "BHT__Button--lg": "BHT__Button--lg",
136
+ "BHT__Button--primary": "BHT__Button--primary",
137
+ "BHT__Button--secondary": "BHT__Button--secondary",
138
+ "BHT__Button--ghost": "BHT__Button--ghost",
139
+ "BHT__Button--danger": "BHT__Button--danger",
140
+ "BHT__Button--icon-only": "BHT__Button--icon-only",
141
+ "BHT__Button--disabled": "BHT__Button--disabled",
142
+ "BHT__Button--loading": "BHT__Button--loading"
143
+ };
144
+
145
+ // button.tsx
146
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
147
+ var Button = ({
148
+ variant = "primary",
149
+ size = "md",
150
+ loading = false,
151
+ iconOnly = false,
152
+ disabled,
153
+ className,
154
+ children,
155
+ asChild = false,
156
+ ...rest
157
+ }) => {
158
+ const [rootClass, getChildClass] = getClassName({
159
+ className,
160
+ rootClass: "BHT__Button",
161
+ modifiers: {
162
+ [variant]: true,
163
+ [size]: true,
164
+ loading,
165
+ disabled: !!disabled,
166
+ "icon-only": iconOnly
167
+ },
168
+ styles: button_module_default
169
+ });
170
+ const Comp = asChild ? Slot : "button";
171
+ return /* @__PURE__ */ jsx(
172
+ Comp,
173
+ {
174
+ ...rest,
175
+ className: rootClass,
176
+ disabled: disabled || loading,
177
+ "aria-busy": loading || void 0,
178
+ children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
179
+ loading && /* @__PURE__ */ jsx("span", { className: getChildClass("spinner"), "aria-hidden": "true" }),
180
+ /* @__PURE__ */ jsx("span", { className: getChildClass("label"), children })
181
+ ] })
182
+ }
183
+ );
184
+ };
185
+ export {
186
+ Button
187
+ };
188
+ /*! Bundled license information:
189
+
190
+ classnames/index.js:
191
+ (*!
192
+ Copyright (c) 2018 Jed Watson.
193
+ Licensed under the MIT License (MIT), see
194
+ http://jedwatson.github.io/classnames
195
+ *)
196
+ */
package/docs.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
3
+ "version": 2,
4
+ "generatedAt": "2026-10-20T05:49:23.249Z",
5
+ "docs": [
6
+ {
7
+ "displayName": "Button",
8
+ "description": "",
9
+ "extendsNative": true,
10
+ "props": [
11
+ {
12
+ "name": "asChild",
13
+ "type": "boolean",
14
+ "required": false,
15
+ "defaultValue": "false",
16
+ "description": "Renders the button's props onto its single child element (via Radix `Slot`) instead of a `<button>`"
17
+ },
18
+ {
19
+ "name": "children",
20
+ "type": "ReactNode",
21
+ "required": false,
22
+ "defaultValue": null,
23
+ "description": "Button label/content"
24
+ },
25
+ {
26
+ "name": "className",
27
+ "type": "string",
28
+ "required": false,
29
+ "defaultValue": null,
30
+ "description": "Additional class names to merge with the component root element"
31
+ },
32
+ {
33
+ "name": "iconOnly",
34
+ "type": "boolean",
35
+ "required": false,
36
+ "defaultValue": "false",
37
+ "description": "Renders the button as a square icon-only control with no visible label spacing"
38
+ },
39
+ {
40
+ "name": "loading",
41
+ "type": "boolean",
42
+ "required": false,
43
+ "defaultValue": "false",
44
+ "description": "Shows a spinner and disables the button while an action is in flight"
45
+ },
46
+ {
47
+ "name": "size",
48
+ "type": "\"sm\" | \"md\" | \"lg\"",
49
+ "required": false,
50
+ "defaultValue": "md",
51
+ "description": "Controls the size; defaults to md"
52
+ },
53
+ {
54
+ "name": "variant",
55
+ "type": "\"primary\" | \"secondary\" | \"ghost\" | \"danger\"",
56
+ "required": false,
57
+ "defaultValue": "primary",
58
+ "description": "Controls the color/style treatment; defaults to primary"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "functions": []
64
+ }
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Button } from "./button.js";
2
+ export type { ButtonProps, ButtonVariant, ButtonSize } from "./button.js";
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@behivetech/atoms.button",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.4.2",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "private": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ },
13
+ "./styles.css": "./dist/index.css"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "index.ts",
19
+ "button.tsx",
20
+ "button.module.scss",
21
+ "*.example.tsx",
22
+ "docs.json"
23
+ ],
24
+ "registry": {
25
+ "status": "stable",
26
+ "tags": []
27
+ },
28
+ "peerDependencies": {
29
+ "@radix-ui/react-slot": "^1.1.0",
30
+ "react": "^19.2.7",
31
+ "react-dom": "^19.2.7"
32
+ },
33
+ "devDependencies": {
34
+ "@radix-ui/react-slot": "^1.3.3",
35
+ "@types/react": "19.2.17",
36
+ "@types/react-dom": "19.2.3",
37
+ "eslint": "^9.39.5",
38
+ "tsup": "^8.5.1",
39
+ "typescript": "^6.0.3",
40
+ "vitest": "^4.1.10",
41
+ "@bht-component-registry/env": "0.1.0",
42
+ "@bht-component-registry/class-names": "0.0.6"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/behivetech/bht-galaxy.git",
47
+ "directory": "registry/behivetech/atoms/button"
48
+ },
49
+ "publishConfig": {
50
+ "registry": "https://registry.npmjs.org",
51
+ "access": "public",
52
+ "provenance": true
53
+ },
54
+ "scripts": {
55
+ "build": "tsup && bht-registry-docgen",
56
+ "dev": "tsup --watch",
57
+ "lint": "eslint . --max-warnings 0",
58
+ "check-types": "tsc --noEmit",
59
+ "test": "vitest run"
60
+ }
6
61
  }