@behivetech/atoms.card 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 +93 -2
- package/card.example.tsx +48 -0
- package/card.module.scss +178 -0
- package/card.tsx +116 -0
- package/dist/index.cjs +219 -0
- package/dist/index.css +111 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +207 -0
- package/docs.json +78 -0
- package/index.ts +2 -0
- package/package.json +58 -3
package/README.md
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Card
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A surface container with configurable padding and an optional header (icon + title + an `action` slot).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
import { Card } from "@behivetech/atoms.card";
|
|
9
|
+
|
|
10
|
+
function Example() {
|
|
11
|
+
const onClose = () => alert("closed");
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Card padding="md">Content</Card>
|
|
15
|
+
|
|
16
|
+
<Card title="Settings" padding="md">
|
|
17
|
+
Content
|
|
18
|
+
</Card>
|
|
19
|
+
|
|
20
|
+
<Card
|
|
21
|
+
icon="🧩"
|
|
22
|
+
title="Block"
|
|
23
|
+
action={<button onClick={onClose}>✕</button>}
|
|
24
|
+
padding="md"
|
|
25
|
+
>
|
|
26
|
+
Content
|
|
27
|
+
</Card>
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `action` slot is freeform — pass a close button, a menu, multiple buttons, or nothing. If `icon`, `title`, and `action` are all omitted, no header is rendered and `children` render directly in the card (unchanged from a plain `Card`). When a header is present, `padding` applies to the body instead of the whole card, so the header can span edge-to-edge with its own fixed padding.
|
|
34
|
+
|
|
35
|
+
## Heading level
|
|
36
|
+
|
|
37
|
+
`title` renders inside an `h1`–`h6` tag (not a `span`) so it takes its correct place in the page's heading outline — set via `headingLevel`, which defaults to `3`. The default assumes the common case: a page's own `h1`, sections within it titled `h2`, and a `Card` nested inside one of those sections landing at `h3`. Override it when a card sits at a different depth — e.g. `headingLevel={2}` for a card used directly under a page's `h1` with no intervening section:
|
|
38
|
+
|
|
39
|
+
```jsx
|
|
40
|
+
<Card title="Settings" headingLevel={2} padding="md">
|
|
41
|
+
Content
|
|
42
|
+
</Card>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Root element
|
|
46
|
+
|
|
47
|
+
The root renders as a `<section>`, labelled by `title` via `aria-labelledby`, whenever a `title` is given — an HTML5 `<section>` should have an accessible heading, and `title` is exactly that. With no `title` (a bare surface, or a header made up of just `icon`/`action`), the root stays a plain `<div>`, since an unlabelled `<section>` isn't a meaningful landmark.
|
|
48
|
+
|
|
49
|
+
## Interactive (clickable) cards
|
|
50
|
+
|
|
51
|
+
Pass `onSelect` to turn the body into a real `<button>` — hover/focus affordance plus a centered, stacked content layout — for tile/action-card patterns like a component picker grid:
|
|
52
|
+
|
|
53
|
+
```jsx
|
|
54
|
+
import { Card } from "@behivetech/atoms.card";
|
|
55
|
+
import { Icon } from "@behivetech/atoms.icon";
|
|
56
|
+
|
|
57
|
+
function Example() {
|
|
58
|
+
const item = { name: "Hero banner" };
|
|
59
|
+
const handleSelect = (selected) => alert(selected.name);
|
|
60
|
+
return (
|
|
61
|
+
<Card padding="md" onSelect={() => handleSelect(item)}>
|
|
62
|
+
<Icon name="box" size="lg" />
|
|
63
|
+
<span>{item.name}</span>
|
|
64
|
+
</Card>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This is the same job MUI's `CardActionArea` does, folded into `Card` itself rather than a separate component — `children` must stay to phrasing content (text, spans, icons), since a native `<button>` can't contain block-level elements like `<div>`. `onSelect` is deliberately distinct from `onClick`: `onClick` is the plain pass-through to the root element (e.g. to stop click propagation on a whole panel), while `onSelect` specifically drives the interactive-body behavior described here.
|
|
70
|
+
|
|
71
|
+
## Overriding surface styling
|
|
72
|
+
|
|
73
|
+
`background`/`border`/`border-radius` read from `--card-background`/`--card-border`/`--card-radius` custom properties (falling back to the design tokens above). A consumer embedding a `Card` for a different context — a docked panel with no elevation, a modal with a larger radius — can set these on its own wrapping class instead of fighting CSS-module specificity:
|
|
74
|
+
|
|
75
|
+
```scss
|
|
76
|
+
.my-docked-panel {
|
|
77
|
+
--card-border: none;
|
|
78
|
+
--card-radius: 0;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Props
|
|
83
|
+
|
|
84
|
+
| Prop | Type | Default | Description |
|
|
85
|
+
| ---------------- | -------------------------------------- | ------- | --------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `padding` | `none \| sm \| md \| lg` | `none` | Internal padding — applies to the body when a header is present |
|
|
87
|
+
| `icon` | `ReactNode` | — | Optional icon rendered before the title |
|
|
88
|
+
| `title` | `ReactNode` | — | Optional header title |
|
|
89
|
+
| `headingLevel` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `3` | Which heading tag wraps `title` |
|
|
90
|
+
| `action` | `ReactNode` | — | Optional content rendered at the end of the header |
|
|
91
|
+
| `onSelect` | `MouseEventHandler<HTMLButtonElement>` | — | Makes the body a clickable `<button>` with hover/focus affordance and a centered stack layout |
|
|
92
|
+
| `scrollableBody` | `boolean` | `false` | Keeps the header fixed and scrolls only the body; for cards with a bounded height |
|
|
93
|
+
| `children` | `ReactNode` | — | Card content |
|
|
94
|
+
| `className` | `string` | — | Additional class names |
|
package/card.example.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Card } from "./card.js";
|
|
2
|
+
|
|
3
|
+
export const DefaultCard = () => (
|
|
4
|
+
<Card padding="md">Default card with md padding</Card>
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export const NoPaddingCard = () => (
|
|
8
|
+
<Card padding="none">Card with no padding</Card>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export const SmallPaddingCard = () => (
|
|
12
|
+
<Card padding="sm">Card with sm padding</Card>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const LargePaddingCard = () => (
|
|
16
|
+
<Card padding="lg">Card with lg padding</Card>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const CardWithHeader = () => (
|
|
20
|
+
<Card title="Settings" padding="md">
|
|
21
|
+
Card body content
|
|
22
|
+
</Card>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const CardWithIconAndAction = () => (
|
|
26
|
+
<Card
|
|
27
|
+
icon="🧩"
|
|
28
|
+
title="Block"
|
|
29
|
+
action={<button onClick={() => {}}>✕</button>}
|
|
30
|
+
padding="md"
|
|
31
|
+
>
|
|
32
|
+
Card body content
|
|
33
|
+
</Card>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const InteractiveCard = () => (
|
|
37
|
+
<Card padding="md" onSelect={() => {}}>
|
|
38
|
+
<span>🧩</span>
|
|
39
|
+
<span>Click me</span>
|
|
40
|
+
</Card>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export const CardAsPageLevelSection = () => (
|
|
44
|
+
<Card title="Settings" headingLevel={2} padding="md">
|
|
45
|
+
Used directly under a page’s h1, with no intervening section — its
|
|
46
|
+
title takes the h2 slot instead of the h3 default.
|
|
47
|
+
</Card>
|
|
48
|
+
);
|
package/card.module.scss
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Material Design 3 outlined card.
|
|
2
|
+
//
|
|
3
|
+
// Modal and Drawer render their panels as a Card and re-theme it through the
|
|
4
|
+
// `--card-background` / `--card-border` / `--card-radius` custom properties
|
|
5
|
+
// rather than by reaching into these classes, so those three stay the only
|
|
6
|
+
// public styling hooks. Each color falls back to the pre-MD3 `--color-*` name,
|
|
7
|
+
// which `cms.base-styles` aliases to the matching MD3 role.
|
|
8
|
+
|
|
9
|
+
.BHT__Card {
|
|
10
|
+
background: var(
|
|
11
|
+
--card-background,
|
|
12
|
+
var(--md-sys-color-surface, var(--color-surface))
|
|
13
|
+
);
|
|
14
|
+
color: var(--md-sys-color-on-surface, var(--color-on-surface));
|
|
15
|
+
border: var(
|
|
16
|
+
--card-border,
|
|
17
|
+
1px solid var(--md-sys-color-outline-variant, var(--color-border))
|
|
18
|
+
);
|
|
19
|
+
border-radius: var(--card-radius, var(--md-sys-shape-corner-medium, 12px));
|
|
20
|
+
// Grid/flex items default to their content's min-content width, not their
|
|
21
|
+
// track/basis minimum — without this, a Card holding wide content (a long
|
|
22
|
+
// stat value, an action row) forces its container wider instead of shrinking.
|
|
23
|
+
min-width: 0;
|
|
24
|
+
|
|
25
|
+
&__header {
|
|
26
|
+
padding: var(--spacing-4) var(--spacing-6);
|
|
27
|
+
border-bottom: 1px solid
|
|
28
|
+
var(--md-sys-color-outline-variant, var(--color-border));
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
gap: var(--spacing-4);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&__heading {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: var(--spacing-2);
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&__icon {
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
line-height: 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&__title {
|
|
48
|
+
margin: 0;
|
|
49
|
+
font-family: var(--md-sys-typescale-title-medium-font, var(--font-heading));
|
|
50
|
+
font-size: var(--md-sys-typescale-title-medium-size, 1rem);
|
|
51
|
+
font-weight: var(--md-sys-typescale-title-medium-weight, 500);
|
|
52
|
+
line-height: var(--md-sys-typescale-title-medium-line-height, 1.5rem);
|
|
53
|
+
letter-spacing: var(--md-sys-typescale-title-medium-tracking, 0.0094em);
|
|
54
|
+
color: var(--md-sys-color-on-surface, var(--color-text-heading));
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
text-overflow: ellipsis;
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&__action {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: var(--spacing-2);
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&__body-padding {
|
|
68
|
+
/* Resets so this works whether it renders as a <div> (static) or a
|
|
69
|
+
<button> (interactive) — the button UA defaults are what actually
|
|
70
|
+
need overriding here. */
|
|
71
|
+
display: block;
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding: 0;
|
|
74
|
+
margin: 0;
|
|
75
|
+
background: none;
|
|
76
|
+
border: none;
|
|
77
|
+
font: inherit;
|
|
78
|
+
color: inherit;
|
|
79
|
+
text-align: inherit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&--interactive {
|
|
83
|
+
&.BHT__Card {
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.BHT__Card {
|
|
88
|
+
&__body-padding {
|
|
89
|
+
position: relative;
|
|
90
|
+
isolation: isolate;
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: var(--spacing-2);
|
|
95
|
+
text-align: center;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
|
|
98
|
+
// State layer: a veil of the content color, as on Button, so hover and
|
|
99
|
+
// press follow the palette instead of swapping to a fixed surface.
|
|
100
|
+
&::after {
|
|
101
|
+
content: "";
|
|
102
|
+
position: absolute;
|
|
103
|
+
inset: 0;
|
|
104
|
+
background: currentColor;
|
|
105
|
+
opacity: 0;
|
|
106
|
+
pointer-events: none;
|
|
107
|
+
transition: opacity var(--md-sys-motion-duration-short-2, 100ms)
|
|
108
|
+
var(--md-sys-motion-easing-standard, ease);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&:hover::after {
|
|
112
|
+
opacity: var(--md-sys-state-hover-opacity, 0.08);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&:focus-visible::after {
|
|
116
|
+
opacity: var(--md-sys-state-focus-opacity, 0.1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&:active::after {
|
|
120
|
+
opacity: var(--md-sys-state-pressed-opacity, 0.1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Inset rather than the usual +2px offset: the root's `overflow:
|
|
124
|
+
// hidden` (needed to clip the state layer to the corners) would cut
|
|
125
|
+
// off a ring drawn outside the body.
|
|
126
|
+
&:focus-visible {
|
|
127
|
+
outline: 3px solid
|
|
128
|
+
var(--md-sys-color-secondary, var(--color-border-focus));
|
|
129
|
+
outline-offset: -3px;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Header stays put; only the body scrolls when content exceeds the card's
|
|
136
|
+
// height — for cards given a bounded height (a modal or drawer panel).
|
|
137
|
+
&--scrollable-body {
|
|
138
|
+
&.BHT__Card {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.BHT__Card {
|
|
145
|
+
&__body-padding {
|
|
146
|
+
overflow-y: auto;
|
|
147
|
+
flex: 1;
|
|
148
|
+
min-height: 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// &--padding-none needs no rule — &__body-padding already resets padding to 0 above.
|
|
154
|
+
|
|
155
|
+
&--padding-sm {
|
|
156
|
+
.BHT__Card {
|
|
157
|
+
&__body-padding {
|
|
158
|
+
padding: var(--spacing-3);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&--padding-md {
|
|
164
|
+
.BHT__Card {
|
|
165
|
+
&__body-padding {
|
|
166
|
+
padding: var(--spacing-5);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&--padding-lg {
|
|
172
|
+
.BHT__Card {
|
|
173
|
+
&__body-padding {
|
|
174
|
+
padding: var(--spacing-8);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
package/card.tsx
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useId,
|
|
3
|
+
type HTMLAttributes,
|
|
4
|
+
type MouseEventHandler,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { getClassName } from "@bht-component-registry/class-names";
|
|
8
|
+
import styles from "./card.module.scss";
|
|
9
|
+
|
|
10
|
+
export type CardPadding = "none" | "sm" | "md" | "lg";
|
|
11
|
+
export type CardHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
12
|
+
|
|
13
|
+
export interface CardProps extends Omit<
|
|
14
|
+
HTMLAttributes<HTMLDivElement>,
|
|
15
|
+
"title" | "onSelect"
|
|
16
|
+
> {
|
|
17
|
+
/** Controls internal padding; defaults to none so consumers opt in. Applies to the body when a header is present. */
|
|
18
|
+
padding?: CardPadding;
|
|
19
|
+
/** Optional icon rendered before the title in the header */
|
|
20
|
+
icon?: ReactNode;
|
|
21
|
+
/** Optional header title; omit along with `icon`/`action` to render no header */
|
|
22
|
+
title?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Which heading tag (`h1`–`h6`) wraps `title`, so the card's title takes its
|
|
25
|
+
* correct place in the page's heading outline. Defaults to `3`, matching a
|
|
26
|
+
* card nested one level below a page's `h1` title and a `section`'s `h2` —
|
|
27
|
+
* override per-instance when a card sits at a different depth.
|
|
28
|
+
*/
|
|
29
|
+
headingLevel?: CardHeadingLevel;
|
|
30
|
+
/** Optional content rendered at the end of the header (buttons, menus, a close control, etc.) */
|
|
31
|
+
action?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Makes the body a real `<button>` — hover/focus affordance plus a centered,
|
|
34
|
+
* stacked content layout — for tile/action-card patterns (a picker grid,
|
|
35
|
+
* for example) instead of a static surface. Distinct from `onClick` (a plain
|
|
36
|
+
* pass-through to the root element, e.g. for stopping click propagation).
|
|
37
|
+
* Omit for a plain, non-interactive card.
|
|
38
|
+
*/
|
|
39
|
+
onSelect?: MouseEventHandler<HTMLButtonElement>;
|
|
40
|
+
/**
|
|
41
|
+
* Keeps the header fixed and scrolls only the body when content exceeds the
|
|
42
|
+
* card's height. Only meaningful when the card has a bounded height (a
|
|
43
|
+
* modal or drawer panel, for example).
|
|
44
|
+
*/
|
|
45
|
+
scrollableBody?: boolean;
|
|
46
|
+
/** Additional class names to merge with the component root element */
|
|
47
|
+
className?: string;
|
|
48
|
+
/** Card body content */
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const Card = ({
|
|
53
|
+
padding = "none",
|
|
54
|
+
icon,
|
|
55
|
+
title,
|
|
56
|
+
headingLevel = 3,
|
|
57
|
+
action,
|
|
58
|
+
onSelect,
|
|
59
|
+
scrollableBody = false,
|
|
60
|
+
className,
|
|
61
|
+
children,
|
|
62
|
+
...rest
|
|
63
|
+
}: CardProps) => {
|
|
64
|
+
const generatedTitleId = useId();
|
|
65
|
+
const hasHeader =
|
|
66
|
+
icon !== undefined || title !== undefined || action !== undefined;
|
|
67
|
+
const hasTitle = title !== undefined;
|
|
68
|
+
const interactive = onSelect !== undefined;
|
|
69
|
+
const HeadingTag = `h${headingLevel}` as React.ElementType;
|
|
70
|
+
// A section with no accessible name is not a meaningful landmark, so only
|
|
71
|
+
// use <section> when there's an actual heading (title) to name it with.
|
|
72
|
+
const RootTag = (hasTitle ? "section" : "div") as React.ElementType;
|
|
73
|
+
const titleId = hasTitle ? `${generatedTitleId}-title` : undefined;
|
|
74
|
+
|
|
75
|
+
const [rootClass, getChildClass] = getClassName({
|
|
76
|
+
className,
|
|
77
|
+
rootClass: "BHT__Card",
|
|
78
|
+
modifiers: {
|
|
79
|
+
[`padding-${padding}`]: true,
|
|
80
|
+
interactive,
|
|
81
|
+
"scrollable-body": scrollableBody,
|
|
82
|
+
},
|
|
83
|
+
styles,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const BodyTag = (interactive ? "button" : "div") as React.ElementType;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<RootTag {...rest} className={rootClass} aria-labelledby={titleId}>
|
|
90
|
+
{hasHeader && (
|
|
91
|
+
<div className={getChildClass("header")}>
|
|
92
|
+
<span className={getChildClass("heading")}>
|
|
93
|
+
{icon !== undefined && (
|
|
94
|
+
<span className={getChildClass("icon")}>{icon}</span>
|
|
95
|
+
)}
|
|
96
|
+
{hasTitle && (
|
|
97
|
+
<HeadingTag id={titleId} className={getChildClass("title")}>
|
|
98
|
+
{title}
|
|
99
|
+
</HeadingTag>
|
|
100
|
+
)}
|
|
101
|
+
</span>
|
|
102
|
+
{action !== undefined && (
|
|
103
|
+
<span className={getChildClass("action")}>{action}</span>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
<BodyTag
|
|
108
|
+
type={interactive ? "button" : undefined}
|
|
109
|
+
onClick={onSelect}
|
|
110
|
+
className={getChildClass("body-padding")}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</BodyTag>
|
|
114
|
+
</RootTag>
|
|
115
|
+
);
|
|
116
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
Card: () => Card
|
|
98
|
+
});
|
|
99
|
+
module.exports = __toCommonJS(index_exports);
|
|
100
|
+
|
|
101
|
+
// card.tsx
|
|
102
|
+
var import_react = require("react");
|
|
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
|
+
// card.module.scss
|
|
141
|
+
var card_module_default = {
|
|
142
|
+
"BHT__Card": "BHT__Card",
|
|
143
|
+
"BHT__Card__header": "BHT__Card__header",
|
|
144
|
+
"BHT__Card__heading": "BHT__Card__heading",
|
|
145
|
+
"BHT__Card__icon": "BHT__Card__icon",
|
|
146
|
+
"BHT__Card__title": "BHT__Card__title",
|
|
147
|
+
"BHT__Card__action": "BHT__Card__action",
|
|
148
|
+
"BHT__Card__body-padding": "BHT__Card__body-padding",
|
|
149
|
+
"BHT__Card--interactive": "BHT__Card--interactive",
|
|
150
|
+
"BHT__Card--scrollable-body": "BHT__Card--scrollable-body",
|
|
151
|
+
"BHT__Card--padding-sm": "BHT__Card--padding-sm",
|
|
152
|
+
"BHT__Card--padding-md": "BHT__Card--padding-md",
|
|
153
|
+
"BHT__Card--padding-lg": "BHT__Card--padding-lg"
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// card.tsx
|
|
157
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
158
|
+
var Card = ({
|
|
159
|
+
padding = "none",
|
|
160
|
+
icon,
|
|
161
|
+
title,
|
|
162
|
+
headingLevel = 3,
|
|
163
|
+
action,
|
|
164
|
+
onSelect,
|
|
165
|
+
scrollableBody = false,
|
|
166
|
+
className,
|
|
167
|
+
children,
|
|
168
|
+
...rest
|
|
169
|
+
}) => {
|
|
170
|
+
const generatedTitleId = (0, import_react.useId)();
|
|
171
|
+
const hasHeader = icon !== void 0 || title !== void 0 || action !== void 0;
|
|
172
|
+
const hasTitle = title !== void 0;
|
|
173
|
+
const interactive = onSelect !== void 0;
|
|
174
|
+
const HeadingTag = `h${headingLevel}`;
|
|
175
|
+
const RootTag = hasTitle ? "section" : "div";
|
|
176
|
+
const titleId = hasTitle ? `${generatedTitleId}-title` : void 0;
|
|
177
|
+
const [rootClass, getChildClass] = getClassName({
|
|
178
|
+
className,
|
|
179
|
+
rootClass: "BHT__Card",
|
|
180
|
+
modifiers: {
|
|
181
|
+
[`padding-${padding}`]: true,
|
|
182
|
+
interactive,
|
|
183
|
+
"scrollable-body": scrollableBody
|
|
184
|
+
},
|
|
185
|
+
styles: card_module_default
|
|
186
|
+
});
|
|
187
|
+
const BodyTag = interactive ? "button" : "div";
|
|
188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RootTag, { ...rest, className: rootClass, "aria-labelledby": titleId, children: [
|
|
189
|
+
hasHeader && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: getChildClass("header"), children: [
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: getChildClass("heading"), children: [
|
|
191
|
+
icon !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("icon"), children: icon }),
|
|
192
|
+
hasTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadingTag, { id: titleId, className: getChildClass("title"), children: title })
|
|
193
|
+
] }),
|
|
194
|
+
action !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("action"), children: action })
|
|
195
|
+
] }),
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
197
|
+
BodyTag,
|
|
198
|
+
{
|
|
199
|
+
type: interactive ? "button" : void 0,
|
|
200
|
+
onClick: onSelect,
|
|
201
|
+
className: getChildClass("body-padding"),
|
|
202
|
+
children
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] });
|
|
206
|
+
};
|
|
207
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
208
|
+
0 && (module.exports = {
|
|
209
|
+
Card
|
|
210
|
+
});
|
|
211
|
+
/*! Bundled license information:
|
|
212
|
+
|
|
213
|
+
classnames/index.js:
|
|
214
|
+
(*!
|
|
215
|
+
Copyright (c) 2018 Jed Watson.
|
|
216
|
+
Licensed under the MIT License (MIT), see
|
|
217
|
+
http://jedwatson.github.io/classnames
|
|
218
|
+
*)
|
|
219
|
+
*/
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
@charset "UTF-8";
|
|
2
|
+
|
|
3
|
+
/* esbuild-sass-plugin:css-chunkcard.module.scss */
|
|
4
|
+
.BHT__Card {
|
|
5
|
+
background: var(--card-background, var(--md-sys-color-surface, var(--color-surface)));
|
|
6
|
+
color: var(--md-sys-color-on-surface, var(--color-on-surface));
|
|
7
|
+
border: var(--card-border, 1px solid var(--md-sys-color-outline-variant, var(--color-border)));
|
|
8
|
+
border-radius: var(--card-radius, var(--md-sys-shape-corner-medium, 12px));
|
|
9
|
+
min-width: 0;
|
|
10
|
+
}
|
|
11
|
+
.BHT__Card__header {
|
|
12
|
+
padding: var(--spacing-4) var(--spacing-6);
|
|
13
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant, var(--color-border));
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
gap: var(--spacing-4);
|
|
18
|
+
}
|
|
19
|
+
.BHT__Card__heading {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--spacing-2);
|
|
23
|
+
min-width: 0;
|
|
24
|
+
}
|
|
25
|
+
.BHT__Card__icon {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
line-height: 1;
|
|
28
|
+
}
|
|
29
|
+
.BHT__Card__title {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-family: var(--md-sys-typescale-title-medium-font, var(--font-heading));
|
|
32
|
+
font-size: var(--md-sys-typescale-title-medium-size, 1rem);
|
|
33
|
+
font-weight: var(--md-sys-typescale-title-medium-weight, 500);
|
|
34
|
+
line-height: var(--md-sys-typescale-title-medium-line-height, 1.5rem);
|
|
35
|
+
letter-spacing: var(--md-sys-typescale-title-medium-tracking, 0.0094em);
|
|
36
|
+
color: var(--md-sys-color-on-surface, var(--color-text-heading));
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
text-overflow: ellipsis;
|
|
39
|
+
white-space: nowrap;
|
|
40
|
+
}
|
|
41
|
+
.BHT__Card__action {
|
|
42
|
+
display: inline-flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: var(--spacing-2);
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
}
|
|
47
|
+
.BHT__Card__body-padding {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 100%;
|
|
50
|
+
padding: 0;
|
|
51
|
+
margin: 0;
|
|
52
|
+
background: none;
|
|
53
|
+
border: none;
|
|
54
|
+
font: inherit;
|
|
55
|
+
color: inherit;
|
|
56
|
+
text-align: inherit;
|
|
57
|
+
}
|
|
58
|
+
.BHT__Card--interactive.BHT__Card {
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
.BHT__Card--interactive .BHT__Card__body-padding {
|
|
62
|
+
position: relative;
|
|
63
|
+
isolation: isolate;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: var(--spacing-2);
|
|
68
|
+
text-align: center;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
}
|
|
71
|
+
.BHT__Card--interactive .BHT__Card__body-padding::after {
|
|
72
|
+
content: "";
|
|
73
|
+
position: absolute;
|
|
74
|
+
inset: 0;
|
|
75
|
+
background: currentColor;
|
|
76
|
+
opacity: 0;
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
transition: opacity var(--md-sys-motion-duration-short-2, 100ms) var(--md-sys-motion-easing-standard, ease);
|
|
79
|
+
}
|
|
80
|
+
.BHT__Card--interactive .BHT__Card__body-padding:hover::after {
|
|
81
|
+
opacity: var(--md-sys-state-hover-opacity, 0.08);
|
|
82
|
+
}
|
|
83
|
+
.BHT__Card--interactive .BHT__Card__body-padding:focus-visible::after {
|
|
84
|
+
opacity: var(--md-sys-state-focus-opacity, 0.1);
|
|
85
|
+
}
|
|
86
|
+
.BHT__Card--interactive .BHT__Card__body-padding:active::after {
|
|
87
|
+
opacity: var(--md-sys-state-pressed-opacity, 0.1);
|
|
88
|
+
}
|
|
89
|
+
.BHT__Card--interactive .BHT__Card__body-padding:focus-visible {
|
|
90
|
+
outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
|
|
91
|
+
outline-offset: -3px;
|
|
92
|
+
}
|
|
93
|
+
.BHT__Card--scrollable-body.BHT__Card {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
.BHT__Card--scrollable-body .BHT__Card__body-padding {
|
|
99
|
+
overflow-y: auto;
|
|
100
|
+
flex: 1;
|
|
101
|
+
min-height: 0;
|
|
102
|
+
}
|
|
103
|
+
.BHT__Card--padding-sm .BHT__Card__body-padding {
|
|
104
|
+
padding: var(--spacing-3);
|
|
105
|
+
}
|
|
106
|
+
.BHT__Card--padding-md .BHT__Card__body-padding {
|
|
107
|
+
padding: var(--spacing-5);
|
|
108
|
+
}
|
|
109
|
+
.BHT__Card--padding-lg .BHT__Card__body-padding {
|
|
110
|
+
padding: var(--spacing-8);
|
|
111
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode, MouseEventHandler } from 'react';
|
|
3
|
+
|
|
4
|
+
type CardPadding = "none" | "sm" | "md" | "lg";
|
|
5
|
+
type CardHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
|
+
interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onSelect"> {
|
|
7
|
+
/** Controls internal padding; defaults to none so consumers opt in. Applies to the body when a header is present. */
|
|
8
|
+
padding?: CardPadding;
|
|
9
|
+
/** Optional icon rendered before the title in the header */
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
/** Optional header title; omit along with `icon`/`action` to render no header */
|
|
12
|
+
title?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Which heading tag (`h1`–`h6`) wraps `title`, so the card's title takes its
|
|
15
|
+
* correct place in the page's heading outline. Defaults to `3`, matching a
|
|
16
|
+
* card nested one level below a page's `h1` title and a `section`'s `h2` —
|
|
17
|
+
* override per-instance when a card sits at a different depth.
|
|
18
|
+
*/
|
|
19
|
+
headingLevel?: CardHeadingLevel;
|
|
20
|
+
/** Optional content rendered at the end of the header (buttons, menus, a close control, etc.) */
|
|
21
|
+
action?: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Makes the body a real `<button>` — hover/focus affordance plus a centered,
|
|
24
|
+
* stacked content layout — for tile/action-card patterns (a picker grid,
|
|
25
|
+
* for example) instead of a static surface. Distinct from `onClick` (a plain
|
|
26
|
+
* pass-through to the root element, e.g. for stopping click propagation).
|
|
27
|
+
* Omit for a plain, non-interactive card.
|
|
28
|
+
*/
|
|
29
|
+
onSelect?: MouseEventHandler<HTMLButtonElement>;
|
|
30
|
+
/**
|
|
31
|
+
* Keeps the header fixed and scrolls only the body when content exceeds the
|
|
32
|
+
* card's height. Only meaningful when the card has a bounded height (a
|
|
33
|
+
* modal or drawer panel, for example).
|
|
34
|
+
*/
|
|
35
|
+
scrollableBody?: boolean;
|
|
36
|
+
/** Additional class names to merge with the component root element */
|
|
37
|
+
className?: string;
|
|
38
|
+
/** Card body content */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
declare const Card: ({ padding, icon, title, headingLevel, action, onSelect, scrollableBody, className, children, ...rest }: CardProps) => react.JSX.Element;
|
|
42
|
+
|
|
43
|
+
export { Card, type CardHeadingLevel, type CardPadding, type CardProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
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
|
+
// card.tsx
|
|
89
|
+
import {
|
|
90
|
+
useId
|
|
91
|
+
} from "react";
|
|
92
|
+
|
|
93
|
+
// ../../../../packages/registry/class-names/dist/index.js
|
|
94
|
+
var import_classnames = __toESM(require_classnames(), 1);
|
|
95
|
+
var getClassName = ({
|
|
96
|
+
className,
|
|
97
|
+
modifiers = {},
|
|
98
|
+
rootClass,
|
|
99
|
+
styles = {}
|
|
100
|
+
}) => {
|
|
101
|
+
function getModifiers() {
|
|
102
|
+
const updatedModifiers = {};
|
|
103
|
+
const modifierKeys = Object.keys(modifiers);
|
|
104
|
+
if (modifierKeys.length) {
|
|
105
|
+
modifierKeys.forEach((modKey) => {
|
|
106
|
+
const classKey = `${rootClass}--${modKey}`;
|
|
107
|
+
const newClassName = styles[classKey] || classKey;
|
|
108
|
+
updatedModifiers[newClassName] = modifiers[modKey] ?? false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return updatedModifiers;
|
|
112
|
+
}
|
|
113
|
+
function getChildClass(childClassName) {
|
|
114
|
+
const classKey = `${rootClass}__${childClassName}`;
|
|
115
|
+
return styles[classKey] || classKey;
|
|
116
|
+
}
|
|
117
|
+
function getRootStyles() {
|
|
118
|
+
return (0, import_classnames.default)(
|
|
119
|
+
className,
|
|
120
|
+
{
|
|
121
|
+
...getModifiers()
|
|
122
|
+
},
|
|
123
|
+
styles[rootClass] || rootClass
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return [getRootStyles(), getChildClass];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// card.module.scss
|
|
130
|
+
var card_module_default = {
|
|
131
|
+
"BHT__Card": "BHT__Card",
|
|
132
|
+
"BHT__Card__header": "BHT__Card__header",
|
|
133
|
+
"BHT__Card__heading": "BHT__Card__heading",
|
|
134
|
+
"BHT__Card__icon": "BHT__Card__icon",
|
|
135
|
+
"BHT__Card__title": "BHT__Card__title",
|
|
136
|
+
"BHT__Card__action": "BHT__Card__action",
|
|
137
|
+
"BHT__Card__body-padding": "BHT__Card__body-padding",
|
|
138
|
+
"BHT__Card--interactive": "BHT__Card--interactive",
|
|
139
|
+
"BHT__Card--scrollable-body": "BHT__Card--scrollable-body",
|
|
140
|
+
"BHT__Card--padding-sm": "BHT__Card--padding-sm",
|
|
141
|
+
"BHT__Card--padding-md": "BHT__Card--padding-md",
|
|
142
|
+
"BHT__Card--padding-lg": "BHT__Card--padding-lg"
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// card.tsx
|
|
146
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
147
|
+
var Card = ({
|
|
148
|
+
padding = "none",
|
|
149
|
+
icon,
|
|
150
|
+
title,
|
|
151
|
+
headingLevel = 3,
|
|
152
|
+
action,
|
|
153
|
+
onSelect,
|
|
154
|
+
scrollableBody = false,
|
|
155
|
+
className,
|
|
156
|
+
children,
|
|
157
|
+
...rest
|
|
158
|
+
}) => {
|
|
159
|
+
const generatedTitleId = useId();
|
|
160
|
+
const hasHeader = icon !== void 0 || title !== void 0 || action !== void 0;
|
|
161
|
+
const hasTitle = title !== void 0;
|
|
162
|
+
const interactive = onSelect !== void 0;
|
|
163
|
+
const HeadingTag = `h${headingLevel}`;
|
|
164
|
+
const RootTag = hasTitle ? "section" : "div";
|
|
165
|
+
const titleId = hasTitle ? `${generatedTitleId}-title` : void 0;
|
|
166
|
+
const [rootClass, getChildClass] = getClassName({
|
|
167
|
+
className,
|
|
168
|
+
rootClass: "BHT__Card",
|
|
169
|
+
modifiers: {
|
|
170
|
+
[`padding-${padding}`]: true,
|
|
171
|
+
interactive,
|
|
172
|
+
"scrollable-body": scrollableBody
|
|
173
|
+
},
|
|
174
|
+
styles: card_module_default
|
|
175
|
+
});
|
|
176
|
+
const BodyTag = interactive ? "button" : "div";
|
|
177
|
+
return /* @__PURE__ */ jsxs(RootTag, { ...rest, className: rootClass, "aria-labelledby": titleId, children: [
|
|
178
|
+
hasHeader && /* @__PURE__ */ jsxs("div", { className: getChildClass("header"), children: [
|
|
179
|
+
/* @__PURE__ */ jsxs("span", { className: getChildClass("heading"), children: [
|
|
180
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", { className: getChildClass("icon"), children: icon }),
|
|
181
|
+
hasTitle && /* @__PURE__ */ jsx(HeadingTag, { id: titleId, className: getChildClass("title"), children: title })
|
|
182
|
+
] }),
|
|
183
|
+
action !== void 0 && /* @__PURE__ */ jsx("span", { className: getChildClass("action"), children: action })
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ jsx(
|
|
186
|
+
BodyTag,
|
|
187
|
+
{
|
|
188
|
+
type: interactive ? "button" : void 0,
|
|
189
|
+
onClick: onSelect,
|
|
190
|
+
className: getChildClass("body-padding"),
|
|
191
|
+
children
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
] });
|
|
195
|
+
};
|
|
196
|
+
export {
|
|
197
|
+
Card
|
|
198
|
+
};
|
|
199
|
+
/*! Bundled license information:
|
|
200
|
+
|
|
201
|
+
classnames/index.js:
|
|
202
|
+
(*!
|
|
203
|
+
Copyright (c) 2018 Jed Watson.
|
|
204
|
+
Licensed under the MIT License (MIT), see
|
|
205
|
+
http://jedwatson.github.io/classnames
|
|
206
|
+
*)
|
|
207
|
+
*/
|
package/docs.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
|
|
3
|
+
"version": 2,
|
|
4
|
+
"generatedAt": "2026-10-20T05:49:42.170Z",
|
|
5
|
+
"docs": [
|
|
6
|
+
{
|
|
7
|
+
"displayName": "Card",
|
|
8
|
+
"description": "",
|
|
9
|
+
"extendsNative": true,
|
|
10
|
+
"props": [
|
|
11
|
+
{
|
|
12
|
+
"name": "action",
|
|
13
|
+
"type": "ReactNode",
|
|
14
|
+
"required": false,
|
|
15
|
+
"defaultValue": null,
|
|
16
|
+
"description": "Optional content rendered at the end of the header (buttons, menus, a close control, etc.)"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "children",
|
|
20
|
+
"type": "ReactNode",
|
|
21
|
+
"required": false,
|
|
22
|
+
"defaultValue": null,
|
|
23
|
+
"description": "Card body 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": "headingLevel",
|
|
34
|
+
"type": "1 | 2 | 3 | 4 | 5 | 6",
|
|
35
|
+
"required": false,
|
|
36
|
+
"defaultValue": "3",
|
|
37
|
+
"description": "Which heading tag (`h1`–`h6`) wraps `title`, so the card's title takes its\ncorrect place in the page's heading outline. Defaults to `3`, matching a\ncard nested one level below a page's `h1` title and a `section`'s `h2` —\noverride per-instance when a card sits at a different depth."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "icon",
|
|
41
|
+
"type": "ReactNode",
|
|
42
|
+
"required": false,
|
|
43
|
+
"defaultValue": null,
|
|
44
|
+
"description": "Optional icon rendered before the title in the header"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "onSelect",
|
|
48
|
+
"type": "MouseEventHandler<HTMLButtonElement>",
|
|
49
|
+
"required": false,
|
|
50
|
+
"defaultValue": null,
|
|
51
|
+
"description": "Makes the body a real `<button>` — hover/focus affordance plus a centered,\nstacked content layout — for tile/action-card patterns (a picker grid,\nfor example) instead of a static surface. Distinct from `onClick` (a plain\npass-through to the root element, e.g. for stopping click propagation).\nOmit for a plain, non-interactive card."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "padding",
|
|
55
|
+
"type": "\"none\" | \"sm\" | \"md\" | \"lg\"",
|
|
56
|
+
"required": false,
|
|
57
|
+
"defaultValue": "none",
|
|
58
|
+
"description": "Controls internal padding; defaults to none so consumers opt in. Applies to the body when a header is present."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "scrollableBody",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"required": false,
|
|
64
|
+
"defaultValue": "false",
|
|
65
|
+
"description": "Keeps the header fixed and scrolls only the body when content exceeds the\ncard's height. Only meaningful when the card has a bounded height (a\nmodal or drawer panel, for example)."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "title",
|
|
69
|
+
"type": "ReactNode",
|
|
70
|
+
"required": false,
|
|
71
|
+
"defaultValue": null,
|
|
72
|
+
"description": "Optional header title; omit along with `icon`/`action` to render no header"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"functions": []
|
|
78
|
+
}
|
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@behivetech/atoms.card",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
"card.tsx",
|
|
20
|
+
"card.module.scss",
|
|
21
|
+
"*.example.tsx",
|
|
22
|
+
"docs.json"
|
|
23
|
+
],
|
|
24
|
+
"registry": {
|
|
25
|
+
"status": "stable",
|
|
26
|
+
"tags": []
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^19.2.7",
|
|
30
|
+
"react-dom": "^19.2.7"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "19.2.17",
|
|
34
|
+
"@types/react-dom": "19.2.3",
|
|
35
|
+
"esbuild-sass-plugin": "^3.7.0",
|
|
36
|
+
"eslint": "^9.39.5",
|
|
37
|
+
"postcss-modules": "^9.0.1",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.10",
|
|
41
|
+
"@bht-component-registry/class-names": "0.0.6",
|
|
42
|
+
"@bht-component-registry/env": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/behivetech/bht-galaxy.git",
|
|
47
|
+
"directory": "registry/behivetech/atoms/card"
|
|
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
|
}
|