@podoba/react 0.0.8 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podoba/react",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "podoba React components — React Aria Components + Tailwind primitives + layout, built with uic.",
6
6
  "repository": {
@@ -25,8 +25,8 @@
25
25
  "typecheck": "tsc --build"
26
26
  },
27
27
  "dependencies": {
28
- "@podoba/tokens": "^0.0.8",
29
- "@podoba/tailwind": "^0.0.8",
28
+ "@podoba/tokens": "^0.0.9",
29
+ "@podoba/tailwind": "^0.0.9",
30
30
  "react-aria-components": "1.18.0",
31
31
  "class-variance-authority": "0.7.1",
32
32
  "clsx": "2.1.1",
@@ -0,0 +1,121 @@
1
+ import {
2
+ Button as RACButton,
3
+ Disclosure as RACDisclosure,
4
+ DisclosurePanel as RACDisclosurePanel,
5
+ type DisclosureProps as RACDisclosureProps,
6
+ } from 'react-aria-components'
7
+ import type { ReactNode } from 'react'
8
+
9
+ /**
10
+ * CollapsibleCard — a titled, expandable card (title + description + chevron,
11
+ * expanding to reveal a body).
12
+ *
13
+ * Styled composition over React Aria Components' `Disclosure` (see
14
+ * `./disclosure.tsx` for the unstyled primitive): RAC supplies the WAI-ARIA
15
+ * disclosure wiring (`aria-expanded` / `aria-controls`, keyboard activation,
16
+ * hidden-until-expanded panel) and, on its root element, a `data-expanded`
17
+ * attribute. We make the root a Tailwind `group`, so the chevron rotates via
18
+ * `group-data-[expanded]:rotate-90` — no render-prop plumbing needed.
19
+ *
20
+ * Token mapping (Tailwind + `@podoba/tokens` CSS vars — no SCSS, hard rule #3):
21
+ * - surface → `bg-surface-card` (#f7f6f2), 1px `border-border`, `rounded-lg`
22
+ * (8px) — same skin as `Card` / `StatsCard`.
23
+ * - title → `text-heading4` (1.125rem) `font-semibold text-fg`.
24
+ * - description → `text-body text-fg-muted`.
25
+ *
26
+ * Presentational only (hard rule #1): all copy via props, no API/i18n.
27
+ *
28
+ * Controlled or uncontrolled — pass `isExpanded` + `onExpandedChange` to control,
29
+ * or `defaultExpanded` to seed the uncontrolled state.
30
+ *
31
+ * <CollapsibleCard title="Variables" description="Shared variables applied across this template.">
32
+ * <Select label="Color theme" …/>
33
+ * </CollapsibleCard>
34
+ */
35
+ export type CollapsibleCardProps = {
36
+ /** Bold card title (the always-visible header line). */
37
+ title: ReactNode
38
+ /** Optional muted supporting line under the title. */
39
+ description?: ReactNode
40
+ /** The collapsible body, revealed when expanded. */
41
+ children?: ReactNode
42
+ /** Seed the uncontrolled expanded state. */
43
+ defaultExpanded?: boolean
44
+ /** Controlled expanded state (pair with `onExpandedChange`). */
45
+ isExpanded?: boolean
46
+ /** Fires when the user toggles the card. */
47
+ onExpandedChange?: (isExpanded: boolean) => void
48
+ /** Disable expand/collapse. */
49
+ isDisabled?: boolean
50
+ /** Extra classes merged onto the card root. */
51
+ className?: string
52
+ 'data-testid'?: string
53
+ }
54
+
55
+ const Chevron = () => (
56
+ <svg
57
+ width="16"
58
+ height="16"
59
+ viewBox="0 0 16 16"
60
+ fill="none"
61
+ aria-hidden="true"
62
+ className="mt-0.5 shrink-0 text-fg transition-transform duration-150 group-data-[expanded]:rotate-90"
63
+ >
64
+ <path
65
+ d="M6 4l4 4-4 4"
66
+ stroke="currentColor"
67
+ strokeWidth="1.5"
68
+ strokeLinecap="round"
69
+ strokeLinejoin="round"
70
+ />
71
+ </svg>
72
+ )
73
+
74
+ export function CollapsibleCard({
75
+ title,
76
+ description,
77
+ children,
78
+ defaultExpanded,
79
+ isExpanded,
80
+ onExpandedChange,
81
+ isDisabled,
82
+ className,
83
+ ...rest
84
+ }: CollapsibleCardProps) {
85
+ const disclosureProps: RACDisclosureProps = {
86
+ defaultExpanded,
87
+ isExpanded,
88
+ onExpandedChange,
89
+ isDisabled,
90
+ }
91
+ return (
92
+ <RACDisclosure
93
+ {...disclosureProps}
94
+ data-testid={rest['data-testid']}
95
+ className={[
96
+ 'group overflow-hidden rounded-lg border border-border bg-surface-card',
97
+ className,
98
+ ]
99
+ .filter(Boolean)
100
+ .join(' ')}
101
+ >
102
+ <RACButton
103
+ slot="trigger"
104
+ className={
105
+ 'flex w-full items-center justify-between gap-4 rounded-lg p-6 text-left outline-none ' +
106
+ 'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-60 ' +
107
+ 'data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring data-[focus-visible]:ring-inset'
108
+ }
109
+ >
110
+ <span className="flex flex-col gap-1">
111
+ <span className="text-heading4 font-semibold leading-tight text-fg">{title}</span>
112
+ {description ? (
113
+ <span className="text-body leading-snug text-fg-muted">{description}</span>
114
+ ) : null}
115
+ </span>
116
+ <Chevron />
117
+ </RACButton>
118
+ <RACDisclosurePanel className="px-6 pb-6">{children}</RACDisclosurePanel>
119
+ </RACDisclosure>
120
+ )
121
+ }
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ export * from "./components/context-menu";
35
35
  export * from "./components/tooltip";
36
36
  export * from "./components/toast";
37
37
  export * from "./components/disclosure";
38
+ export * from "./components/collapsible-card";
38
39
  export * from "./components/tabs";
39
40
  export * from "./components/section-tabs";
40
41
  export * from "./components/separator";