@roadlittledawn/docs-design-system-react 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,14 +5,22 @@ interface CollapserProps {
5
5
  /** Optional ID for the title element */
6
6
  id?: string;
7
7
  /**
8
- * Whether the collapser should be open by default
8
+ * Whether the collapser should be open by default (uncontrolled)
9
9
  * @default false
10
10
  */
11
11
  defaultOpen?: boolean;
12
+ /**
13
+ * Controlled open state (used by CollapserGroup)
14
+ */
15
+ open?: boolean;
16
+ /**
17
+ * Callback when toggle is clicked (used by CollapserGroup)
18
+ */
19
+ onToggle?: () => void;
12
20
  /** Content to show/hide when toggling */
13
21
  children: ReactNode;
14
22
  /** Additional CSS classes */
15
23
  className?: string;
16
24
  }
17
- export declare function Collapser({ title, id, defaultOpen, children, className, }: CollapserProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function Collapser({ title, id, defaultOpen, open: controlledOpen, onToggle, children, className, }: CollapserProps): import("react/jsx-runtime").JSX.Element;
18
26
  export {};
@@ -2,19 +2,30 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect } from "react";
3
3
  import { useKeyPress } from "../hooks/useKeyPress";
4
4
  export function Collapser(_a) {
5
- var title = _a.title, id = _a.id, _b = _a.defaultOpen, defaultOpen = _b === void 0 ? false : _b, children = _a.children, _c = _a.className, className = _c === void 0 ? "" : _c;
6
- var _d = useState(defaultOpen), isOpen = _d[0], setIsOpen = _d[1];
5
+ var title = _a.title, id = _a.id, _b = _a.defaultOpen, defaultOpen = _b === void 0 ? false : _b, controlledOpen = _a.open, onToggle = _a.onToggle, children = _a.children, _c = _a.className, className = _c === void 0 ? "" : _c;
6
+ var _d = useState(defaultOpen), uncontrolledOpen = _d[0], setUncontrolledOpen = _d[1];
7
+ var isControlled = controlledOpen !== undefined;
8
+ var isOpen = isControlled ? controlledOpen : uncontrolledOpen;
7
9
  var _e = useState(undefined), height = _e[0], setHeight = _e[1];
8
10
  var contentRef = useRef(null);
9
11
  // Keyboard shortcuts: 's' or 'f' to show, 'h' to hide
10
- useKeyPress(['s', 'f', 'h'], function (e) { return setIsOpen(e.key !== 'h'); });
12
+ useKeyPress(['s', 'f', 'h'], function (e) {
13
+ if (!isControlled) {
14
+ setUncontrolledOpen(e.key !== 'h');
15
+ }
16
+ });
11
17
  useEffect(function () {
12
18
  if (contentRef.current) {
13
19
  setHeight(contentRef.current.scrollHeight);
14
20
  }
15
21
  }, [children]);
16
22
  var toggleOpen = function () {
17
- setIsOpen(!isOpen);
23
+ if (onToggle) {
24
+ onToggle();
25
+ }
26
+ else {
27
+ setUncontrolledOpen(!uncontrolledOpen);
28
+ }
18
29
  };
19
30
  var collapserClasses = ["dds-collapser", className]
20
31
  .filter(Boolean)
@@ -23,6 +23,6 @@ export declare const WithID: Story;
23
23
  */
24
24
  export declare const ComplexContent: Story;
25
25
  /**
26
- * Multiple collapsers in a FAQ-style layout.
26
+ * Multiple collapsers in a FAQ-style layout using CollapserGroup.
27
27
  */
28
28
  export declare const FAQExample: Story;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Collapser } from './Collapser';
3
+ import { CollapserGroup } from './CollapserGroup';
3
4
  /**
4
5
  * The Collapser component creates expandable/collapsible content sections with smooth animations.
5
6
  */
@@ -55,8 +56,8 @@ export var ComplexContent = {
55
56
  },
56
57
  };
57
58
  /**
58
- * Multiple collapsers in a FAQ-style layout.
59
+ * Multiple collapsers in a FAQ-style layout using CollapserGroup.
59
60
  */
