@blakesteve/roster 2.7.0 β†’ 2.8.1

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,113 +1,172 @@
1
- # Roster πŸ“‹
1
+ # @blakesteve/roster ✦
2
2
 
3
- **Roster** is the atomic UI component library powering the **MegaSquad** ecosystem.
3
+ A production-grade atomic component library built with **React 19**, **TypeScript**, **Tailwind CSS v4**, and **Vite**.
4
4
 
5
- It is built with **React 19**, **TypeScript**, **Tailwind CSS v4**, and **Vite**, offering a set of high-quality, accessible, and theme-aware components.
5
+ Roster ships a curated set of accessible, theme-aware components organized around the Atomic Design methodology β€” atoms, molecules, and organisms β€” each with Storybook documentation and full test coverage.
6
6
 
7
- ## πŸš€ Features
7
+ ## Features
8
8
 
9
- - **Next.js App Router Ready:** All components are automatically bundled with `"use client"` directives for seamless SSR integration without context crashes.
10
- - **Decoupled Theming:** Bring your own brand. Roster ships structural CSS separately from its default design tokens, allowing you to easily override CSS variables or use the default MegaSquad theme.
11
- - **Atomic Design:** Components organized by atoms, molecules, and organisms.
12
- - **TypeScript First:** Fully typed for excellent developer experience (DX).
13
- - **Adaptive Dark Mode:** Fully supports manual, class-based dark mode toggling (`.dark`) independent of OS preferences.
14
- - **Headless UI:** Accessible interactive components powered by `@headlessui/react`.
15
- - **Tree-Shakeable:** Import only what you need.
9
+ - **Atomic Design** β€” components organized as atoms, molecules, and organisms
10
+ - **TypeScript first** β€” fully typed props with exported variant types for maximum DX
11
+ - **Adaptive dark mode** β€” class-based toggling (`.dark`) independent of OS preferences
12
+ - **`"use client"` pre-bundled** β€” all outputs include the directive for seamless Next.js App Router integration
13
+ - **Accessible** β€” interactive components powered by [`@headlessui/react`](https://headlessui.com) and [`@radix-ui`](https://radix-ui.com)
14
+ - **Tree-shakeable** β€” import only what you need
16
15
 
17
- ## πŸ“¦ Installation
16
+ ## Installation
18
17
 
19
18
  ```bash
20
- npm install roster
21
- # or
22
- yarn add roster
19
+ npm install @blakesteve/roster
23
20
  ```
24
21
 
25
- ### Peer Dependencies
22
+ Roster ships pre-compiled CSS β€” you do not need Tailwind installed in your host app to use it.
26
23
 
27
- Roster relies on a few peer dependencies that must be installed in your host application:
24
+ ### Peer dependencies
28
25
 
29
26
  ```json
30
- "peerDependencies": {
27
+ {
31
28
  "react": "^19.0.0",
32
- "react-dom": "^19.0.0",
33
- "tailwindcss": "^4.0.0"
29
+ "react-dom": "^19.0.0"
34
30
  }
35
31
  ```
36
32
 
37
- ## πŸ’» Usage
33
+ ## Setup
38
34
 
39
- ### 1. Import Styles
35
+ Import Roster's CSS once at the root of your application (`layout.tsx`, `main.tsx`, or `App.tsx`):
40
36
 
41
- Roster splits its CSS into two parts to give you ultimate flexibility over your theme. You must import the CSS at the root of your application (e.g., in `layout.tsx`, `main.tsx`, or `App.tsx`).
42
-
43
- ````tsx
44
- // 1. Required: The structural Tailwind utility classes
45
- import "roster/dist/roster.css";
46
-
47
- // 2. Optional: The default MegaSquad color and theme tokens
48
- // (Omit this file if you are defining your own --roster-* variables in your app's global CSS)
49
- import "roster/dist/tokens.css";
37
+ ```tsx
38
+ // Compiled component styles β€” always required
39
+ import "@blakesteve/roster/style.css";
50
40
 
51
- ### 2. Import Components
41
+ // Default design tokens (color palette, spacing, radius)
42
+ // Omit this if you are supplying your own --roster-* CSS variables
43
+ import "@blakesteve/roster/tokens.css";
44
+ ```
52
45
 
53
- Now you can use the components anywhere in your app.
46
+ ## Quick start
54
47
 
55
48
  ```tsx
56
- import { Button, Spinner } from "roster";
49
+ import { Button, Spinner } from "@blakesteve/roster";
57
50
 
58
51
  function App() {
59
52
  return (
60
- <div className="p-4 flex flex-col gap-4">
61
- {/* Primary Button */}
62
- <Button colorScheme="primary" onClick={() => alert("Clicked!")}>
63
- Save Changes
53
+ <div className="flex flex-col gap-4 p-4">
54
+ <Button colorScheme="primary" onClick={() => alert("Saved!")}>
55
+ Save changes
64
56
  </Button>
65
57
 
66
- {/* Destructive Variant */}
67
- <Button colorScheme="destructive" variant="outline">
68
- Delete Account
58
+ <Button colorScheme="error" variant="outline">
59
+ Delete account
69
60
  </Button>
70
61
 
71
- {/* Loading State */}
72
- <Button isLoading>Processing...</Button>
62
+ <Button isLoading>Processing…</Button>
73
63
 
74
- {/* Standalone Spinner */}
75
- <Spinner size="lg" className="text-accent-500" />
64
+ <Spinner size="lg" />
76
65
  </div>
77
66
  );
78
67
  }
79
- ````
80
-
81
- ## 🎨 Development
68
+ ```
82
69
 
83
- We use **Storybook** to develop and document components in isolation.
70
+ ## Component catalog
71
+
72
+ ### Atoms
73
+
74
+ | Component | Description |
75
+ |---|---|
76
+ | `Avatar` | User avatar with image, initials fallback, and optional popover |
77
+ | `Badge` | Status label with semantic color schemes and fill variants |
78
+ | `Button` | Primary interactive element β€” solid, soft, outline, ghost, link variants |
79
+ | `Card` | Bordered surface container |
80
+ | `Checkbox` | Accessible checkbox with label support |
81
+ | `Disclosure` | Show/hide toggle using HeadlessUI |
82
+ | `Input` | Text input with label, error state, and icon slots |
83
+ | `Link` | Styled anchor with variant support |
84
+ | `AvatarStrip` | Stacked avatar row with overflow chip, dismiss button, trailing slot, and label area |
85
+ | `CollapsibleDescription` | Clamped text block with fade and Read more / Show less toggle |
86
+ | `LiquidTabs` | Controlled tab strip with a liquid sliding pill indicator β€” pill and filled variants |
87
+ | `Select` | Dropdown selector |
88
+ | `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend |
89
+ | `Spinner` | Loading indicator |
90
+ | `Switch` | Toggle switch |
91
+ | `Textarea` | Multi-line text input |
92
+ | `Tooltip` | Radix-powered tooltip β€” hover/focus on desktop, tap-to-toggle on mobile |
93
+
94
+ ### Molecules
95
+
96
+ | Component | Description |
97
+ |---|---|
98
+ | `Accordion` | Collapsible content sections (single or multi-expand) |
99
+ | `Breadcrumbs` | Navigation trail |
100
+ | `CallToAction` | Prominent hero-style CTA block |
101
+ | `EmptyState` | Zero-data placeholder with icon and action slot |
102
+ | `ErrorState` | Error display with retry action |
103
+ | `MatchupCard` | Head-to-head comparison card |
104
+
105
+ ### Organisms
106
+
107
+ | Component | Description |
108
+ |---|---|
109
+ | `ActionBar` | Sticky bottom action strip |
110
+ | `Countdown` | Live countdown timer |
111
+ | `DataTable` | Full-featured table with sorting and pagination via TanStack Table |
112
+ | `Dialog` | Modal dialog |
113
+ | `Footer` | Site footer |
114
+ | `Navbar` | Responsive navigation bar with mobile slide-out |
115
+ | `Table` | Static data table |
116
+
117
+ ### Hooks
118
+
119
+ | Hook | Description |
120
+ |---|---|
121
+ | `useCountdown` | Countdown timer logic without the UI |
122
+
123
+ ## Development
124
+
125
+ Storybook is the component playground. Each component has dedicated stories covering all variants, props, and light/dark mode.
84
126
 
85
127
  ```bash
86
- # Install dependencies
87
128
  npm install
88
-
89
- # Start Storybook
90
129
  npm run storybook
130
+ # β†’ http://localhost:6006
91
131
  ```
92
132
 
93
- This will open the component playground at http://localhost:6006.
133
+ ## Testing
94
134
 
95
- ## πŸ› οΈ Building
135
+ ```bash
136
+ # Unit tests (Vitest + jsdom)
137
+ npm run test:run
138
+
139
+ # Unit tests in watch mode
140
+ npm run test
141
+
142
+ # Storybook interaction tests (Vitest + Playwright)
143
+ npx vitest run --project storybook
144
+ ```
96
145
 
97
- To build the library for production (outputs to `dist/`):
146
+ ## Building
98
147
 
99
148
  ```bash
100
149
  npm run build
101
150
  ```
102
151
 
103
- This generates:
152
+ Output in `dist/`:
153
+
154
+ | File | Description |
155
+ |---|---|
156
+ | `roster.es.js` | ES module bundle |
157
+ | `roster.umd.js` | UMD bundle |
158
+ | `roster.css` | Compiled component styles |
159
+ | `tokens.css` | Design token CSS variables |
160
+ | `index.d.ts` | TypeScript definitions |
161
+
162
+ ## Contributing
163
+
164
+ Pull requests are welcome. Please:
104
165
 
105
- - `dist/roster.es.js` (Main library ES Modules)
106
- - `dist/tokens.es.js` (Token injection script)
107
- - `dist/roster.css` (Compiled structural Tailwind styles)
108
- - `dist/tokens.css` (Design token CSS variables)
109
- - `dist/index.d.ts` (Type definitions)
166
+ 1. Open an issue or discussion first for significant changes
167
+ 2. Follow the existing code style (CVA variants, forwardRef, Storybook stories, unit tests)
168
+ 3. Fill out the pull request template checklist before requesting review
110
169
 
111
- ## πŸ“„ License
170
+ ## License
112
171
 
113
- MIT Β© Blake Ball
172
+ MIT Β© [Blake Ball](https://github.com/blakesteve)
@@ -0,0 +1,53 @@
1
+ import { ReactNode } from 'react';
2
+ export type AvatarStripColorScheme = "primary" | "orange" | "teal" | "purple" | "amber" | "success" | "error" | "neutral";
3
+ export interface AvatarStripItem {
4
+ /** Unique identifier. Pass as `excludeKey` to hide this item (e.g. current user). */
5
+ key: string;
6
+ /** Display name β€” used as the avatar title and the source of the single-letter initials. */
7
+ label: string;
8
+ /** Avatar image URL. Falls back to initials when omitted or when the image fails to load. */
9
+ src?: string;
10
+ /** Background color scheme for the initials avatar. Defaults to `"neutral"`. */
11
+ colorScheme?: AvatarStripColorScheme;
12
+ /** When provided, the avatar is wrapped in an `<a>` tag with this href. */
13
+ href?: string;
14
+ }
15
+ export interface AvatarStripProps {
16
+ /** Ordered list of avatar items. */
17
+ items: AvatarStripItem[];
18
+ /** Maximum number of avatars to show before collapsing into a +N chip. Default: `5`. */
19
+ maxDisplay?: number;
20
+ /**
21
+ * The true total count. When provided, the overflow chip shows
22
+ * `totalCount - visible.length` instead of `items.length - maxDisplay`,
23
+ * keeping the number accurate when the fetched array is a subset of the real total.
24
+ */
25
+ totalCount?: number;
26
+ /** Remove one item by key before rendering β€” typically used to hide the current user. */
27
+ excludeKey?: string;
28
+ /** When provided, renders a `βœ•` dismiss button at the leading edge of the strip. */
29
+ onDismiss?: () => void;
30
+ /**
31
+ * Rendered immediately after the overflow chip inside the avatar stack.
32
+ * Use for a ghost-slot CTA, an add-member button, or any custom element
33
+ * that should sit within the `-space-x-2` stack.
34
+ */
35
+ trailingSlot?: ReactNode;
36
+ /** Rendered to the right of the avatar stack. Accepts any React node. */
37
+ label?: ReactNode;
38
+ /**
39
+ * Tailwind class that sets the ring color around each avatar and the overflow chip.
40
+ * Should match the background the strip is rendered on to produce the cutout stack effect.
41
+ * Default: `"ring-white dark:ring-gray-900"`.
42
+ */
43
+ ringClass?: string;
44
+ /** Extra classes applied to the outermost `<div>`. */
45
+ className?: string;
46
+ }
47
+ /**
48
+ * A compact social-proof strip: a stacked row of overlapping avatars with an
49
+ * optional overflow chip, dismiss button, trailing slot, and label area.
50
+ *
51
+ * Returns `null` when there are no visible items and no `trailingSlot`.
52
+ */
53
+ export declare function AvatarStrip({ items, maxDisplay, totalCount, excludeKey, onDismiss, trailingSlot, label, ringClass, className, }: AvatarStripProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ export type CollapsibleDescriptionSize = "sm" | "md" | "lg";
3
+ export interface CollapsibleDescriptionProps {
4
+ /** Content to display. Accepts a string or any React node. */
5
+ children: React.ReactNode;
6
+ /** Controls how many lines are visible before the toggle appears. */
7
+ size?: CollapsibleDescriptionSize;
8
+ /** Label for the expand button. */
9
+ expandLabel?: string;
10
+ /** Label for the collapse button. */
11
+ collapseLabel?: string;
12
+ /** Extra classes applied to the outer wrapper. */
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Clamps content to a fixed height and reveals a toggle when it overflows.
17
+ * Uses CSS `mask-image` for the fade-out effect so it works on any background
18
+ * without needing to know the background color.
19
+ */
20
+ export declare function CollapsibleDescription({ children, size, expandLabel, collapseLabel, className, }: CollapsibleDescriptionProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { ReactNode } from 'react';
2
+ export interface TabItem {
3
+ id: string;
4
+ /** Static label, or a render function that receives whether this tab is active. */
5
+ label: ReactNode | ((isActive: boolean) => ReactNode);
6
+ }
7
+ export interface LiquidTabsProps {
8
+ tabs: TabItem[];
9
+ /** The `id` of the currently active tab. */
10
+ activeTab: string;
11
+ /** Called with the new tab `id` when the user selects a different tab. */
12
+ onChange: (id: string) => void;
13
+ /**
14
+ * `"pill"` (default) β€” floating pill inside a padded container.
15
+ * Width is `w-fit` by default; use `fullWidth` to stretch it.
16
+ * Liquid feel via scaleY squash during the stretch phase.
17
+ *
18
+ * `"filled"` β€” active tab fills its entire cell; container is always `w-full`.
19
+ * Liquid feel via border-radius morphing (oval during stretch) so the
20
+ * full-height pill never gaps at the container edges.
21
+ */
22
+ variant?: "pill" | "filled";
23
+ /** Stretch the `pill` variant container to full width with `flex-1` buttons. */
24
+ fullWidth?: boolean;
25
+ /** Extra classes applied to the outer container. */
26
+ className?: string;
27
+ }
28
+ /**
29
+ * Controlled tab strip with a liquid sliding indicator.
30
+ *
31
+ * The pill animation is driven by direct DOM ref manipulation (no setState)
32
+ * to avoid re-renders during the transition. Two-phase motion:
33
+ * 1. Stretch β€” pill expands to span the gap between old and new tab (130 ms ease-out).
34
+ * 2. Contract β€” pill snaps to the target tab (160 ms ease-in).
35
+ *
36
+ * `getBoundingClientRect` is used to measure tab positions, so the component
37
+ * must be rendered in a document with a real layout engine to animate.
38
+ */
39
+ export declare function LiquidTabs({ tabs, activeTab, onChange, variant, fullWidth, className, }: LiquidTabsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ export type SegmentBarSize = "sm" | "md";
2
+ export interface SegmentBarSegment {
3
+ /** Unique identifier for the segment. */
4
+ key: string;
5
+ /** Display label used in the legend and the bar segment's title tooltip. */
6
+ label: string;
7
+ /** Raw vote / count / quantity. Percentages are computed from the total. */
8
+ value: number;
9
+ /** CSS color for the bar segment and legend dot (hex, rgb, hsl, or CSS variable). */
10
+ color: string;
11
+ }
12
+ export interface SegmentBarProps {
13
+ /** Ordered list of segments rendered left to right. */
14
+ segments: SegmentBarSegment[];
15
+ /** Height of the bar track. */
16
+ size?: SegmentBarSize;
17
+ /** Render a dot-and-label legend below the bar. */
18
+ showLegend?: boolean;
19
+ /** Extra classes applied to the outer wrapper. */
20
+ className?: string;
21
+ }
22
+ /**
23
+ * A proportional horizontal bar divided into colored segments.
24
+ * Percentages are computed automatically from raw `value` fields.
25
+ * Colors are passed as CSS color strings so any palette works without
26
+ * requiring specific Tailwind classes to be present in the consumer's build.
27
+ *
28
+ * Returns `null` when all segment values sum to zero.
29
+ */
30
+ export declare function SegmentBar({ segments, size, showLegend, className, }: SegmentBarProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+ export type TooltipPlacement = "top" | "bottom" | "left" | "right";
3
+ export type TooltipVariant = "dark" | "light";
4
+ export interface TooltipProps {
5
+ /** The tooltip's popup content. Accepts a string or any React node. */
6
+ content: React.ReactNode;
7
+ /** The element that triggers the tooltip on hover, focus, or tap. */
8
+ children: React.ReactNode;
9
+ /** Which side of the trigger the tooltip appears on. Radix auto-flips if there isn't enough room. */
10
+ placement?: TooltipPlacement;
11
+ /** Visual theme for the tooltip bubble. `dark` is the default and works on both light and dark backgrounds. */
12
+ variant?: TooltipVariant;
13
+ /** Milliseconds of hover delay before the tooltip opens (default 300). */
14
+ delayDuration?: number;
15
+ /** Mount the tooltip open β€” useful for visual stories and testing. */
16
+ defaultOpen?: boolean;
17
+ /** Extra classes applied to the tooltip content bubble. */
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Radix-powered tooltip with a caret arrow and entry animation.
22
+ *
23
+ * - **Desktop:** opens on hover (configurable delay) or keyboard focus.
24
+ * - **Mobile:** tap the trigger to toggle open; tap anywhere else to dismiss.
25
+ * This avoids tooltips getting permanently stuck open on touchscreens.
26
+ *
27
+ * The trigger is wrapped in a focusable `<span>` so any element β€” including
28
+ * non-interactive ones β€” can act as a tooltip anchor without breaking
29
+ * Radix's `asChild` forwarding.
30
+ */
31
+ export declare function Tooltip({ content, children, placement, variant, delayDuration, defaultOpen, className, }: TooltipProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -21,6 +21,11 @@ export * from './components/atoms/Switch/Switch';
21
21
  export * from './components/atoms/Switch/switch-variants';
22
22
  export * from './components/atoms/Textarea/Textarea';
23
23
  export * from './components/atoms/Textarea/textarea-variants';
24
+ export * from './components/atoms/AvatarStrip/AvatarStrip';
25
+ export * from './components/atoms/LiquidTabs/LiquidTabs';
26
+ export * from './components/atoms/Tooltip/Tooltip';
27
+ export * from './components/atoms/CollapsibleDescription/CollapsibleDescription';
28
+ export * from './components/atoms/SegmentBar/SegmentBar';
24
29
  export * from './components/molecules/Accordion/Accordion';
25
30
  export * from './components/molecules/Breadcrumbs/Breadcrumbs';
26
31
  export * from './components/molecules/Breadcrumbs/breadcrumb-variants';