@flikk/ui 1.0.0-beta.26 → 1.0.0-beta.27
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/dist/components/ai/PromptInput/PromptInput.js +3 -0
- package/dist/components/ai/PromptInput/VoiceRecorder.js +3 -0
- package/dist/components/core/Button/Button.theme.js +1 -1
- package/dist/components/core/Calendar/Calendar.theme.js +1 -1
- package/dist/components/core/CommandPalette/CommandItem.js +4 -5
- package/dist/components/core/CommandPalette/CommandPalette.js +1 -1
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +11 -13
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +0 -2
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +13 -14
- package/dist/components/core/DotSeparator/DotSeparator.d.ts +3 -0
- package/dist/components/core/DotSeparator/DotSeparator.js +19 -0
- package/dist/components/core/DotSeparator/DotSeparator.theme.d.ts +2 -0
- package/dist/components/core/DotSeparator/DotSeparator.theme.js +10 -0
- package/dist/components/core/DotSeparator/DotSeparator.types.d.ts +25 -0
- package/dist/components/core/DotSeparator/index.d.ts +3 -0
- package/dist/components/core/Dropdown/Dropdown.theme.js +2 -3
- package/dist/components/core/Dropdown/DropdownItem.js +1 -1
- package/dist/components/core/Dropdown/DropdownMenu.js +1 -1
- package/dist/components/core/MenuItem/MenuItem.js +5 -2
- package/dist/components/core/MenuItem/MenuItem.theme.d.ts +6 -0
- package/dist/components/core/MenuItem/MenuItem.theme.js +19 -16
- package/dist/components/core/MenuItem/MenuItem.types.d.ts +2 -0
- package/dist/components/core/NavItem/NavItem.js +20 -4
- package/dist/components/core/NavItem/NavItem.theme.js +13 -2
- package/dist/components/core/Popover/Popover.types.d.ts +6 -2
- package/dist/components/core/Popover/PopoverBody.js +10 -3
- package/dist/components/core/Segmented/Segmented.theme.js +1 -1
- package/dist/components/core/Sidebar/Sidebar.d.ts +2 -0
- package/dist/components/core/Sidebar/Sidebar.js +15 -7
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +92 -4
- package/dist/components/core/Sidebar/SidebarCard.d.ts +25 -0
- package/dist/components/core/Sidebar/SidebarCard.js +68 -0
- package/dist/components/core/Sidebar/SidebarCard.theme.d.ts +8 -0
- package/dist/components/core/Sidebar/SidebarCard.theme.js +18 -0
- package/dist/components/core/Sidebar/SidebarContent.js +5 -1
- package/dist/components/core/Sidebar/SidebarContext.js +1 -1
- package/dist/components/core/Sidebar/SidebarHeader.js +10 -2
- package/dist/components/core/Sidebar/SidebarLogo.js +38 -3
- package/dist/components/core/Sidebar/SidebarSearch.d.ts +22 -0
- package/dist/components/core/Sidebar/SidebarSearch.js +135 -0
- package/dist/components/core/Sidebar/SidebarToggle.js +1 -1
- package/dist/components/core/Sidebar/SidebarUserProfile.js +2 -2
- package/dist/components/core/Sidebar/index.d.ts +3 -1
- package/dist/components/core/Tag/Tag.js +29 -5
- package/dist/components/core/Tag/Tag.theme.js +13 -8
- package/dist/components/core/Tag/Tag.types.d.ts +7 -0
- package/dist/components/core/Tooltip/Tooltip.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -2
- package/dist/components/core/index.d.ts +1 -0
- package/dist/components/core/index.js +4 -0
- package/dist/components/data-display/GanttChart/GanttChart.d.ts +1 -1
- package/dist/components/data-display/GanttChart/GanttChart.js +31 -4
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +19 -17
- package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +19 -0
- package/dist/components/data-display/GanttChart/GanttChart.utils.js +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.d.ts +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +3 -3
- package/dist/components/data-display/GanttChart/GanttDependencyArrows.js +7 -4
- package/dist/components/data-display/GanttChart/GanttMilestone.js +5 -5
- package/dist/components/data-display/GanttChart/GanttTablePanel.js +1 -1
- package/dist/components/data-display/GanttChart/GanttTableTaskRow.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTaskBar.js +37 -16
- package/dist/components/data-display/GanttChart/GanttTimelineGrid.js +2 -1
- package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +35 -11
- package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
- package/dist/components/data-display/GanttChart/hooks/useGanttDrag.js +3 -2
- package/dist/components/data-display/GanttChart/index.d.ts +1 -1
- package/dist/components/data-display/Table/Table.theme.js +3 -5
- package/dist/components/effects/Aurora/Aurora.d.ts +6 -17
- package/dist/components/effects/Aurora/Aurora.js +297 -131
- package/dist/components/effects/Aurora/Aurora.types.d.ts +6 -0
- package/dist/components/forms/Combobox/Combobox.js +1 -1
- package/dist/components/forms/Combobox/Combobox.theme.js +2 -4
- package/dist/components/forms/CronInput/CronInput.js +1 -1
- package/dist/components/forms/CronInput/CronInput.theme.js +14 -23
- package/dist/components/forms/DatePicker/DatePicker.theme.js +11 -18
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +6 -13
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
- package/dist/components/forms/FileUpload/FileUpload.animations.d.ts +29 -0
- package/dist/components/forms/FileUpload/FileUpload.animations.js +19 -0
- package/dist/components/forms/FileUpload/FileUpload.js +87 -48
- package/dist/components/forms/FileUpload/FileUpload.theme.d.ts +2 -10
- package/dist/components/forms/FileUpload/FileUpload.theme.js +16 -30
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +26 -10
- package/dist/components/forms/Input/Input.theme.js +13 -10
- package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
- package/dist/components/forms/Mention/Mention.js +152 -57
- package/dist/components/forms/Mention/Mention.theme.js +16 -23
- package/dist/components/forms/Mention/Mention.types.d.ts +12 -12
- package/dist/components/forms/Mention/Mention.utils.js +32 -4
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +3 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +5 -5
- package/dist/components/forms/Select/Select.js +2 -2
- package/dist/components/forms/Select/Select.theme.js +3 -3
- package/dist/components/forms/TimePicker/TimePickerContent.js +3 -0
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
- package/dist/components/forms/forms.theme.js +21 -16
- package/dist/components/forms/index.js +3 -0
- package/dist/components/layout/AppShell/AppShell.d.ts +10 -0
- package/dist/components/layout/AppShell/AppShell.js +84 -0
- package/dist/components/layout/AppShell/AppShell.theme.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShell.theme.js +42 -0
- package/dist/components/layout/AppShell/AppShell.types.d.ts +127 -0
- package/dist/components/layout/AppShell/AppShellContext.d.ts +14 -0
- package/dist/components/layout/AppShell/AppShellContext.js +19 -0
- package/dist/components/layout/AppShell/AppShellFooter.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellFooter.js +12 -0
- package/dist/components/layout/AppShell/AppShellHeader.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellHeader.js +12 -0
- package/dist/components/layout/AppShell/AppShellMain.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellMain.js +19 -0
- package/dist/components/layout/AppShell/AppShellRightPanel.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellRightPanel.js +16 -0
- package/dist/components/layout/AppShell/AppShellSidebar.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellSidebar.js +16 -0
- package/dist/components/layout/AppShell/AppShellTopbar.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellTopbar.js +12 -0
- package/dist/components/layout/AppShell/index.d.ts +3 -0
- package/dist/components/layout/FormLayout/FormLayout.js +3 -0
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/hooks/useSelectPortal.d.ts +10 -2
- package/dist/hooks/useSelectPortal.js +45 -24
- package/dist/index.js +6 -0
- package/dist/registry.json +10021 -0
- package/dist/styles.css +1 -1
- package/dist/utils/dateUtils.js +11 -1
- package/package.json +4 -2
- package/src/global.scss +29 -8
- package/src/styles/theme.css +51 -12
|
@@ -72,7 +72,7 @@ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
72
72
|
/**
|
|
73
73
|
* Children components
|
|
74
74
|
*/
|
|
75
|
-
children
|
|
75
|
+
children?: ReactNode;
|
|
76
76
|
/**
|
|
77
77
|
* Custom theme overrides
|
|
78
78
|
*/
|
|
@@ -176,11 +176,99 @@ export interface SidebarUserProfileProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
176
176
|
children?: ReactNode;
|
|
177
177
|
}
|
|
178
178
|
export interface SidebarLogoProps extends HTMLAttributes<HTMLDivElement> {
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Logo shown when the sidebar is **expanded**. Accepts any node — an `<img>`,
|
|
181
|
+
* an inline `<svg>`, or composed JSX (e.g. a mark + wordmark). Preferred
|
|
182
|
+
* over the legacy `icon` + `text` API.
|
|
183
|
+
*/
|
|
184
|
+
expanded?: ReactNode;
|
|
185
|
+
/**
|
|
186
|
+
* Logo shown when the sidebar is **collapsed**. Typically a square mark
|
|
187
|
+
* (image, SVG, or JSX). If omitted, falls back to `expanded`, then to the
|
|
188
|
+
* legacy `icon` prop.
|
|
189
|
+
*/
|
|
190
|
+
collapsed?: ReactNode;
|
|
191
|
+
/**
|
|
192
|
+
* Convenience: image URL for the expanded logo. Rendered as an `<img>` with
|
|
193
|
+
* sensible defaults (`h-8 w-auto object-contain`). Use `expandedImageClassName`
|
|
194
|
+
* to override sizing / object-fit. Ignored when `expanded` (ReactNode) is set.
|
|
195
|
+
*/
|
|
196
|
+
expandedSrc?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Convenience: image URL for the collapsed logo. Rendered as an `<img>` in a
|
|
199
|
+
* 36×36 (`size-9`) slot — matching the nav icon track — with `object-cover`
|
|
200
|
+
* by default. Use `collapsedImageClassName` to switch to `object-contain`,
|
|
201
|
+
* adjust border-radius, etc. Ignored when `collapsed` (ReactNode) is set.
|
|
202
|
+
*/
|
|
203
|
+
collapsedSrc?: string;
|
|
204
|
+
/** ClassName forwarded to the auto-rendered `<img>` for `expandedSrc`. */
|
|
205
|
+
expandedImageClassName?: string;
|
|
206
|
+
/** ClassName forwarded to the auto-rendered `<img>` for `collapsedSrc`. */
|
|
207
|
+
collapsedImageClassName?: string;
|
|
208
|
+
/** Alt text for any auto-rendered `<img>` (both `expandedSrc` and `collapsedSrc`). */
|
|
209
|
+
alt?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Legacy: always-visible icon/mark. Shown in both states; the `text` prop
|
|
212
|
+
* fades in alongside it when expanded. Use `expanded` / `collapsed` instead
|
|
213
|
+
* for full control (especially for image/SVG logos that differ per state).
|
|
214
|
+
*/
|
|
215
|
+
icon?: ReactNode;
|
|
216
|
+
/**
|
|
217
|
+
* Legacy: text rendered to the right of `icon` when expanded — animates
|
|
218
|
+
* in/out on collapse. Use `expanded` instead for full control.
|
|
219
|
+
*/
|
|
182
220
|
text?: ReactNode;
|
|
183
221
|
}
|
|
222
|
+
export interface SidebarSearchProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
|
|
223
|
+
/** Controlled value */
|
|
224
|
+
value?: string;
|
|
225
|
+
/** Uncontrolled initial value */
|
|
226
|
+
defaultValue?: string;
|
|
227
|
+
/**
|
|
228
|
+
* Fires on every keystroke. Consumers wire this to render results wherever
|
|
229
|
+
* they like (popover, command palette, side panel). The Sidebar tree itself
|
|
230
|
+
* is NOT filtered.
|
|
231
|
+
*/
|
|
232
|
+
onChange?: (value: string) => void;
|
|
233
|
+
/** @default "Search…" */
|
|
234
|
+
placeholder?: string;
|
|
235
|
+
/** @default 'sm' */
|
|
236
|
+
size?: "sm" | "md";
|
|
237
|
+
/** Override the leading icon (defaults to MagnifyingGlassIcon). */
|
|
238
|
+
iconStart?: ReactNode;
|
|
239
|
+
/** Tooltip + aria-label for the collapsed-mode icon button. @default "Search" */
|
|
240
|
+
collapsedLabel?: string;
|
|
241
|
+
/** Hide the collapsed-mode icon button entirely. @default false */
|
|
242
|
+
hideWhenCollapsed?: boolean;
|
|
243
|
+
/** Forwarded to the underlying Input's group wrapper — use for background
|
|
244
|
+
* overrides on custom sidebar surfaces (e.g. `bg-transparent`). */
|
|
245
|
+
inputGroupClassName?: string;
|
|
246
|
+
}
|
|
247
|
+
export interface SidebarCardTheme {
|
|
248
|
+
/** Outer wrapper applied in both styled and unstyled modes — must carry
|
|
249
|
+
* `overflow-hidden` so the height collapse animation clips children. */
|
|
250
|
+
wrapperStyle: string;
|
|
251
|
+
/** Default container chrome (rounded / surface bg / border / padding).
|
|
252
|
+
* Skipped when the consumer sets `unstyled`. */
|
|
253
|
+
baseStyle: string;
|
|
254
|
+
}
|
|
255
|
+
export interface SidebarCardThemeOverrides {
|
|
256
|
+
wrapperStyle?: string;
|
|
257
|
+
baseStyle?: string;
|
|
258
|
+
}
|
|
259
|
+
export interface SidebarCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
260
|
+
children: ReactNode;
|
|
261
|
+
className?: string;
|
|
262
|
+
/**
|
|
263
|
+
* Drop the default container styling (rounded / bg / border / padding)
|
|
264
|
+
* so consumers can put an edge-to-edge image, a custom panel, or any
|
|
265
|
+
* non-card-shaped content inside without fighting the default chrome.
|
|
266
|
+
* The disappear-on-collapse animation is unchanged.
|
|
267
|
+
* @default false
|
|
268
|
+
*/
|
|
269
|
+
unstyled?: boolean;
|
|
270
|
+
theme?: SidebarCardThemeOverrides;
|
|
271
|
+
}
|
|
184
272
|
export interface SidebarProviderProps {
|
|
185
273
|
children: React.ReactNode;
|
|
186
274
|
defaultCollapsed?: boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { SidebarCardProps } from "./Sidebar.types";
|
|
3
|
+
/**
|
|
4
|
+
* A collapse-aware container for arbitrary sidebar content — promo CTAs,
|
|
5
|
+
* storage meters, tip-of-the-day, banner images, anything. Renders its
|
|
6
|
+
* children when the sidebar is expanded and **disappears entirely** when
|
|
7
|
+
* the sidebar collapses (smooth height + opacity exit via AnimatePresence).
|
|
8
|
+
*
|
|
9
|
+
* Composition is open: drop in raw text, an `<img>`, a `<Button>`,
|
|
10
|
+
* `<Progress>`, or any custom JSX. There are intentionally no sub-parts
|
|
11
|
+
* (`Sidebar.Card.Header`/`.Body`/…) because the whole card vanishes
|
|
12
|
+
* together — granular slots add ceremony with no payoff.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <Sidebar.Card>
|
|
16
|
+
* <p className="text-sm">Upgrade for unlimited projects.</p>
|
|
17
|
+
* <Button size="sm" className="mt-2 w-full">Upgrade</Button>
|
|
18
|
+
* </Sidebar.Card>
|
|
19
|
+
*
|
|
20
|
+
* @example Edge-to-edge image (no default container chrome):
|
|
21
|
+
* <Sidebar.Card unstyled>
|
|
22
|
+
* <img src="/promo.jpg" alt="" className="w-full rounded-[var(--card-radius)]" />
|
|
23
|
+
* </Sidebar.Card>
|
|
24
|
+
*/
|
|
25
|
+
export declare const SidebarCard: React.ForwardRefExoticComponent<SidebarCardProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React__default, { useMemo, useState, useEffect } from 'react';
|
|
3
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { sidebarCardTheme } from './SidebarCard.theme.js';
|
|
6
|
+
import { useSidebarCollapsed } from './SidebarContext.js';
|
|
7
|
+
|
|
8
|
+
// Sidebar.Card mount is sequenced relative to the sidebar's collapse/expand
|
|
9
|
+
// animation. On collapse → the card exits immediately (popLayout + slide/fade
|
|
10
|
+
// in parallel with the rail narrowing). On expand → the card mount is delayed
|
|
11
|
+
// until the sidebar's spring has settled, so the card lays out at its FINAL
|
|
12
|
+
// expanded width on first render and the text never reflows mid-animation.
|
|
13
|
+
// 260ms ≈ the spring's 99% settle time (stiffness 320, damping 34) — see
|
|
14
|
+
// Sidebar.tsx's SIDEBAR_TRANSITION comment for the underlying math.
|
|
15
|
+
const MOUNT_DELAY_MS = 260;
|
|
16
|
+
/**
|
|
17
|
+
* A collapse-aware container for arbitrary sidebar content — promo CTAs,
|
|
18
|
+
* storage meters, tip-of-the-day, banner images, anything. Renders its
|
|
19
|
+
* children when the sidebar is expanded and **disappears entirely** when
|
|
20
|
+
* the sidebar collapses (smooth height + opacity exit via AnimatePresence).
|
|
21
|
+
*
|
|
22
|
+
* Composition is open: drop in raw text, an `<img>`, a `<Button>`,
|
|
23
|
+
* `<Progress>`, or any custom JSX. There are intentionally no sub-parts
|
|
24
|
+
* (`Sidebar.Card.Header`/`.Body`/…) because the whole card vanishes
|
|
25
|
+
* together — granular slots add ceremony with no payoff.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <Sidebar.Card>
|
|
29
|
+
* <p className="text-sm">Upgrade for unlimited projects.</p>
|
|
30
|
+
* <Button size="sm" className="mt-2 w-full">Upgrade</Button>
|
|
31
|
+
* </Sidebar.Card>
|
|
32
|
+
*
|
|
33
|
+
* @example Edge-to-edge image (no default container chrome):
|
|
34
|
+
* <Sidebar.Card unstyled>
|
|
35
|
+
* <img src="/promo.jpg" alt="" className="w-full rounded-[var(--card-radius)]" />
|
|
36
|
+
* </Sidebar.Card>
|
|
37
|
+
*/
|
|
38
|
+
const SidebarCard = React__default.forwardRef(({ children, className, unstyled = false, theme: customTheme, ...props }, ref) => {
|
|
39
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
40
|
+
const theme = useMemo(() => ({ ...sidebarCardTheme, ...customTheme }), [customTheme]);
|
|
41
|
+
// Card is visible when expanded. Hide immediately on collapse (so the
|
|
42
|
+
// AnimatePresence exit + popLayout reflow happens in parallel with the
|
|
43
|
+
// rail narrowing). On expand, wait for the sidebar spring to settle
|
|
44
|
+
// before mounting — see `MOUNT_DELAY_MS` rationale.
|
|
45
|
+
const [shouldShow, setShouldShow] = useState(!isCollapsedView);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (isCollapsedView) {
|
|
48
|
+
setShouldShow(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const id = setTimeout(() => setShouldShow(true), MOUNT_DELAY_MS);
|
|
52
|
+
return () => clearTimeout(id);
|
|
53
|
+
}, [isCollapsedView]);
|
|
54
|
+
// Strip drag/animation DOM handlers that collide with motion's prop types
|
|
55
|
+
// (same pattern the other Sidebar pieces use, e.g. Sidebar.tsx:54-61).
|
|
56
|
+
const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, ...motionSafeProps } = props;
|
|
57
|
+
return (
|
|
58
|
+
// `mode="popLayout"` removes the exiting card from the layout flow
|
|
59
|
+
// immediately so siblings (footer, nav rows below) snap into their new
|
|
60
|
+
// positions without waiting on the card's exit animation.
|
|
61
|
+
jsx(AnimatePresence, { initial: false, mode: "popLayout", children: shouldShow && (jsx(motion.div, { ref: ref, initial: { opacity: 0, x: -16 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -16 }, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, className: cn(theme.wrapperStyle,
|
|
62
|
+
// Default card chrome (rounded / bg / border / p-3) unless the
|
|
63
|
+
// consumer opts out. className wins per shadcn rules.
|
|
64
|
+
!unstyled && theme.baseStyle, className), ...motionSafeProps, children: children }, "sidebar-card")) }));
|
|
65
|
+
});
|
|
66
|
+
SidebarCard.displayName = "SidebarCard";
|
|
67
|
+
|
|
68
|
+
export { SidebarCard };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SidebarCardTheme } from "./Sidebar.types";
|
|
2
|
+
/**
|
|
3
|
+
* Default theme for `<Sidebar.Card>`. Small surface — the component itself
|
|
4
|
+
* only renders a single styled wrapper that disappears on collapse, so the
|
|
5
|
+
* theme just holds the default outer chrome. Set `unstyled` on the card to
|
|
6
|
+
* skip it entirely (e.g. for full-bleed images).
|
|
7
|
+
*/
|
|
8
|
+
export declare const sidebarCardTheme: SidebarCardTheme;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default theme for `<Sidebar.Card>`. Small surface — the component itself
|
|
3
|
+
* only renders a single styled wrapper that disappears on collapse, so the
|
|
4
|
+
* theme just holds the default outer chrome. Set `unstyled` on the card to
|
|
5
|
+
* skip it entirely (e.g. for full-bleed images).
|
|
6
|
+
*/
|
|
7
|
+
const sidebarCardTheme = {
|
|
8
|
+
// Card chrome — surface bg, hairline border, sidebar-tuned padding (p-3 is
|
|
9
|
+
// tighter than core/Card's p-6 because the rail is narrow). `overflow-hidden`
|
|
10
|
+
// keeps any children clipped to the rounded corners.
|
|
11
|
+
baseStyle: "rounded-[var(--card-radius)] bg-[var(--color-surface)] border border-[var(--color-border)] p-3 overflow-hidden",
|
|
12
|
+
// Wrapper applied in both styled + unstyled modes. Empty by default —
|
|
13
|
+
// the slide/fade animation lives on motion.div itself, not on this layer,
|
|
14
|
+
// so no overflow trickery needed here.
|
|
15
|
+
wrapperStyle: "",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { sidebarCardTheme };
|
|
@@ -8,7 +8,11 @@ import '../ScrollArea/smooth/SmoothScrollEngine.js';
|
|
|
8
8
|
const SidebarContent = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
9
9
|
const { isCollapsedView } = useSidebarCollapsed();
|
|
10
10
|
const density = useSidebarDensity();
|
|
11
|
-
return (jsx(ScrollArea, { ref: ref, type: "none", size: "thin", className: "flex-1 min-h-0", ...props, children: jsx("div", { className: cn("py-3 transition-[padding] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", density === "compact" && "py-2",
|
|
11
|
+
return (jsx(ScrollArea, { ref: ref, type: "none", size: "thin", className: "flex-1 min-h-0", ...props, children: jsx("div", { className: cn("py-3 transition-[padding] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", density === "compact" && "py-2",
|
|
12
|
+
// Collapsed px-3 (12) on a 60px rail leaves a 36px interior — every
|
|
13
|
+
// child row (NavItem, Sidebar.Search) is exactly 36px wide, matching
|
|
14
|
+
// the 36×36 hit-area target.
|
|
15
|
+
isCollapsedView ? "px-3" : "px-2.5", className), children: children }) }));
|
|
12
16
|
});
|
|
13
17
|
SidebarContent.displayName = "SidebarContent";
|
|
14
18
|
|
|
@@ -8,12 +8,20 @@ import { Button } from '../Button/Button.js';
|
|
|
8
8
|
|
|
9
9
|
const SidebarHeader = React__default.forwardRef(({ logo, showToggle = true, customToggle, children, className, ...props }, ref) => {
|
|
10
10
|
const sidebarContext = useSidebarContext();
|
|
11
|
-
const { isMobile, isMobileOpen
|
|
11
|
+
const { isMobile, isMobileOpen } = useSidebarCollapsed();
|
|
12
12
|
// If children are provided, use them instead of default layout
|
|
13
13
|
if (children) {
|
|
14
14
|
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-b border-[var(--color-border)]", className), ...props, children: children }));
|
|
15
15
|
}
|
|
16
|
-
return (jsxs("div", { ref: ref, className: cn("flex items-center flex-shrink-0 h-14 border-b border-[var(--color-border)] overflow-visible relative transition-[padding,background-color,border-color] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]",
|
|
16
|
+
return (jsxs("div", { ref: ref, className: cn("flex items-center flex-shrink-0 h-14 border-b border-[var(--color-border)] overflow-visible relative transition-[padding,background-color,border-color] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]",
|
|
17
|
+
// Padding mirrors Sidebar.Content so the header logo sits on the
|
|
18
|
+
// same horizontal track as the NavItem rows below it. px-3 (12px)
|
|
19
|
+
// on the 60px collapsed rail leaves a 36px interior — a 32px logo
|
|
20
|
+
// (size-8) lands with 2px slack on each side; a 36px logo (size-9)
|
|
21
|
+
// fills the interior exactly. Left-pinned in both states (no
|
|
22
|
+
// `justify-*` toggle) so nothing jumps mid-animation. Same value
|
|
23
|
+
// for collapsed + expanded → no padding transition needed.
|
|
24
|
+
"px-3", className), ...props, children: [jsx("div", { className: "flex items-center min-w-0 flex-1", children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "ghost", size: "sm", className: "size-7 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-4" }) })), !isMobile && showToggle && (customToggle || jsx(SidebarToggle, {}))] }));
|
|
17
25
|
});
|
|
18
26
|
SidebarHeader.displayName = "SidebarHeader";
|
|
19
27
|
|
|
@@ -1,12 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { useSidebarCollapsed } from './SidebarContext.js';
|
|
6
6
|
|
|
7
|
-
const SidebarLogo = React__default.forwardRef(({ icon, text, className, ...props }, ref) => {
|
|
7
|
+
const SidebarLogo = React__default.forwardRef(({ expanded, collapsed, expandedSrc, collapsedSrc, expandedImageClassName, collapsedImageClassName, alt = "", icon, text, className, ...props }, ref) => {
|
|
8
|
+
var _a, _b;
|
|
8
9
|
const { isCollapsedView } = useSidebarCollapsed();
|
|
9
|
-
|
|
10
|
+
// Auto-render <img> for the src convenience API. ReactNode props still win
|
|
11
|
+
// (full control); src is the easy path for plain image/SVG assets.
|
|
12
|
+
//
|
|
13
|
+
// Defaults:
|
|
14
|
+
// - Collapsed: size-9 (36px) square, object-cover — matches the nav icon
|
|
15
|
+
// track so the collapsed logo aligns visually with NavItem rows below.
|
|
16
|
+
// - Expanded: h-8 w-auto object-contain — typical wordmark sizing.
|
|
17
|
+
// Override via `collapsedImageClassName` / `expandedImageClassName`
|
|
18
|
+
// (tailwind-merge resolves conflicts in favor of the user's classes).
|
|
19
|
+
const expandedNode = expanded !== null && expanded !== void 0 ? expanded : (expandedSrc ? (jsx("img", { src: expandedSrc, alt: alt, className: cn("h-8 w-auto object-contain", expandedImageClassName) })) : undefined);
|
|
20
|
+
const collapsedNode = collapsed !== null && collapsed !== void 0 ? collapsed : (collapsedSrc ? (jsx("img", { src: collapsedSrc, alt: alt, className: cn("size-9 object-cover", collapsedImageClassName) })) : undefined);
|
|
21
|
+
// New API takes precedence: if the user passed `expanded`/`collapsed`
|
|
22
|
+
// (ReactNode) OR their `*Src` shortcuts, render those (cross-faded).
|
|
23
|
+
// Otherwise fall back to the legacy icon-plus-animated-text rendering so
|
|
24
|
+
// existing consumers keep working.
|
|
25
|
+
const hasNewApi = expandedNode !== undefined || collapsedNode !== undefined;
|
|
26
|
+
if (hasNewApi) {
|
|
27
|
+
// Resolve the logo for the current state, falling all the way back to
|
|
28
|
+
// the legacy `icon` so a partial new-API usage still has something to
|
|
29
|
+
// show. Per-state fallback: collapsed → expanded → icon, and vice versa.
|
|
30
|
+
const currentLogo = isCollapsedView
|
|
31
|
+
? (_a = collapsedNode !== null && collapsedNode !== void 0 ? collapsedNode : expandedNode) !== null && _a !== void 0 ? _a : icon
|
|
32
|
+
: (_b = expandedNode !== null && expandedNode !== void 0 ? expandedNode : collapsedNode) !== null && _b !== void 0 ? _b : icon;
|
|
33
|
+
return (jsx("div", { ref: ref,
|
|
34
|
+
// No `overflow-hidden`: would clip wide logos in collapsed mode.
|
|
35
|
+
// Sidebar itself is `overflow-visible`.
|
|
36
|
+
className: cn("flex items-center", className), ...props, children: jsx(AnimatePresence, { mode: "wait", initial: false, children: jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] }, className: "flex items-center", children: currentLogo }, isCollapsedView ? "collapsed" : "expanded") }) }));
|
|
37
|
+
}
|
|
38
|
+
// Legacy API: icon + animated text.
|
|
39
|
+
return (jsxs("div", { ref: ref,
|
|
40
|
+
// No `overflow-hidden` here — that would clip the icon when the
|
|
41
|
+
// sidebar is collapsed and horizontal space is tight. The text
|
|
42
|
+
// collapse animation owns its own `overflow-hidden` on the motion.div
|
|
43
|
+
// below, which is the only place it's actually needed.
|
|
44
|
+
className: cn("flex items-center gap-2", className), ...props, children: [jsx("div", { className: "flex-shrink-0", children: icon }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && text && (jsx(motion.div, { className: "overflow-hidden whitespace-nowrap", initial: { opacity: 0, x: -8 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -8 }, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] }, children: typeof text === "string" ? (jsx("span", { className: "text-base font-semibold tracking-tight text-[var(--color-text-primary)]", children: text })) : (text) }, "logo-text")) })] }));
|
|
10
45
|
});
|
|
11
46
|
SidebarLogo.displayName = "SidebarLogo";
|
|
12
47
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { SidebarSearchProps } from "./Sidebar.types";
|
|
3
|
+
/**
|
|
4
|
+
* Optional search field for the Sidebar. Emits the typed query via `onChange`
|
|
5
|
+
* (or via the controlled `value` prop) — does NOT filter the Sidebar tree itself.
|
|
6
|
+
* Consumers wire `onChange` to whatever surfaces results (popover, command palette,
|
|
7
|
+
* side panel, route, …) so the Sidebar stays visually clean.
|
|
8
|
+
*
|
|
9
|
+
* The collapse animation mirrors NavItem (single rendered structure across both
|
|
10
|
+
* states, icon always present in a fixed flex slot, the expanded content
|
|
11
|
+
* animates in/out via AnimatePresence). The field's border + surface fill fade
|
|
12
|
+
* out via a CSS transition so the collapsed rail just shows the icon — same
|
|
13
|
+
* look as the NavItem rows above it. Click the icon to expand the sidebar and
|
|
14
|
+
* focus the input. Opt out of the collapsed affordance with `hideWhenCollapsed`.
|
|
15
|
+
*
|
|
16
|
+
* Implementation note: this uses a small inline field instead of the shared
|
|
17
|
+
* `<Input>` so the icon + animated input can be siblings under one wrapper
|
|
18
|
+
* (Input's internal layout wouldn't permit the NavItem-style animation). The
|
|
19
|
+
* border/focus styling matches `formsBaseTheme.inputGroupBaseStyle` (inset
|
|
20
|
+
* box-shadow + ring) so it reads as a normal form control when expanded.
|
|
21
|
+
*/
|
|
22
|
+
export declare const SidebarSearch: React.ForwardRefExoticComponent<SidebarSearchProps & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React__default, { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
4
|
+
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
|
5
|
+
import { cn } from '../../../utils/cn.js';
|
|
6
|
+
import { Tooltip } from '../Tooltip/Tooltip.js';
|
|
7
|
+
import '../Tooltip/Tooltip.animations.js';
|
|
8
|
+
import { useSidebarContext, useSidebarCollapsed } from './SidebarContext.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Optional search field for the Sidebar. Emits the typed query via `onChange`
|
|
12
|
+
* (or via the controlled `value` prop) — does NOT filter the Sidebar tree itself.
|
|
13
|
+
* Consumers wire `onChange` to whatever surfaces results (popover, command palette,
|
|
14
|
+
* side panel, route, …) so the Sidebar stays visually clean.
|
|
15
|
+
*
|
|
16
|
+
* The collapse animation mirrors NavItem (single rendered structure across both
|
|
17
|
+
* states, icon always present in a fixed flex slot, the expanded content
|
|
18
|
+
* animates in/out via AnimatePresence). The field's border + surface fill fade
|
|
19
|
+
* out via a CSS transition so the collapsed rail just shows the icon — same
|
|
20
|
+
* look as the NavItem rows above it. Click the icon to expand the sidebar and
|
|
21
|
+
* focus the input. Opt out of the collapsed affordance with `hideWhenCollapsed`.
|
|
22
|
+
*
|
|
23
|
+
* Implementation note: this uses a small inline field instead of the shared
|
|
24
|
+
* `<Input>` so the icon + animated input can be siblings under one wrapper
|
|
25
|
+
* (Input's internal layout wouldn't permit the NavItem-style animation). The
|
|
26
|
+
* border/focus styling matches `formsBaseTheme.inputGroupBaseStyle` (inset
|
|
27
|
+
* box-shadow + ring) so it reads as a normal form control when expanded.
|
|
28
|
+
*/
|
|
29
|
+
const SidebarSearch = React__default.forwardRef(({ value: controlledValue, defaultValue = "", onChange, placeholder = "Search…", size = "md", iconStart, collapsedLabel = "Search", hideWhenCollapsed = false, className, inputGroupClassName, ...props }, ref) => {
|
|
30
|
+
const context = useSidebarContext();
|
|
31
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
32
|
+
const isControlled = controlledValue !== undefined;
|
|
33
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
34
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
35
|
+
// Compose the user's forwarded ref with our local ref so we can focus
|
|
36
|
+
// the input after the rail expands.
|
|
37
|
+
const inputRef = useRef(null);
|
|
38
|
+
const setInputRef = (el) => {
|
|
39
|
+
inputRef.current = el;
|
|
40
|
+
if (typeof ref === "function")
|
|
41
|
+
ref(el);
|
|
42
|
+
else if (ref)
|
|
43
|
+
ref.current = el;
|
|
44
|
+
};
|
|
45
|
+
// Focus the input on the frame *after* collapse→expand transitions, but only
|
|
46
|
+
// when the user triggered it from the icon click (not e.g. via Sidebar.Toggle,
|
|
47
|
+
// where stealing focus would be surprising).
|
|
48
|
+
const pendingFocusRef = useRef(false);
|
|
49
|
+
const wasCollapsedRef = useRef(isCollapsedView);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (wasCollapsedRef.current && !isCollapsedView && pendingFocusRef.current) {
|
|
52
|
+
const id = requestAnimationFrame(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
53
|
+
pendingFocusRef.current = false;
|
|
54
|
+
wasCollapsedRef.current = isCollapsedView;
|
|
55
|
+
return () => cancelAnimationFrame(id);
|
|
56
|
+
}
|
|
57
|
+
wasCollapsedRef.current = isCollapsedView;
|
|
58
|
+
}, [isCollapsedView]);
|
|
59
|
+
const handleChange = (e) => {
|
|
60
|
+
const next = e.target.value;
|
|
61
|
+
if (!isControlled)
|
|
62
|
+
setInternalValue(next);
|
|
63
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(next);
|
|
64
|
+
};
|
|
65
|
+
const handleExpand = () => {
|
|
66
|
+
if (!isCollapsedView)
|
|
67
|
+
return;
|
|
68
|
+
pendingFocusRef.current = true;
|
|
69
|
+
context === null || context === void 0 ? void 0 : context.setCollapsed(false);
|
|
70
|
+
};
|
|
71
|
+
const handleKeyDown = (e) => {
|
|
72
|
+
if (!isCollapsedView)
|
|
73
|
+
return;
|
|
74
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
handleExpand();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
if (isCollapsedView && hideWhenCollapsed)
|
|
80
|
+
return null;
|
|
81
|
+
// Form-field shell — same inset-shadow border + focus ring as the shared
|
|
82
|
+
// forms.theme.ts (inputGroupBaseStyle), kept in sync intentionally. CSS
|
|
83
|
+
// transitions on background + box-shadow let the field fade out cleanly
|
|
84
|
+
// as the rail collapses; AnimatePresence handles the input itself.
|
|
85
|
+
const shellExpanded = "bg-[var(--color-surface)] shadow-[inset_0_0_0_1px_var(--color-border)] " +
|
|
86
|
+
"focus-within:shadow-[inset_0_0_0_1px_var(--color-primary-600)] " +
|
|
87
|
+
"focus-within:ring-4 focus-within:ring-[var(--color-primary)]/10 " +
|
|
88
|
+
"not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-border-secondary)] " +
|
|
89
|
+
"dark:focus-within:shadow-[inset_0_0_0_1px_var(--color-primary-500)] dark:focus-within:ring-[var(--color-primary-500)]/20";
|
|
90
|
+
// Collapsed: keep the surface fill so the icon reads as a search "button"
|
|
91
|
+
// (not a transparent row). Hover lifts to surface-hover.
|
|
92
|
+
const shellCollapsed = "bg-[var(--color-surface)] shadow-[inset_0_0_0_0_transparent] " +
|
|
93
|
+
"hover:bg-[var(--color-surface-hover)] cursor-pointer";
|
|
94
|
+
const tooltipPlacement = (context === null || context === void 0 ? void 0 : context.side) === "right" ? "left" : "right";
|
|
95
|
+
const field = (jsxs(motion.div, { role: isCollapsedView ? "button" : undefined, tabIndex: isCollapsedView ? 0 : undefined, "aria-label": isCollapsedView ? collapsedLabel : undefined, onClick: isCollapsedView ? handleExpand : undefined, onKeyDown: handleKeyDown, className: cn("relative flex items-center rounded-[var(--form-radius)]",
|
|
96
|
+
// 36px (h-9) in BOTH states so the search row sits at the exact
|
|
97
|
+
// same height as the NavItem rows below it — same look expanded,
|
|
98
|
+
// same 36×36 square collapsed. `size` prop is currently a no-op
|
|
99
|
+
// for height; kept on the API surface for forward compatibility.
|
|
100
|
+
"h-9",
|
|
101
|
+
// NOTE: do NOT toggle `justify-center` between states. Combined
|
|
102
|
+
// with the icon span's constant `pl-2`, the icon sits at the same
|
|
103
|
+
// field-x (8) in both states, so the *only* thing that animates
|
|
104
|
+
// on collapse/expand is the field width (via sidebar's motion
|
|
105
|
+
// width) and the input fading in/out via AnimatePresence. Adding
|
|
106
|
+
// a justify-* toggle here would snap the icon by 4px on frame 0
|
|
107
|
+
// of every transition.
|
|
108
|
+
"transition-[background-color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", "overflow-hidden", isCollapsedView ? shellCollapsed : shellExpanded, inputGroupClassName), children: [jsx("span", { className: cn("flex items-center justify-center flex-shrink-0 pl-2", "text-[var(--color-text-muted)] transition-colors duration-200"), "aria-hidden": !isCollapsedView || undefined, children: iconStart !== null && iconStart !== void 0 ? iconStart : jsx(MagnifyingGlassIcon, { className: "size-5" }) }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && (jsx(motion.div, { initial: { opacity: 0, x: -4 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -4 }, transition: {
|
|
109
|
+
opacity: { duration: 0.15, ease: [0.4, 0, 0.2, 1] },
|
|
110
|
+
x: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
|
|
111
|
+
}, className: "flex-1 min-w-0 flex items-center overflow-hidden", children: jsx("input", { ref: setInputRef, type: "search", value: value, onChange: handleChange, placeholder: placeholder, "aria-label": collapsedLabel, className: cn(
|
|
112
|
+
// Icon span pl-2 (8) + icon size-5 (20) = field-x 8-28.
|
|
113
|
+
// Input pl-3 (12) → text starts at field-x = 28 + 12 = 40,
|
|
114
|
+
// giving a 12px icon→text gap (matches <Input iconStart>'s
|
|
115
|
+
// pl-3 + pl-10 = 12px convention).
|
|
116
|
+
"w-full bg-transparent border-0 outline-none text-sm py-2 pl-3 pr-3", "text-[var(--color-text-primary)] placeholder:text-[var(--color-text-placeholder)]", "dark:placeholder:text-[var(--color-neutral-500)]") }) }, "search-input")) })] }));
|
|
117
|
+
return (
|
|
118
|
+
// Outer horizontal padding mirrors Sidebar.Content (px-3 collapsed,
|
|
119
|
+
// px-2.5 expanded) so the search row sits on the exact same horizontal
|
|
120
|
+
// track as the NavItem rows below it — both have a 36px interior on
|
|
121
|
+
// the 60px collapsed rail, producing identical 36×36 hit-areas.
|
|
122
|
+
jsx("div", { className: cn(
|
|
123
|
+
// py-1 keeps the search row in the same vertical rhythm as the
|
|
124
|
+
// NavGroup's `space-y-1` between nav rows.
|
|
125
|
+
"py-1 transition-[padding] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", isCollapsedView ? "px-3" : "px-2.5", className), ...props, children: isCollapsedView ? (
|
|
126
|
+
// `triggerClassName="block w-full"` makes the Tooltip's wrapping div
|
|
127
|
+
// a full-width block instead of its default `inline-block` — without
|
|
128
|
+
// this, the field collapses to icon-width and sits left-aligned,
|
|
129
|
+
// leaving empty space on the right of the rail. Same fix NavItem
|
|
130
|
+
// uses for its collapsed-mode tooltip wrapper.
|
|
131
|
+
jsx(Tooltip, { content: collapsedLabel, placement: tooltipPlacement, triggerClassName: "block w-full", children: field })) : (field) }));
|
|
132
|
+
});
|
|
133
|
+
SidebarSearch.displayName = "SidebarSearch";
|
|
134
|
+
|
|
135
|
+
export { SidebarSearch };
|
|
@@ -25,7 +25,7 @@ const SidebarToggle = React__default.forwardRef(({ expandIcon, collapseIcon, cla
|
|
|
25
25
|
const positionClasses = side === "left"
|
|
26
26
|
? "absolute right-0 translate-x-1/2 top-4 z-50"
|
|
27
27
|
: "absolute left-0 -translate-x-1/2 top-4 z-50";
|
|
28
|
-
return (jsx(Button, { ref: ref, iconOnly: true, variant: "ghost", className: cn("size-6 p-0 rounded-full border border-[var(--color-border)] bg-[var(--color-background)]/50 backdrop-blur-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-
|
|
28
|
+
return (jsx(Button, { ref: ref, iconOnly: true, variant: "ghost", className: cn("size-6 p-0 rounded-full border border-[var(--color-border)] bg-[var(--color-background)]/50 backdrop-blur-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:shadow-md hover:bg-[var(--color-surface-hover)] opacity-100 transition-all duration-400", positionClasses, className), onClick: handleClick, "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar", title: collapsed ? "Expand sidebar" : "Collapse sidebar", ...buttonProps, children: jsx(motion.div, { initial: false, animate: { rotate: collapsed ? 180 : 0 }, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, className: "flex items-center justify-center", children: jsx(ChevronLeftIcon, { className: "size-3", strokeWidth: 2.5 }) }) }));
|
|
29
29
|
});
|
|
30
30
|
SidebarToggle.displayName = "SidebarToggle";
|
|
31
31
|
|
|
@@ -19,8 +19,8 @@ const SidebarUserProfile = React__default.forwardRef(({ name, email, avatar, sta
|
|
|
19
19
|
const { isCollapsedView } = useSidebarCollapsed();
|
|
20
20
|
// Strip drag/animation DOM handlers that collide with motion's prop types
|
|
21
21
|
const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, ...motionSafeProps } = props;
|
|
22
|
-
const avatarElement = (jsx(Avatar, { src: avatar, name: name,
|
|
23
|
-
const content = (jsxs(motion.div, { ref: ref, className: cn("flex w-full items-center rounded-[var(--radius-base)] cursor-pointer border border-transparent transition-[padding,gap,background-color,border-color,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] ", isCollapsedView ? "justify-center gap-0 px-1
|
|
22
|
+
const avatarElement = (jsx(Avatar, { src: avatar, name: name, status: status !== null && status !== void 0 ? status : "none", className: "size-9" }));
|
|
23
|
+
const content = (jsxs(motion.div, { ref: ref, className: cn("flex w-full items-center rounded-[var(--radius-base)] cursor-pointer border border-transparent transition-[padding,gap,background-color,border-color,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] ", isCollapsedView ? "justify-center gap-0 px-1" : "gap-2 ", className), role: "button", tabIndex: 0, onClick: onClick, onKeyDown: (e) => {
|
|
24
24
|
if ((e.key === "Enter" || e.key === " ") && onClick) {
|
|
25
25
|
e.preventDefault();
|
|
26
26
|
e.currentTarget.click();
|
|
@@ -8,5 +8,7 @@ export { SidebarToggle } from "./SidebarToggle";
|
|
|
8
8
|
export { SidebarMobileTrigger } from "./SidebarMobileTrigger";
|
|
9
9
|
export { SidebarUserProfile } from "./SidebarUserProfile";
|
|
10
10
|
export { SidebarLogo } from "./SidebarLogo";
|
|
11
|
+
export { SidebarSearch } from "./SidebarSearch";
|
|
12
|
+
export { SidebarCard } from "./SidebarCard";
|
|
11
13
|
export { SidebarProvider, useSidebarContext } from "./SidebarContext";
|
|
12
|
-
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarDensity, SidebarContextValue, SidebarProviderProps, SidebarTheme, SidebarThemeOverrides, SidebarLogoProps, } from "./Sidebar.types";
|
|
14
|
+
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarDensity, SidebarContextValue, SidebarProviderProps, SidebarTheme, SidebarThemeOverrides, SidebarLogoProps, SidebarSearchProps, SidebarCardProps, SidebarCardTheme, SidebarCardThemeOverrides, } from "./Sidebar.types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { motion } from 'motion/react';
|
|
4
4
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
@@ -18,7 +18,7 @@ import { tagVariants, tagLayoutTransition } from './Tag.animations.js';
|
|
|
18
18
|
* <Tag startContent={<CodeIcon className="size-4" />} onRemove={() => {}}>React</Tag>
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
const Tag = React__default.forwardRef(({ children, size = "md", removable = true, onRemove, avatar, startContent, disabled = false, className, theme: customTheme, ...props }, ref) => {
|
|
21
|
+
const Tag = React__default.forwardRef(({ children, size = "md", removable = true, onRemove, avatar, startContent, disabled = false, animated = true, className, theme: customTheme, ...props }, ref) => {
|
|
22
22
|
var _a, _b, _c;
|
|
23
23
|
// Merge default theme with custom theme overrides
|
|
24
24
|
const theme = {
|
|
@@ -47,14 +47,38 @@ const Tag = React__default.forwardRef(({ children, size = "md", removable = true
|
|
|
47
47
|
// Avatar takes precedence over startContent
|
|
48
48
|
const renderLeadingContent = () => {
|
|
49
49
|
if (avatar) {
|
|
50
|
-
return (jsx("span", {
|
|
50
|
+
return (jsx("span", {
|
|
51
|
+
// leading-[0] collapses the inline line-box strut: the Avatar's
|
|
52
|
+
// inner circle is inline-flex, so a non-zero line-height adds
|
|
53
|
+
// descender space below it and pins the circle to the top of a
|
|
54
|
+
// taller line box — the visible photo then renders ~1.75px high.
|
|
55
|
+
// Zeroing the line-height makes the 16px circle define the box exactly.
|
|
56
|
+
className: cn("inline-flex items-center justify-center shrink-0 overflow-hidden rounded-full leading-[0]", theme.avatarSizes[size]), children: jsx(Avatar, { src: avatar.src, name: avatar.name, fallback: avatar.fallback, initialsCount: avatar.initialsCount, size: "xs", className: "bg-[var(--color-surface-hover)] border border-[var(--color-border)]" }) }));
|
|
51
57
|
}
|
|
52
58
|
if (startContent) {
|
|
53
|
-
return (jsx("span", {
|
|
59
|
+
return (jsx("span", {
|
|
60
|
+
// leading-[0]: same line-box collapse as the avatar wrapper above,
|
|
61
|
+
// so inline startContent (icons/emoji) stays vertically centered.
|
|
62
|
+
className: cn("inline-flex items-center justify-center shrink-0 leading-[0]", theme.startContentSizes[size]), children: startContent }));
|
|
54
63
|
}
|
|
55
64
|
return null;
|
|
56
65
|
};
|
|
57
|
-
|
|
66
|
+
const content = (jsxs(Fragment, { children: [renderLeadingContent(), children, showRemoveButton && (jsx("button", { type: "button", onClick: handleRemove, className: theme.removeButtonStyle, "aria-label": `Remove ${typeof children === "string" ? children : "tag"}`, children: jsx(XMarkIcon, { className: theme.removeIconSizes[size] }) }))] }));
|
|
67
|
+
const sharedProps = {
|
|
68
|
+
ref,
|
|
69
|
+
className: cn(theme.baseStyle, theme.sizes[size], hasLeadingContent && "pl-1", showRemoveButton && "pr-0.5", disabled && theme.disabledStyle, className),
|
|
70
|
+
"data-size": size,
|
|
71
|
+
"data-disabled": disabled ? "true" : "false",
|
|
72
|
+
"data-removable": removable ? "true" : "false",
|
|
73
|
+
"data-has-avatar": avatar ? "true" : "false",
|
|
74
|
+
...props,
|
|
75
|
+
};
|
|
76
|
+
// Plain span when animation is opted out (e.g. inside a contentEditable host,
|
|
77
|
+
// where Framer-Motion's layout FLIP fights the caret on every keystroke).
|
|
78
|
+
if (!animated) {
|
|
79
|
+
return jsx("span", { ...sharedProps, children: content });
|
|
80
|
+
}
|
|
81
|
+
return (jsx(motion.span, { ...sharedProps, layout: true, transition: tagLayoutTransition, variants: tagVariants, exit: "exit", children: content }));
|
|
58
82
|
});
|
|
59
83
|
Tag.displayName = "Tag";
|
|
60
84
|
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
const tagTheme = {
|
|
2
|
-
baseStyle: "inline-flex items-center gap-1 font-medium rounded-full " +
|
|
3
|
-
"bg-[var(--color-
|
|
2
|
+
baseStyle: "inline-flex items-center gap-1.5 font-medium rounded-full " +
|
|
3
|
+
"bg-[var(--color-surface-raised)] border border-[var(--color-border)] " +
|
|
4
4
|
"text-[var(--color-text-primary)]",
|
|
5
5
|
sizes: {
|
|
6
|
-
sm: "text-xs px-2
|
|
7
|
-
md: "text-sm px-2
|
|
6
|
+
sm: "text-xs px-2 py-0.5",
|
|
7
|
+
md: "text-sm px-2 py-0.5",
|
|
8
8
|
},
|
|
9
|
+
// No left margin here: the avatar/icon's left inset is owned by the tag's
|
|
10
|
+
// `pl-1` (applied when there's leading content), not by a per-size `ml-*`.
|
|
11
|
+
// Stacking `ml-*` on top of that padding pushed the avatar too far from the
|
|
12
|
+
// left edge and produced a lopsided pill. The avatar-to-text gap is handled
|
|
13
|
+
// by the base `gap-1`, not by this margin.
|
|
9
14
|
avatarSizes: {
|
|
10
|
-
sm: "size-4
|
|
11
|
-
md: "size-5
|
|
15
|
+
sm: "size-4",
|
|
16
|
+
md: "size-5",
|
|
12
17
|
},
|
|
13
18
|
startContentSizes: {
|
|
14
|
-
sm: "
|
|
15
|
-
md: "
|
|
19
|
+
sm: "",
|
|
20
|
+
md: "",
|
|
16
21
|
},
|
|
17
22
|
disabledStyle: "opacity-50 cursor-not-allowed",
|
|
18
23
|
removeButtonStyle: "inline-flex items-center justify-center rounded-full p-1 " +
|