@tribepad/themis 1.1.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tribepad/themis",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Accessible React component library built on React Aria primitives",
5
5
  "author": "Tribepad <mbasford@tribepad.com>",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@ import {
15
15
  } from 'lucide-react';
16
16
  import { Sidebar } from './SidebarCompound';
17
17
  import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '../Accordion';
18
+ import { Skeleton } from '../Skeleton';
18
19
  import type { SidebarRailVariant } from './Sidebar.types';
19
20
 
20
21
  const meta = {
@@ -54,6 +55,34 @@ function PanelContentForItem({ id }: { id: string }): ReactElement {
54
55
  );
55
56
  }
56
57
 
58
+ /**
59
+ * Fixed-width placeholder shown in place of real children while the
60
+ * panel is mid-collapse / mid-expand. Because the layout's intrinsic
61
+ * width never depends on the content, nothing reflows as the panel's
62
+ * `max-width` animates between 0 and its expanded value.
63
+ */
64
+ function PanelSkeletonForWidth({
65
+ width,
66
+ rows = 4,
67
+ }: {
68
+ width: 'sm' | 'md' | 'lg' | 'xl';
69
+ rows?: number;
70
+ }): ReactElement {
71
+ const widthPx = { sm: 220, md: 280, lg: 340, xl: 420 }[width];
72
+ return (
73
+ <div aria-hidden="true" style={{ width: `${widthPx}px` }} className="shrink-0">
74
+ <div className="flex items-center px-3 py-2 border-b border-[var(--border)] h-[40px]">
75
+ <Skeleton variant="text" className="h-4 w-[100px]" />
76
+ </div>
77
+ <div className="flex flex-col gap-2 p-3">
78
+ {Array.from({ length: rows }).map((_, i) => (
79
+ <Skeleton key={i} variant="text" className="h-3" style={{ width: `${widthPx - 60 - i * 12}px` }} />
80
+ ))}
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
57
86
  const NAV_ITEMS = [
58
87
  { id: 'home', icon: <Home />, label: 'Home' },
59
88
  { id: 'inbox', icon: <Inbox />, label: 'Inbox' },
@@ -74,12 +103,14 @@ function SidebarDemo({
74
103
  showPanel = true,
75
104
  showPopovers = false,
76
105
  showBadges = false,
106
+ withSkeleton = true,
77
107
  railVariant = 'default' as SidebarRailVariant,
78
108
  }: {
79
109
  defaultCollapsed?: boolean;
80
110
  showPanel?: boolean;
81
111
  showPopovers?: boolean;
82
112
  showBadges?: boolean;
113
+ withSkeleton?: boolean;
83
114
  railVariant?: SidebarRailVariant;
84
115
  }): ReactElement {
85
116
  const [activeId, setActiveId] = useState('home');
@@ -108,7 +139,11 @@ function SidebarDemo({
108
139
  ))}
109
140
  </Sidebar.Rail>
110
141
  {showPanel && (
111
- <Sidebar.Panel width="md" aria-label={`${activeId} details`}>
142
+ <Sidebar.Panel
143
+ width="md"
144
+ aria-label={`${activeId} details`}
145
+ skeleton={withSkeleton ? <PanelSkeletonForWidth width="md" /> : undefined}
146
+ >
112
147
  <PanelContentForItem id={activeId} />
113
148
  </Sidebar.Panel>
114
149
  )}
@@ -147,6 +182,16 @@ export const WithPopovers: Story = {
147
182
  render: () => <SidebarDemo showPopovers />,
148
183
  };
149
184
 
185
+ /**
186
+ * Same as `Default` but without the panel `skeleton` prop — content is
187
+ * rendered directly inside the panel as it animates. Sub-nav text wraps
188
+ * and unwraps as the panel resizes; toggle the collapse button to see
189
+ * the difference vs. the skeleton-enabled stories.
190
+ */
191
+ export const WithoutSkeleton: Story = {
192
+ render: () => <SidebarDemo withSkeleton={false} />,
193
+ };
194
+
150
195
  /** Badge counts on rail items (including 99+ cap) */
151
196
  export const WithBadges: Story = {
152
197
  render: () => <SidebarDemo showBadges />,