60
61
  export var FAQExample = {
61
- render: function () { return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' }, children: [_jsx(Collapser, { title: "What is this documentation system?", children: _jsx("p", { children: "This is a comprehensive documentation design system that provides reusable components and guidelines for creating effective technical documentation." }) }), _jsx(Collapser, { title: "How do I get started?", children: _jsx("p", { children: "Start by installing the component package, then explore the components in Storybook to understand their usage and configuration options." }) }), _jsx(Collapser, { title: "Can I customize the components?", children: _jsx("p", { children: "Yes! All components accept a className prop for custom styling, and you can override the default styles using CSS." }) }), _jsx(Collapser, { title: "Is this accessible?", children: _jsx("p", { children: "Accessibility is a core principle. All components follow WAI-ARIA best practices and are keyboard navigable." }) })] })); },
62
+ render: function () { return (_jsxs(CollapserGroup, { children: [_jsx(Collapser, { title: "What is this documentation system?", children: _jsx("p", { children: "This is a comprehensive documentation design system that provides reusable components and guidelines for creating effective technical documentation." }) }), _jsx(Collapser, { title: "How do I get started?", children: _jsx("p", { children: "Start by installing the component package, then explore the components in Storybook to understand their usage and configuration options." }) }), _jsx(Collapser, { title: "Can I customize the components?", children: _jsx("p", { children: "Yes! All components accept a className prop for custom styling, and you can override the default styles using CSS." }) }), _jsx(Collapser, { title: "Is this accessible?", children: _jsx("p", { children: "Accessibility is a core principle. All components follow WAI-ARIA best practices and are keyboard navigable." }) })] })); },
62
63
  };
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ export interface CollapserGroupProps {
3
+ children: React.ReactNode;
4
+ /** Spacing between collapsers (CSS gap value) */
5
+ spacing?: string;
6
+ /** Allow multiple collapsers to be open simultaneously */
7
+ allowMultiple?: boolean;
8
+ /** Index(es) of collapser(s) that should be open by default */
9
+ defaultOpen?: number | number[];
10
+ /** Callback when collapser open state changes */
11
+ onChange?: (openIndexes: number[]) => void;
12
+ /** Additional CSS classes */
13
+ className?: string;
14
+ }
15
+ export declare const CollapserGroup: React.FC<CollapserGroupProps>;
@@ -0,0 +1,56 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { jsx as _jsx } from "react/jsx-runtime";
22
+ import { useState, Children, cloneElement, isValidElement } from 'react';
23
+ import { Collapser } from './Collapser';
24
+ export var CollapserGroup = function (_a) {
25
+ var children = _a.children, _b = _a.spacing, spacing = _b === void 0 ? '0.5rem' : _b, _c = _a.allowMultiple, allowMultiple = _c === void 0 ? true : _c, defaultOpen = _a.defaultOpen, onChange = _a.onChange, _d = _a.className, className = _d === void 0 ? '' : _d;
26
+ var _e = useState(function () {
27
+ if (defaultOpen === undefined)
28
+ return [];
29
+ return Array.isArray(defaultOpen) ? defaultOpen : [defaultOpen];
30
+ }), openIndexes = _e[0], setOpenIndexes = _e[1];
31
+ var handleToggle = function (index) {
32
+ setOpenIndexes(function (prev) {
33
+ var next;
34
+ if (allowMultiple) {
35
+ next = prev.includes(index)
36
+ ? prev.filter(function (i) { return i !== index; })
37
+ : __spreadArray(__spreadArray([], prev, true), [index], false);
38
+ }
39
+ else {
40
+ next = prev.includes(index) ? [] : [index];
41
+ }
42
+ onChange === null || onChange === void 0 ? void 0 : onChange(next);
43
+ return next;
44
+ });
45
+ };
46
+ return (_jsx("div", { className: "dds-collapser-group ".concat(className).trim(), style: { gap: spacing }, children: Children.map(children, function (child, index) {
47
+ if (!isValidElement(child))
48
+ return child;
49
+ // Only inject props if child is a Collapser component
50
+ if (child.type === Collapser) {
51
+ return cloneElement(child, __assign(__assign({}, child.props), { open: openIndexes.includes(index), onToggle: function () { return handleToggle(index); } }));
52
+ }
53
+ return child;
54
+ }) }));
55
+ };
56
+ CollapserGroup.displayName = 'CollapserGroup';
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { CollapserGroup } from './CollapserGroup';
3
+ /**
4
+ * CollapserGroup manages multiple Collapser components with consistent spacing
5
+ * and optional accordion behavior.
6
+ */
7
+ declare const meta: Meta<typeof CollapserGroup>;
8
+ export default meta;
9
+ type Story = StoryObj<typeof CollapserGroup>;
10
+ /**
11
+ * Basic group with default spacing, allowing multiple collapsers to be open.
12
+ */
13
+ export declare const Basic: Story;
14
+ /**
15
+ * Accordion mode - only one collapser can be open at a time.
16
+ */
17
+ export declare const AccordionMode: Story;
18
+ /**
19
+ * Custom spacing between collapsers.
20
+ */
21
+ export declare const CustomSpacing: Story;
22
+ /**
23
+ * One collapser open by default.
24
+ */
25
+ export declare const DefaultOpen: Story;
26
+ /**
27
+ * Multiple collapsers open by default.
28
+ */
29
+ export declare const MultipleDefaultOpen: Story;
@@ -0,0 +1,77 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { CollapserGroup } from './CollapserGroup';
14
+ import { Collapser } from './Collapser';
15
+ /**
16
+ * CollapserGroup manages multiple Collapser components with consistent spacing
17
+ * and optional accordion behavior.
18
+ */
19
+ var meta = {
20
+ title: 'Components/CollapserGroup',
21
+ component: CollapserGroup,
22
+ tags: ['autodocs'],
23
+ parameters: {
24
+ docs: {
25
+ description: {
26
+ component: "\nCollapserGroup provides a container for multiple Collapser components with built-in spacing and accordion functionality.\n\n## When to Use\n\n- FAQ sections with multiple questions\n- Accordion-style navigation or content sections\n- Any grouped collapsible content that needs consistent spacing\n- When you want only one section open at a time (accordion mode)\n\n## When Not to Use\n\n- For a single Collapser (use Collapser directly)\n- When collapsers need independent styling or spacing\n\n## Accessibility\n\n- Maintains all Collapser accessibility features\n- Keyboard navigation works within the group\n- Accordion mode provides clear single-selection behavior\n ",
27
+ },
28
+ },
29
+ },
30
+ };
31
+ export default meta;
32
+ /**
33
+ * Basic group with default spacing, allowing multiple collapsers to be open.
34
+ */
35
+ export var Basic = {
36
+ args: {
37
+ spacing: '0.5rem',
38
+ allowMultiple: true,
39
+ },
40
+ render: function (args) { return (_jsxs(CollapserGroup, __assign({}, args, { children: [_jsx(Collapser, { title: "What is this documentation system?", children: _jsx("p", { children: "This is a comprehensive documentation design system that provides reusable components and guidelines for creating effective technical documentation." }) }), _jsx(Collapser, { title: "How do I get started?", children: _jsx("p", { children: "Start by installing the component package, then explore the components in Storybook to understand their usage and configuration options." }) }), _jsx(Collapser, { title: "Can I customize the components?", children: _jsx("p", { children: "Yes! All components accept a className prop for custom styling, and you can override the default styles using CSS." }) })] }))); },
41
+ };
42
+ /**
43
+ * Accordion mode - only one collapser can be open at a time.
44
+ */
45
+ export var AccordionMode = {
46
+ args: {
47
+ allowMultiple: false,
48
+ },
49
+ render: function (args) { return (_jsxs(CollapserGroup, __assign({}, args, { children: [_jsxs(Collapser, { title: "Installation", children: [_jsx("p", { children: "Install the package using npm or yarn:" }), _jsx("pre", { children: "npm install @roadlittledawn/docs-design-system" })] }), _jsxs(Collapser, { title: "Configuration", children: [_jsx("p", { children: "Import the CSS and components in your application:" }), _jsx("pre", { children: "import '@roadlittledawn/docs-design-system/dist/styles.css';" })] }), _jsxs(Collapser, { title: "Usage", children: [_jsx("p", { children: "Use the components in your React application:" }), _jsx("pre", { children: '<Button variant="primary">Click me</Button>' })] })] }))); },
50
+ };
51
+ /**
52
+ * Custom spacing between collapsers.
53
+ */
54
+ export var CustomSpacing = {
55
+ args: {
56
+ spacing: '1.5rem',
57
+ },
58
+ render: function (args) { return (_jsxs(CollapserGroup, __assign({}, args, { children: [_jsx(Collapser, { title: "Section 1", children: _jsx("p", { children: "Content with larger spacing between sections." }) }), _jsx(Collapser, { title: "Section 2", children: _jsx("p", { children: "This makes the layout more breathable." }) }), _jsx(Collapser, { title: "Section 3", children: _jsx("p", { children: "Useful for prominent content sections." }) })] }))); },
59
+ };
60
+ /**
61
+ * One collapser open by default.
62
+ */
63
+ export var DefaultOpen = {
64
+ args: {
65
+ defaultOpen: 0,
66
+ },
67
+ render: function (args) { return (_jsxs(CollapserGroup, __assign({}, args, { children: [_jsx(Collapser, { title: "Getting Started", children: _jsx("p", { children: "This section is open by default." }) }), _jsx(Collapser, { title: "Advanced Topics", children: _jsx("p", { children: "This section starts closed." }) }), _jsx(Collapser, { title: "API Reference", children: _jsx("p", { children: "This section also starts closed." }) })] }))); },
68
+ };
69
+ /**
70
+ * Multiple collapsers open by default.
71
+ */
72
+ export var MultipleDefaultOpen = {
73
+ args: {
74
+ defaultOpen: [0, 2],
75
+ },
76
+ render: function (args) { return (_jsxs(CollapserGroup, __assign({}, args, { children: [_jsx(Collapser, { title: "Introduction", children: _jsx("p", { children: "This section is open by default." }) }), _jsx(Collapser, { title: "Installation", children: _jsx("p", { children: "This section starts closed." }) }), _jsx(Collapser, { title: "Quick Start", children: _jsx("p", { children: "This section is also open by default." }) })] }))); },
77
+ };
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ interface TabsProps {
3
+ /** ID of the initially active tab */
4
+ defaultActiveTab?: string;
5
+ /** Controlled active tab ID */
6
+ activeTab?: string;
7
+ /** Callback when tab changes */
8
+ onTabChange?: (id: string) => void;
9
+ /** Tab content */
10
+ children: React.ReactNode;
11
+ /** Additional CSS classes */
12
+ className?: string;
13
+ }
14
+ export declare function Tabs({ defaultActiveTab, activeTab: controlledActiveTab, onTabChange, children, className, }: TabsProps): import("react/jsx-runtime").JSX.Element;
15
+ interface TabListProps {
16
+ /** Tab buttons */
17
+ children: React.ReactNode;
18
+ /** Additional CSS classes */
19
+ className?: string;
20
+ }
21
+ export declare function TabList({ children, className }: TabListProps): import("react/jsx-runtime").JSX.Element;
22
+ interface TabProps {
23
+ /** Unique identifier for this tab */
24
+ id: string;
25
+ /** Tab label */
26
+ children: React.ReactNode;
27
+ /** Additional CSS classes */
28
+ className?: string;
29
+ }
30
+ export declare function Tab({ id, children, className }: TabProps): import("react/jsx-runtime").JSX.Element;
31
+ interface TabPanelProps {
32
+ /** ID matching the corresponding Tab */
33
+ id: string;
34
+ /** Panel content */
35
+ children: React.ReactNode;
36
+ /** Additional CSS classes */
37
+ className?: string;
38
+ }
39
+ export declare function TabPanel({ id, children, className }: TabPanelProps): import("react/jsx-runtime").JSX.Element | null;
40
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, createContext, useContext } from "react";
3
+ var TabsContext = createContext(null);
4
+ export function Tabs(_a) {
5
+ var defaultActiveTab = _a.defaultActiveTab, controlledActiveTab = _a.activeTab, onTabChange = _a.onTabChange, children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b;
6
+ var _c = useState(defaultActiveTab || ""), internalActiveTab = _c[0], setInternalActiveTab = _c[1];
7
+ var isControlled = controlledActiveTab !== undefined;
8
+ var activeTab = isControlled ? controlledActiveTab : internalActiveTab;
9
+ var setActiveTab = function (id) {
10
+ if (!isControlled) {
11
+ setInternalActiveTab(id);
12
+ }
13
+ onTabChange === null || onTabChange === void 0 ? void 0 : onTabChange(id);
14
+ };
15
+ return (_jsx(TabsContext.Provider, { value: { activeTab: activeTab, setActiveTab: setActiveTab }, children: _jsx("div", { className: "dds-tabs ".concat(className).trim(), children: children }) }));
16
+ }
17
+ export function TabList(_a) {
18
+ var children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b;
19
+ return (_jsx("div", { className: "dds-tabs-list ".concat(className).trim(), role: "tablist", children: children }));
20
+ }
21
+ export function Tab(_a) {
22
+ var id = _a.id, children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b;
23
+ var context = useContext(TabsContext);
24
+ if (!context) {
25
+ throw new Error("Tab must be used within Tabs component");
26
+ }
27
+ var activeTab = context.activeTab, setActiveTab = context.setActiveTab;
28
+ var isActive = activeTab === id;
29
+ return (_jsx("button", { className: "dds-tab ".concat(isActive ? "dds-tab-active" : "", " ").concat(className).trim(), onClick: function () { return setActiveTab(id); }, role: "tab", "aria-selected": isActive, "aria-controls": "panel-".concat(id), id: "tab-".concat(id), type: "button", children: children }));
30
+ }
31
+ export function TabPanel(_a) {
32
+ var id = _a.id, children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b;
33
+ var context = useContext(TabsContext);
34
+ if (!context) {
35
+ throw new Error("TabPanel must be used within Tabs component");
36
+ }
37
+ var activeTab = context.activeTab;
38
+ var isActive = activeTab === id;
39
+ if (!isActive)
40
+ return null;
41
+ return (_jsx("div", { className: "dds-tab-panel ".concat(className).trim(), role: "tabpanel", "aria-labelledby": "tab-".concat(id), id: "panel-".concat(id), children: children }));
42
+ }
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Tabs } from "./Tabs";
3
+ /**
4
+ * Tabs organize and segment related content, reducing cognitive load by allowing users
5
+ * to toggle between views without navigating to new pages.
6
+ */
7
+ declare const meta: Meta<typeof Tabs>;
8
+ export default meta;
9
+ type Story = StoryObj<typeof Tabs>;
10
+ /**
11
+ * Basic tabs with three sections of content.
12
+ */
13
+ export declare const Basic: Story;
14
+ /**
15
+ * Tabs showing code examples in different programming languages.
16
+ */
17
+ export declare const CodeExamples: Story;
18
+ /**
19
+ * Tabs with longer content demonstrating scrollable tab list.
20
+ */
21
+ export declare const ManyTabs: Story;
22
+ /**
23
+ * Controlled tabs where the active tab is managed by parent component state.
24
+ */
25
+ export declare const Controlled: Story;
@@ -0,0 +1,89 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import React from "react";
14
+ import { Tabs, TabList, Tab, TabPanel } from "./Tabs";
15
+ import { CodeBlock } from "./CodeBlock";
16
+ /**
17
+ * Tabs organize and segment related content, reducing cognitive load by allowing users
18
+ * to toggle between views without navigating to new pages.
19
+ */
20
+ var meta = {
21
+ title: "Components/Tabs",
22
+ component: Tabs,
23
+ tags: ["autodocs"],
24
+ parameters: {
25
+ docs: {
26
+ description: {
27
+ component: "\nTabs are best used to organize, segment, and reduce cognitive load when presenting large amounts of related but distinct content.\n\n## When to Use\n\n- **Segmenting Related Alternatives**: Show different solutions for the same task (e.g., code examples in Python, Java, JavaScript)\n- **Managing Complex/Long Documents**: Break down extensive documentation into smaller, more manageable sections\n- **Context-Switching**: Allow users to switch between different views (e.g., \"Overview,\" \"Usage,\" \"API Reference\") while staying in the same context\n- **Organizing Workflows**: Separate complex workflows, such as student work from instructions, or setup steps from troubleshooting\n- **Reducing Clutter**: Hide non-essential or less frequently used information, showing only relevant details at one time\n\n## When Not to Use\n\n- When content is short and can be easily scrolled\n- When users need to compare all content simultaneously\n- If the information in the tabs is not related\n- For navigation between pages (use Link component instead)\n\n## Best Practices\n\n- **Default View**: Always set a logical default tab (e.g., the most commonly used language or platform)\n- **Clear Labels**: Ensure tab labels are concise, descriptive, and clearly distinguish between options\n- **Limit Number**: Avoid using too many tabs (more than 5-6 can clutter the interface)\n- **Avoid Nested Tabs**: Too many levels of navigation can confuse users\n\n## Tabs vs Collapsers\n\nUse **tabs** for a small number of distinct, parallel content sections where users rarely need to compare them simultaneously. Use **collapsers** (accordions) for long, vertical content, mobile-first layouts, or FAQs to minimize scrolling and allow users to see all options at once.\n\n## Accessibility\n\n- Uses proper ARIA attributes (`role=\"tab\"`, `role=\"tabpanel\"`, `aria-selected`)\n- Tab buttons are keyboard accessible\n- Active tab is clearly indicated visually and to screen readers\n ",
28
+ },
29
+ },
30
+ },
31
+ };
32
+ export default meta;
33
+ /**
34
+ * Basic tabs with three sections of content.
35
+ */
36
+ export var Basic = {
37
+ args: {
38
+ defaultActiveTab: "overview",
39
+ },
40
+ parameters: {
41
+ docs: {
42
+ source: {
43
+ code: "<Tabs defaultActiveTab=\"overview\">\n <TabList>\n <Tab id=\"overview\">Overview</Tab>\n <Tab id=\"usage\">Usage</Tab>\n <Tab id=\"api\">API Reference</Tab>\n </TabList>\n <TabPanel id=\"overview\">\n <p>\n This is the overview section. It provides a high-level introduction\n to the topic.\n </p>\n </TabPanel>\n <TabPanel id=\"usage\">\n <p>\n This is the usage section. It contains practical examples and\n instructions.\n </p>\n </TabPanel>\n <TabPanel id=\"api\">\n <p>\n This is the API reference section. It documents the technical\n interface.\n </p>\n </TabPanel>\n</Tabs>",
44
+ },
45
+ },
46
+ },
47
+ render: function (args) { return (_jsxs(Tabs, __assign({}, args, { children: [_jsxs(TabList, { children: [_jsx(Tab, { id: "overview", children: "Overview" }), _jsx(Tab, { id: "usage", children: "Usage" }), _jsx(Tab, { id: "api", children: "API Reference" })] }), _jsx(TabPanel, { id: "overview", children: _jsx("p", { children: "This is the overview section. It provides a high-level introduction to the topic." }) }), _jsx(TabPanel, { id: "usage", children: _jsx("p", { children: "This is the usage section. It contains practical examples and instructions." }) }), _jsx(TabPanel, { id: "api", children: _jsx("p", { children: "This is the API reference section. It documents the technical interface." }) })] }))); },
48
+ };
49
+ /**
50
+ * Tabs showing code examples in different programming languages.
51
+ */
52
+ export var CodeExamples = {
53
+ args: {
54
+ defaultActiveTab: "javascript",
55
+ },
56
+ parameters: {
57
+ docs: {
58
+ source: {
59
+ code: "<Tabs defaultActiveTab=\"javascript\">\n <TabList>\n <Tab id=\"javascript\">JavaScript</Tab>\n <Tab id=\"python\">Python</Tab>\n <Tab id=\"java\">Java</Tab>\n </TabList>\n <TabPanel id=\"javascript\">\n <CodeBlock \n language=\"javascript\" \n code={`const greeting = \"Hello, world!\";\nconsole.log(greeting);`} \n />\n </TabPanel>\n <TabPanel id=\"python\">\n <CodeBlock \n language=\"python\" \n code={`greeting = \"Hello, world!\"\nprint(greeting)`} \n />\n </TabPanel>\n <TabPanel id=\"java\">\n <CodeBlock \n language=\"java\" \n code={`String greeting = \"Hello, world!\";\nSystem.out.println(greeting);`} \n />\n </TabPanel>\n</Tabs>",
60
+ },
61
+ },
62
+ },
63
+ render: function (args) { return (_jsxs(Tabs, __assign({}, args, { children: [_jsxs(TabList, { children: [_jsx(Tab, { id: "javascript", children: "JavaScript" }), _jsx(Tab, { id: "python", children: "Python" }), _jsx(Tab, { id: "java", children: "Java" })] }), _jsx(TabPanel, { id: "javascript", children: _jsx(CodeBlock, { language: "javascript", code: "const greeting = \"Hello, world!\";\nconsole.log(greeting);" }) }), _jsx(TabPanel, { id: "python", children: _jsx(CodeBlock, { language: "python", code: "greeting = \"Hello, world!\"\nprint(greeting)" }) }), _jsx(TabPanel, { id: "java", children: _jsx(CodeBlock, { language: "java", code: "String greeting = \"Hello, world!\";\nSystem.out.println(greeting);" }) })] }))); },
64
+ };
65
+ /**
66
+ * Tabs with longer content demonstrating scrollable tab list.
67
+ */
68
+ export var ManyTabs = {
69
+ args: {
70
+ defaultActiveTab: "tab1",
71
+ },
72
+ parameters: {
73
+ docs: {
74
+ source: {
75
+ code: "<Tabs defaultActiveTab=\"tab1\">\n <TabList>\n <Tab id=\"tab1\">Introduction</Tab>\n <Tab id=\"tab2\">Getting Started</Tab>\n <Tab id=\"tab3\">Configuration</Tab>\n <Tab id=\"tab4\">Advanced Usage</Tab>\n <Tab id=\"tab5\">Troubleshooting</Tab>\n <Tab id=\"tab6\">FAQ</Tab>\n </TabList>\n <TabPanel id=\"tab1\">\n <p>Introduction content goes here.</p>\n </TabPanel>\n <TabPanel id=\"tab2\">\n <p>Getting started content goes here.</p>\n </TabPanel>\n <TabPanel id=\"tab3\">\n <p>Configuration content goes here.</p>\n </TabPanel>\n <TabPanel id=\"tab4\">\n <p>Advanced usage content goes here.</p>\n </TabPanel>\n <TabPanel id=\"tab5\">\n <p>Troubleshooting content goes here.</p>\n </TabPanel>\n <TabPanel id=\"tab6\">\n <p>FAQ content goes here.</p>\n </TabPanel>\n</Tabs>",
76
+ },
77
+ },
78
+ },
79
+ render: function (args) { return (_jsxs(Tabs, __assign({}, args, { children: [_jsxs(TabList, { children: [_jsx(Tab, { id: "tab1", children: "Introduction" }), _jsx(Tab, { id: "tab2", children: "Getting Started" }), _jsx(Tab, { id: "tab3", children: "Configuration" }), _jsx(Tab, { id: "tab4", children: "Advanced Usage" }), _jsx(Tab, { id: "tab5", children: "Troubleshooting" }), _jsx(Tab, { id: "tab6", children: "FAQ" })] }), _jsx(TabPanel, { id: "tab1", children: _jsx("p", { children: "Introduction content goes here." }) }), _jsx(TabPanel, { id: "tab2", children: _jsx("p", { children: "Getting started content goes here." }) }), _jsx(TabPanel, { id: "tab3", children: _jsx("p", { children: "Configuration content goes here." }) }), _jsx(TabPanel, { id: "tab4", children: _jsx("p", { children: "Advanced usage content goes here." }) }), _jsx(TabPanel, { id: "tab5", children: _jsx("p", { children: "Troubleshooting content goes here." }) }), _jsx(TabPanel, { id: "tab6", children: _jsx("p", { children: "FAQ content goes here." }) })] }))); },
80
+ };
81
+ /**
82
+ * Controlled tabs where the active tab is managed by parent component state.
83
+ */
84
+ export var Controlled = {
85
+ render: function ControlledExample() {
86
+ var _a = React.useState("tab1"), activeTab = _a[0], setActiveTab = _a[1];
87
+ return (_jsxs("div", { children: [_jsxs("p", { style: { marginBottom: "1rem", color: "var(--dds-tabs-panel-text)" }, children: ["Active tab: ", _jsx("strong", { children: activeTab })] }), _jsxs(Tabs, { activeTab: activeTab, onTabChange: setActiveTab, children: [_jsxs(TabList, { children: [_jsx(Tab, { id: "tab1", children: "Tab 1" }), _jsx(Tab, { id: "tab2", children: "Tab 2" }), _jsx(Tab, { id: "tab3", children: "Tab 3" })] }), _jsx(TabPanel, { id: "tab1", children: _jsx("p", { children: "Content for tab 1" }) }), _jsx(TabPanel, { id: "tab2", children: _jsx("p", { children: "Content for tab 2" }) }), _jsx(TabPanel, { id: "tab3", children: _jsx("p", { children: "Content for tab 3" }) })] })] }));
88
+ },
89
+ };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,9 @@ export * from './components/Callout';
3
3
  export * from './components/Card';
4
4
  export * from './components/CardGrid';
5
5
  export * from './components/Collapser';
6
+ export * from './components/CollapserGroup';
6
7
  export * from './components/CodeBlock';
8
+ export * from './components/Tabs';
7
9
  export * from './components/Typography';
8
10
  export * from './components/Heading';
9
11
  export * from './components/Link';
package/dist/index.js CHANGED
@@ -4,7 +4,9 @@ export * from './components/Callout';
4
4
  export * from './components/Card';
5
5
  export * from './components/CardGrid';
6
6
  export * from './components/Collapser';
7
+ export * from './components/CollapserGroup';
7
8
  export * from './components/CodeBlock';
9
+ export * from './components/Tabs';
8
10
  export * from './components/Typography';
9
11
  export * from './components/Heading';
10
12
  export * from './components/Link';
package/dist/styles.css CHANGED
@@ -209,6 +209,21 @@
209
209
  --dds-collapser-icon-color: #6b7280; /* gray-500 */
210
210
  --dds-collapser-text: #374151; /* gray-700 */
211
211
  --dds-collapser-content-padding: 1rem; /* p-4 */
212
+
213
+ /* Tabs */
214
+ --dds-tabs-margin: 1rem 0;
215
+ --dds-tabs-border: #e5e7eb; /* gray-200 */
216
+ --dds-tabs-tab-padding: 0.75rem 1rem;
217
+ --dds-tabs-tab-bg: transparent;
218
+ --dds-tabs-tab-text: #6b7280; /* gray-500 */
219
+ --dds-tabs-tab-size: 0.875rem;
220
+ --dds-tabs-tab-text-hover: #374151; /* gray-700 */
221
+ --dds-tabs-tab-bg-hover: transparent;
222
+ --dds-tabs-tab-text-active: #111827; /* gray-900 */
223
+ --dds-tabs-tab-bg-active: transparent;
224
+ --dds-tabs-tab-border-active: #3b82f6; /* blue-500 */
225
+ --dds-tabs-panel-padding: 1.25rem 0;
226
+ --dds-tabs-panel-text: #111827; /* gray-900 */
212
227
  }
213
228
  /* ==========================================================================
214
229
  Dark Mode Tokens
@@ -297,6 +312,17 @@
297
312
  --dds-collapser-icon-color: #9ca3af; /* gray-400 */
298
313
  --dds-collapser-text: #e5e7eb; /* gray-200 */
299
314
 
315
+ /* Tabs */
316
+ --dds-tabs-border: #4b5563; /* gray-600 */
317
+ --dds-tabs-tab-bg: transparent;
318
+ --dds-tabs-tab-text: #9ca3af; /* gray-400 */
319
+ --dds-tabs-tab-text-hover: #e5e7eb; /* gray-200 */
320
+ --dds-tabs-tab-bg-hover: transparent;
321
+ --dds-tabs-tab-text-active: #f9fafb; /* gray-50 */
322
+ --dds-tabs-tab-bg-active: transparent;
323
+ --dds-tabs-tab-border-active: #60a5fa; /* blue-400 */
324
+ --dds-tabs-panel-text: #e5e7eb; /* gray-200 */
325
+
300
326
  /* CodeBlock */
301
327
  --dds-code-block-bg: #0a0a0a;
302
328
  --dds-code-block-border: #2e2e2e; /* hsla(0,0%,18%,1) */
@@ -422,6 +448,17 @@
422
448
  --dds-collapser-icon-color: #9ca3af;
423
449
  --dds-collapser-text: #e5e7eb;
424
450
 
451
+ /* Tabs */
452
+ --dds-tabs-border: #4b5563;
453
+ --dds-tabs-tab-bg: transparent;
454
+ --dds-tabs-tab-text: #9ca3af;
455
+ --dds-tabs-tab-text-hover: #e5e7eb;
456
+ --dds-tabs-tab-bg-hover: transparent;
457
+ --dds-tabs-tab-text-active: #f9fafb;
458
+ --dds-tabs-tab-bg-active: transparent;
459
+ --dds-tabs-tab-border-active: #60a5fa;
460
+ --dds-tabs-panel-text: #e5e7eb;
461
+
425
462
  /* CodeBlock */
426
463
  --dds-code-block-bg: #0a0a0a;
427
464
  --dds-code-block-border: #262626;
@@ -1012,6 +1049,13 @@ a.no-text-decoration {
1012
1049
  padding: var(--dds-collapser-content-padding);
1013
1050
  color: var(--dds-collapser-text);
1014
1051
  }
1052
+ .dds-collapser-group {
1053
+ display: flex;
1054
+ flex-direction: column;
1055
+ }
1056
+ .dds-collapser-group > .dds-collapser {
1057
+ margin: 0;
1058
+ }
1015
1059
  .dds-code-block {
1016
1060
  border: 1px solid var(--dds-code-block-border);
1017
1061
  border-radius: var(--dds-code-block-radius);
@@ -1272,6 +1316,50 @@ a.no-text-decoration {
1272
1316
  .dds-code-block-content .token.variable {
1273
1317
  color: var(--dds-prism-regex);
1274
1318
  }
1319
+ .dds-tabs {
1320
+ margin: var(--dds-tabs-margin);
1321
+ }
1322
+ .dds-tabs-list {
1323
+ display: flex;
1324
+ gap: 0;
1325
+ border-bottom: 1px solid var(--dds-tabs-border);
1326
+ overflow-x: auto;
1327
+ overflow-y: hidden;
1328
+ -webkit-overflow-scrolling: touch;
1329
+ scrollbar-width: none;
1330
+ -ms-overflow-style: none;
1331
+ }
1332
+ .dds-tabs-list::-webkit-scrollbar {
1333
+ display: none;
1334
+ }
1335
+ .dds-tab {
1336
+ padding: var(--dds-tabs-tab-padding);
1337
+ background-color: var(--dds-tabs-tab-bg);
1338
+ border: none;
1339
+ border-bottom: 2px solid transparent;
1340
+ color: var(--dds-tabs-tab-text);
1341
+ font-size: var(--dds-tabs-tab-size);
1342
+ font-weight: var(--dds-font-medium);
1343
+ cursor: pointer;
1344
+ white-space: nowrap;
1345
+ transition: var(--dds-transition-colors);
1346
+ }
1347
+ .dds-tab:hover {
1348
+ color: var(--dds-tabs-tab-text-hover);
1349
+ background-color: var(--dds-tabs-tab-bg-hover);
1350
+ }
1351
+ .dds-tab-active {
1352
+ color: var(--dds-tabs-tab-text-active);
1353
+ background-color: var(--dds-tabs-tab-bg-active);
1354
+ border-bottom-color: var(--dds-tabs-tab-border-active);
1355
+ }
1356
+ .dds-tab-active:hover {
1357
+ background-color: var(--dds-tabs-tab-bg-active);
1358
+ }
1359
+ .dds-tab-panel {
1360
+ padding: var(--dds-tabs-panel-padding);
1361
+ color: var(--dds-tabs-panel-text);
1362
+ }
1275
1363
  .dds-typography {
1276
1364
  color: var(--dds-heading-color);
1277
1365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roadlittledawn/docs-design-system-react",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "description": "React components for documentation design system",
6
6
  "repository": {