@stacksjs/components 0.2.111 → 0.2.112
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/{Sidebar-4hn7vw4n.stx → Sidebar-bg47fqmq.stx} +100 -3
- package/dist/{SidebarItem-gtrcw4ky.stx → SidebarItem-9rvbwhxr.stx} +11 -7
- package/dist/index.js +15 -12
- package/dist/ui/sidebar/themes.d.ts +26 -3
- package/package.json +4 -3
- package/src/components/CodeBlock.stx +54 -0
- package/src/components/Footer.stx +66 -0
- package/src/components/Hero.stx +25 -0
- package/src/components/Installation.stx +37 -0
- package/src/components/index.ts +4 -0
- package/src/composables/index.ts +4 -0
- package/src/composables/useCopyCode.ts +73 -0
- package/src/composables/useDarkMode.ts +100 -0
- package/src/composables/useSEO.ts +230 -0
- package/src/composables/utilities/index.ts +7 -0
- package/src/composables/utilities/useClickOutside.ts +72 -0
- package/src/composables/utilities/useFocusTrap.ts +159 -0
- package/src/composables/utilities/useKeyboard.ts +124 -0
- package/src/composables/utilities/useLocalStorage.ts +132 -0
- package/src/composables/utilities/useMediaQuery.ts +118 -0
- package/src/index.ts +99 -0
- package/src/stx.d.ts +4 -0
- package/src/ui/accordion/Accordion.stx +146 -0
- package/src/ui/accordion/AccordionItem.stx +37 -0
- package/src/ui/accordion/index.ts +47 -0
- package/src/ui/audio/Audio.stx +50 -0
- package/src/ui/audio/README.md +41 -0
- package/src/ui/audio/index.ts +13 -0
- package/src/ui/auth/Login.stx +161 -0
- package/src/ui/auth/README.md +142 -0
- package/src/ui/auth/Signup.stx +159 -0
- package/src/ui/auth/TwoFactorChallenge.stx +111 -0
- package/src/ui/auth/index.ts +32 -0
- package/src/ui/avatar/Avatar.stx +77 -0
- package/src/ui/avatar/index.ts +12 -0
- package/src/ui/badge/Badge.stx +62 -0
- package/src/ui/badge/index.ts +10 -0
- package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
- package/src/ui/breadcrumb/index.ts +15 -0
- package/src/ui/button/Button.stx +74 -0
- package/src/ui/button/README.md +80 -0
- package/src/ui/button/index.ts +85 -0
- package/src/ui/calendar/Calendar.stx +189 -0
- package/src/ui/calendar/README.md +254 -0
- package/src/ui/calendar/index.ts +14 -0
- package/src/ui/card/Card.stx +68 -0
- package/src/ui/card/index.ts +13 -0
- package/src/ui/checkbox/Checkbox.stx +86 -0
- package/src/ui/checkbox/index.ts +16 -0
- package/src/ui/combobox/Combobox.stx +96 -0
- package/src/ui/combobox/ComboboxButton.stx +25 -0
- package/src/ui/combobox/ComboboxInput.stx +32 -0
- package/src/ui/combobox/ComboboxOption.stx +49 -0
- package/src/ui/combobox/ComboboxOptions.stx +16 -0
- package/src/ui/combobox/README.md +150 -0
- package/src/ui/combobox/index.ts +40 -0
- package/src/ui/command-palette/CommandPalette.stx +81 -0
- package/src/ui/command-palette/CommandPaletteItem.stx +27 -0
- package/src/ui/command-palette/README.md +42 -0
- package/src/ui/command-palette/index.ts +15 -0
- package/src/ui/dialog/Dialog.stx +41 -0
- package/src/ui/dialog/DialogBackdrop.stx +9 -0
- package/src/ui/dialog/DialogDescription.stx +11 -0
- package/src/ui/dialog/DialogPanel.stx +16 -0
- package/src/ui/dialog/DialogTitle.stx +11 -0
- package/src/ui/dialog/README.md +121 -0
- package/src/ui/dialog/index.ts +32 -0
- package/src/ui/drawer/Drawer.stx +120 -0
- package/src/ui/drawer/README.md +90 -0
- package/src/ui/drawer/index.ts +9 -0
- package/src/ui/dropdown/Dropdown.stx +82 -0
- package/src/ui/dropdown/DropdownButton.stx +29 -0
- package/src/ui/dropdown/DropdownItem.stx +35 -0
- package/src/ui/dropdown/DropdownItems.stx +18 -0
- package/src/ui/dropdown/README.md +114 -0
- package/src/ui/dropdown/index.ts +29 -0
- package/src/ui/form/Form.stx +95 -0
- package/src/ui/form/index.ts +67 -0
- package/src/ui/heatmap/Heatmap.stx +294 -0
- package/src/ui/image/Image.stx +165 -0
- package/src/ui/image/README.md +164 -0
- package/src/ui/image/index.ts +22 -0
- package/src/ui/input/EmailInput.stx +30 -0
- package/src/ui/input/NumberInput.stx +126 -0
- package/src/ui/input/PasswordInput.stx +143 -0
- package/src/ui/input/SearchInput.stx +44 -0
- package/src/ui/input/TextInput.stx +132 -0
- package/src/ui/input/index.ts +180 -0
- package/src/ui/listbox/Listbox.stx +101 -0
- package/src/ui/listbox/ListboxButton.stx +28 -0
- package/src/ui/listbox/ListboxLabel.stx +11 -0
- package/src/ui/listbox/ListboxOption.stx +49 -0
- package/src/ui/listbox/ListboxOptions.stx +16 -0
- package/src/ui/listbox/README.md +135 -0
- package/src/ui/listbox/index.ts +39 -0
- package/src/ui/navigator/Navigator.stx +113 -0
- package/src/ui/navigator/README.md +352 -0
- package/src/ui/navigator/index.ts +20 -0
- package/src/ui/notification/Notification.stx +117 -0
- package/src/ui/notification/README.md +114 -0
- package/src/ui/notification/index.ts +12 -0
- package/src/ui/pagination/Pagination.stx +150 -0
- package/src/ui/pagination/index.ts +11 -0
- package/src/ui/payment/Checkout.stx +31 -0
- package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
- package/src/ui/payment/PaymentMethods.stx +104 -0
- package/src/ui/payment/README.md +345 -0
- package/src/ui/payment/SubscriptionCheckout.stx +323 -0
- package/src/ui/payment/index.ts +59 -0
- package/src/ui/popover/Popover.stx +60 -0
- package/src/ui/popover/PopoverButton.stx +29 -0
- package/src/ui/popover/PopoverPanel.stx +23 -0
- package/src/ui/popover/README.md +91 -0
- package/src/ui/popover/index.ts +22 -0
- package/src/ui/portal/Portal.stx +57 -0
- package/src/ui/portal/index.ts +6 -0
- package/src/ui/progress/Progress.stx +131 -0
- package/src/ui/progress/index.ts +12 -0
- package/src/ui/radio/Radio.stx +77 -0
- package/src/ui/radio/index.ts +15 -0
- package/src/ui/radio-group/README.md +139 -0
- package/src/ui/radio-group/RadioGroup.stx +60 -0
- package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
- package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
- package/src/ui/radio-group/RadioGroupOption.stx +58 -0
- package/src/ui/radio-group/index.ts +30 -0
- package/src/ui/select/README.md +180 -0
- package/src/ui/select/Select.stx +82 -0
- package/src/ui/select/index.ts +21 -0
- package/src/ui/sidebar/Sidebar.stx +573 -0
- package/src/ui/sidebar/SidebarFooter.stx +71 -0
- package/src/ui/sidebar/SidebarHeader.stx +103 -0
- package/src/ui/sidebar/SidebarItem.stx +90 -0
- package/src/ui/sidebar/SidebarSection.stx +83 -0
- package/src/ui/sidebar/index.ts +146 -0
- package/src/ui/sidebar/themes.ts +288 -0
- package/src/ui/skeleton/Skeleton.stx +48 -0
- package/src/ui/skeleton/index.ts +11 -0
- package/src/ui/spinner/Spinner.stx +109 -0
- package/src/ui/spinner/index.ts +9 -0
- package/src/ui/stepper/README.md +36 -0
- package/src/ui/stepper/Stepper.stx +17 -0
- package/src/ui/stepper/StepperStep.stx +57 -0
- package/src/ui/stepper/index.ts +19 -0
- package/src/ui/storage/README.md +193 -0
- package/src/ui/storage/index.ts +347 -0
- package/src/ui/switch/README.md +114 -0
- package/src/ui/switch/Switch.stx +72 -0
- package/src/ui/switch/index.ts +10 -0
- package/src/ui/table/README.md +39 -0
- package/src/ui/table/Table.stx +14 -0
- package/src/ui/table/TableBody.stx +10 -0
- package/src/ui/table/TableCell.stx +10 -0
- package/src/ui/table/TableHead.stx +10 -0
- package/src/ui/table/TableHeader.stx +20 -0
- package/src/ui/table/TableRow.stx +11 -0
- package/src/ui/table/index.ts +35 -0
- package/src/ui/tabs/TabPanel.stx +25 -0
- package/src/ui/tabs/Tabs.stx +202 -0
- package/src/ui/tabs/index.ts +50 -0
- package/src/ui/teleport/Teleport.stx +65 -0
- package/src/ui/teleport/index.ts +7 -0
- package/src/ui/textarea/Textarea.stx +108 -0
- package/src/ui/textarea/index.ts +20 -0
- package/src/ui/tooltip/Tooltip.stx +71 -0
- package/src/ui/tooltip/index.ts +10 -0
- package/src/ui/transition/README.md +30 -0
- package/src/ui/transition/Transition.stx +102 -0
- package/src/ui/transition/index.ts +25 -0
- package/src/ui/video/README.md +47 -0
- package/src/ui/video/Video.stx +45 -0
- package/src/ui/video/index.ts +16 -0
- package/src/ui/virtual-list/VirtualList.stx +67 -0
- package/src/ui/virtual-list/index.ts +10 -0
- package/src/ui/virtual-table/VirtualTable.stx +103 -0
- package/src/ui/virtual-table/index.ts +21 -0
- package/src/utils/PROP_VALIDATION.md +458 -0
- package/src/utils/THEME_SYSTEM.md +702 -0
- package/src/utils/accessibility.ts +841 -0
- package/src/utils/animation/easing.ts +368 -0
- package/src/utils/animation/helpers.ts +223 -0
- package/src/utils/animation/index.ts +5 -0
- package/src/utils/animation/keyframes.ts +230 -0
- package/src/utils/error-handling.ts +475 -0
- package/src/utils/highlighter.ts +93 -0
- package/src/utils/i18n.ts +634 -0
- package/src/utils/keep-alive.ts +819 -0
- package/src/utils/lazy-loading.ts +530 -0
- package/src/utils/prop-validation.ts +478 -0
- package/src/utils/theme.ts +626 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
// The window-chrome strip at the top of a macOS sidebar: traffic lights on
|
|
3
|
+
// the left, floating "Liquid Glass" toolbar buttons on the right, and the
|
|
4
|
+
// whole strip acts as a window drag region inside a Craft window.
|
|
5
|
+
export const theme = $props.theme || $props.variant || 'macos'
|
|
6
|
+
export const showWindowControls = $props.showWindowControls ?? true
|
|
7
|
+
export const actions = $props.actions || [] // [{ id, icon, label }]
|
|
8
|
+
export const title = $props.title || ''
|
|
9
|
+
export const subtitle = $props.subtitle || ''
|
|
10
|
+
export const logo = $props.logo || ''
|
|
11
|
+
export const showSearch = $props.showSearch ?? false
|
|
12
|
+
export const searchPlaceholder = $props.searchPlaceholder || 'Search'
|
|
13
|
+
|
|
14
|
+
export const isMacos = theme === 'macos' || theme === 'tahoe' || theme === 'macos-tahoe' || theme === 'macos-latest'
|
|
15
|
+
|
|
16
|
+
// Traffic lights: 13px circles, 7px apart, inset ~21px — measured on
|
|
17
|
+
// macOS Tahoe. Colors are Apple's exact stoplight values.
|
|
18
|
+
export const trafficLightClasses = 'h-[13px] w-[13px] rounded-full shadow-[inset_0_0_1px_rgba(0,0,0,0.2)]'
|
|
19
|
+
|
|
20
|
+
// Floating toolbar button: a 34px glass circle that brightens on hover.
|
|
21
|
+
export const toolbarButtonClasses = [
|
|
22
|
+
'grid h-[34px] w-[34px] place-items-center rounded-full',
|
|
23
|
+
'bg-white/55 dark:bg-white/10',
|
|
24
|
+
'backdrop-blur-xl',
|
|
25
|
+
'shadow-[0_1px_4px_rgba(0,0,0,0.12),inset_0_0.5px_0_rgba(255,255,255,0.6)]',
|
|
26
|
+
'text-[#3c3c43]/75 dark:text-[#ebebf5]/75',
|
|
27
|
+
'transition-colors duration-150',
|
|
28
|
+
'hover:bg-white/80 dark:hover:bg-white/20',
|
|
29
|
+
'active:bg-white/95 dark:active:bg-white/25',
|
|
30
|
+
].join(' ')
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<script client>
|
|
34
|
+
const emit = defineEmits()
|
|
35
|
+
|
|
36
|
+
function onAction(event) {
|
|
37
|
+
emit('action', event.currentTarget?.dataset?.sidebarAction || event.target.closest('[data-sidebar-action]')?.dataset.sidebarAction)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onSearchInput(event) {
|
|
41
|
+
emit('search', event.target.value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function onWindowControl(action) {
|
|
45
|
+
const api = window.craft?.window
|
|
46
|
+
if (action === 'close') api?.close?.()
|
|
47
|
+
if (action === 'minimize') api?.minimize?.()
|
|
48
|
+
if (action === 'zoom') (api?.maximize || api?.zoom)?.call(api)
|
|
49
|
+
emit('windowControl', action)
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
@if(isMacos)
|
|
54
|
+
<div class="flex h-[52px] shrink-0 items-center justify-between pl-[21px] pr-[10px] [-webkit-app-region:drag]">
|
|
55
|
+
@if(showWindowControls)
|
|
56
|
+
<div class="group/lights flex items-center gap-[7px] [-webkit-app-region:no-drag]">
|
|
57
|
+
<button type="button" class="{{ trafficLightClasses }} bg-[#ff5f57]" aria-label="Close window" @click="onWindowControl('close')"></button>
|
|
58
|
+
<button type="button" class="{{ trafficLightClasses }} bg-[#febc2e]" aria-label="Minimize window" @click="onWindowControl('minimize')"></button>
|
|
59
|
+
<button type="button" class="{{ trafficLightClasses }} bg-[#28c840]" aria-label="Zoom window" @click="onWindowControl('zoom')"></button>
|
|
60
|
+
</div>
|
|
61
|
+
@else
|
|
62
|
+
<span></span>
|
|
63
|
+
@endif
|
|
64
|
+
|
|
65
|
+
<div class="flex items-center gap-[8px] [-webkit-app-region:no-drag]">
|
|
66
|
+
@foreach(actions as action)
|
|
67
|
+
<button type="button" class="{{ toolbarButtonClasses }}" aria-label="{{ action.label }}" data-sidebar-action="{{ action.id }}" @click="onAction($event)">
|
|
68
|
+
<span class="{{ action.icon }} h-[16px] w-[16px]" aria-hidden="true"></span>
|
|
69
|
+
</button>
|
|
70
|
+
@endforeach
|
|
71
|
+
<slot name="actions" />
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
@if(showSearch)
|
|
76
|
+
<div class="shrink-0 px-[10px] pb-[6px]">
|
|
77
|
+
<div class="relative">
|
|
78
|
+
<span class="i-f7-search absolute left-[8px] top-1/2 h-[13px] w-[13px] -translate-y-1/2 text-[#3c3c43]/50 dark:text-[#ebebf5]/50" aria-hidden="true"></span>
|
|
79
|
+
<input
|
|
80
|
+
type="text"
|
|
81
|
+
placeholder="{{ searchPlaceholder }}"
|
|
82
|
+
class="h-[28px] w-full rounded-[8px] border-0 bg-black/5 pl-[26px] pr-[8px] text-[13px] text-black placeholder:text-[#3c3c43]/50 focus:outline-none focus:ring-2 focus:ring-[#0088ff]/60 dark:bg-white/10 dark:text-white dark:placeholder:text-[#ebebf5]/50"
|
|
83
|
+
@input="onSearchInput($event)"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
@endif
|
|
88
|
+
@else
|
|
89
|
+
<div class="flex h-13 items-center gap-3 border-b border-black/5 px-4 dark:border-white/10">
|
|
90
|
+
@if(logo)
|
|
91
|
+
<img src="{{ logo }}" alt="{{ title }}" class="h-8 w-8 rounded-lg object-cover" />
|
|
92
|
+
@endif
|
|
93
|
+
<div class="min-w-0">
|
|
94
|
+
@if(title)
|
|
95
|
+
<div class="truncate text-sm font-semibold text-black/90 dark:text-white">{{ title }}</div>
|
|
96
|
+
@endif
|
|
97
|
+
@if(subtitle)
|
|
98
|
+
<div class="truncate text-xs text-black/50 dark:text-white/50">{{ subtitle }}</div>
|
|
99
|
+
@endif
|
|
100
|
+
</div>
|
|
101
|
+
<slot name="actions" />
|
|
102
|
+
</div>
|
|
103
|
+
@endif
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
import { resolveIconColor, resolveSidebarTheme } from './themes'
|
|
3
|
+
|
|
4
|
+
export const id = $props.id || ''
|
|
5
|
+
export const label = $props.label || ''
|
|
6
|
+
export const icon = $props.icon || ''
|
|
7
|
+
export const iconColor = $props.iconColor || ''
|
|
8
|
+
export const image = $props.image || ''
|
|
9
|
+
export const href = $props.href || ''
|
|
10
|
+
export const count = $props.count ?? $props.badge ?? ''
|
|
11
|
+
export const active = $props.active ?? false
|
|
12
|
+
export const disabled = $props.disabled ?? false
|
|
13
|
+
export const expandable = $props.expandable ?? false
|
|
14
|
+
export const expanded = $props.expanded ?? true
|
|
15
|
+
export const depth = $props.depth || 0
|
|
16
|
+
export const parents = $props.parents || ''
|
|
17
|
+
export const theme = $props.theme || $props.variant || 'macos'
|
|
18
|
+
|
|
19
|
+
const themeStyle = resolveSidebarTheme(theme).item
|
|
20
|
+
|
|
21
|
+
export const rowClasses = [
|
|
22
|
+
themeStyle.base,
|
|
23
|
+
disabled ? themeStyle.disabled : themeStyle.hover,
|
|
24
|
+
disabled ? '' : themeStyle.pressed,
|
|
25
|
+
active ? themeStyle.active : '',
|
|
26
|
+
].filter(Boolean).join(' ')
|
|
27
|
+
|
|
28
|
+
export const disclosureClasses = themeStyle.disclosure
|
|
29
|
+
export const chevronClasses = themeStyle.chevron
|
|
30
|
+
export const iconSlotClasses = themeStyle.iconSlot
|
|
31
|
+
export const iconClasses = `${icon} ${themeStyle.icon}`
|
|
32
|
+
export const imageClasses = themeStyle.image
|
|
33
|
+
export const labelClasses = themeStyle.label
|
|
34
|
+
export const countClasses = themeStyle.count
|
|
35
|
+
|
|
36
|
+
// Nested rows are indented; the icon tint accepts macOS system color names
|
|
37
|
+
// ("blue", "yellow", …) or any CSS color.
|
|
38
|
+
export const indentStyle = depth > 0 ? `padding-left: ${depth * themeStyle.indentPerLevel}px` : ''
|
|
39
|
+
// The tint is published as a custom property rather than `color:` directly.
|
|
40
|
+
// An inline `color` would outrank any stylesheet rule, so a selected row
|
|
41
|
+
// could never turn its icon white the way AppKit does, and toggling
|
|
42
|
+
// selection on the client could not restore the original tint afterwards.
|
|
43
|
+
const tint = resolveIconColor(iconColor)
|
|
44
|
+
export const iconStyle = tint ? `--stx-sidebar-icon: ${tint}` : ''
|
|
45
|
+
export const isLink = Boolean(href) && !disabled
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<div
|
|
49
|
+
data-sidebar-row
|
|
50
|
+
data-item-id="{{ id }}"
|
|
51
|
+
@if(parents)data-parents="{{ parents }}"@endif
|
|
52
|
+
@if(expandable)data-expanded="{{ expanded ? 'true' : 'false' }}"@endif
|
|
53
|
+
@if(indentStyle)style="{{ indentStyle }}"@endif
|
|
54
|
+
>
|
|
55
|
+
@if(isLink)
|
|
56
|
+
<a href="{{ href }}" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" @if(active)data-active="true" aria-current="page"@endif>
|
|
57
|
+
<span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
|
|
58
|
+
@if(expandable)
|
|
59
|
+
<span data-sidebar-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
|
|
60
|
+
@endif
|
|
61
|
+
</span>
|
|
62
|
+
@if(image)
|
|
63
|
+
<span class="{{ iconSlotClasses }}"><img src="{{ image }}" alt="" class="{{ imageClasses }}" /></span>
|
|
64
|
+
@elseif(icon)
|
|
65
|
+
<span class="{{ iconSlotClasses }}"><span data-sidebar-icon class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
|
|
66
|
+
@endif
|
|
67
|
+
<span class="{{ labelClasses }}">{{ label }}</span>
|
|
68
|
+
@if(count !== '' && count !== null && count !== undefined)
|
|
69
|
+
<span data-sidebar-count class="{{ countClasses }}">{{ count }}</span>
|
|
70
|
+
@endif
|
|
71
|
+
</a>
|
|
72
|
+
@else
|
|
73
|
+
<button type="button" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" @if(active)data-active="true" aria-current="page"@endif @if(disabled)data-disabled disabled aria-disabled="true"@endif>
|
|
74
|
+
<span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
|
|
75
|
+
@if(expandable)
|
|
76
|
+
<span data-sidebar-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
|
|
77
|
+
@endif
|
|
78
|
+
</span>
|
|
79
|
+
@if(image)
|
|
80
|
+
<span class="{{ iconSlotClasses }}"><img src="{{ image }}" alt="" class="{{ imageClasses }}" /></span>
|
|
81
|
+
@elseif(icon)
|
|
82
|
+
<span class="{{ iconSlotClasses }}"><span data-sidebar-icon class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
|
|
83
|
+
@endif
|
|
84
|
+
<span class="{{ labelClasses }}">{{ label }}</span>
|
|
85
|
+
@if(count !== '' && count !== null && count !== undefined)
|
|
86
|
+
<span data-sidebar-count class="{{ countClasses }}">{{ count }}</span>
|
|
87
|
+
@endif
|
|
88
|
+
</button>
|
|
89
|
+
@endif
|
|
90
|
+
</div>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
import { resolveSidebarTheme } from './themes'
|
|
3
|
+
|
|
4
|
+
export const id = $props.id || ''
|
|
5
|
+
export const label = $props.label || ''
|
|
6
|
+
export const items = $props.items || []
|
|
7
|
+
export const collapsible = $props.collapsible ?? true
|
|
8
|
+
export const collapsed = $props.collapsed ?? false
|
|
9
|
+
export const theme = $props.theme || $props.variant || 'macos'
|
|
10
|
+
|
|
11
|
+
const themeStyle = resolveSidebarTheme(theme)
|
|
12
|
+
export const headerClasses = themeStyle.sectionHeader + (collapsible ? ' cursor-default' : '')
|
|
13
|
+
export const chevronClasses = themeStyle.sectionChevron
|
|
14
|
+
export const groupClasses = themeStyle.sectionGroup
|
|
15
|
+
|
|
16
|
+
// Components can't render themselves recursively, so the item tree is
|
|
17
|
+
// flattened into ready-to-render rows here. Each row remembers its depth
|
|
18
|
+
// and ancestor ids — the Sidebar controller uses those to collapse whole
|
|
19
|
+
// subtrees.
|
|
20
|
+
function flatten(list, depth = 0, parents = []) {
|
|
21
|
+
return list.flatMap((item) => {
|
|
22
|
+
const children = item.children || []
|
|
23
|
+
const row = {
|
|
24
|
+
id: item.id,
|
|
25
|
+
label: item.label,
|
|
26
|
+
icon: item.icon || '',
|
|
27
|
+
iconColor: item.iconColor || '',
|
|
28
|
+
image: item.image || '',
|
|
29
|
+
href: item.href || '',
|
|
30
|
+
count: item.count ?? item.badge ?? '',
|
|
31
|
+
active: item.active === true,
|
|
32
|
+
disabled: item.disabled === true,
|
|
33
|
+
expandable: children.length > 0 || item.expandable === true,
|
|
34
|
+
expanded: item.expanded !== false,
|
|
35
|
+
depth,
|
|
36
|
+
parents: parents.join('/'),
|
|
37
|
+
}
|
|
38
|
+
return [row, ...flatten(children, depth + 1, [...parents, item.id])]
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const rows = flatten(items)
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<section
|
|
46
|
+
data-sidebar-section
|
|
47
|
+
data-section-id="{{ id }}"
|
|
48
|
+
data-expanded="{{ collapsed ? 'false' : 'true' }}"
|
|
49
|
+
>
|
|
50
|
+
@if(label)
|
|
51
|
+
@if(collapsible)
|
|
52
|
+
<button type="button" data-sidebar-section-header class="{{ headerClasses }}" aria-expanded="{{ collapsed ? 'false' : 'true' }}">
|
|
53
|
+
<span class="truncate">{{ label }}</span>
|
|
54
|
+
<span data-sidebar-section-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
|
|
55
|
+
</button>
|
|
56
|
+
@else
|
|
57
|
+
<div class="{{ headerClasses }}">
|
|
58
|
+
<span class="truncate">{{ label }}</span>
|
|
59
|
+
</div>
|
|
60
|
+
@endif
|
|
61
|
+
@endif
|
|
62
|
+
|
|
63
|
+
<div class="{{ groupClasses }}" role="group" @if(label)aria-label="{{ label }}"@endif>
|
|
64
|
+
@foreach(rows as row)
|
|
65
|
+
<SidebarItem
|
|
66
|
+
:id="row.id"
|
|
67
|
+
:label="row.label"
|
|
68
|
+
:icon="row.icon"
|
|
69
|
+
:iconColor="row.iconColor"
|
|
70
|
+
:image="row.image"
|
|
71
|
+
:href="row.href"
|
|
72
|
+
:count="row.count"
|
|
73
|
+
:active="row.active"
|
|
74
|
+
:disabled="row.disabled"
|
|
75
|
+
:expandable="row.expandable"
|
|
76
|
+
:expanded="row.expanded"
|
|
77
|
+
:depth="row.depth"
|
|
78
|
+
:parents="row.parents"
|
|
79
|
+
:theme="theme"
|
|
80
|
+
/>
|
|
81
|
+
@endforeach
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export { default as Sidebar } from './Sidebar.stx'
|
|
2
|
+
export { default as SidebarFooter } from './SidebarFooter.stx'
|
|
3
|
+
export { default as SidebarHeader } from './SidebarHeader.stx'
|
|
4
|
+
export { default as SidebarItem } from './SidebarItem.stx'
|
|
5
|
+
export { default as SidebarSection } from './SidebarSection.stx'
|
|
6
|
+
export * from './themes'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One navigation row.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* { id: 'icloud', label: 'iCloud', icon: 'i-f7-tray', iconColor: 'blue', count: 248, active: true }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface SidebarItemData {
|
|
16
|
+
id: string
|
|
17
|
+
label: string
|
|
18
|
+
/** Iconify utility class, e.g. `i-f7-tray`. F7 icons mirror SF Symbols. */
|
|
19
|
+
icon?: string
|
|
20
|
+
/** macOS system color name (`"blue"`, `"red"`, `"yellow"`, …) or any CSS color. */
|
|
21
|
+
iconColor?: string
|
|
22
|
+
/** Image URL rendered instead of an icon (album art, avatars). */
|
|
23
|
+
image?: string
|
|
24
|
+
href?: string
|
|
25
|
+
/** Right-aligned count — rendered as plain gray text like native macOS. */
|
|
26
|
+
count?: string | number
|
|
27
|
+
/** @deprecated Use `count`. */
|
|
28
|
+
badge?: string | number
|
|
29
|
+
active?: boolean
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
/** Nested rows, indented and collapsible under this one. */
|
|
32
|
+
children?: SidebarItemData[]
|
|
33
|
+
/** Show a disclosure chevron even without children. */
|
|
34
|
+
expandable?: boolean
|
|
35
|
+
/** Initial disclosure state when the item has children. Defaults to true. */
|
|
36
|
+
expanded?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A titled group of rows (e.g. "Favorites"). Untitled when `label` is empty. */
|
|
40
|
+
export interface SidebarSectionData {
|
|
41
|
+
id: string
|
|
42
|
+
label?: string
|
|
43
|
+
items: SidebarItemData[]
|
|
44
|
+
/** Section headers collapse their group on click. Defaults to true. */
|
|
45
|
+
collapsible?: boolean
|
|
46
|
+
/** Initial collapse state. Defaults to false (expanded). */
|
|
47
|
+
collapsed?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sidebar themes. `macos` recreates the sidebar of the latest macOS
|
|
52
|
+
* (Tahoe, macOS 26/27) — translucent material, Liquid Glass edge shimmer,
|
|
53
|
+
* 30px rows with 9px-radius highlights and plain gray counts. `tahoe`,
|
|
54
|
+
* `macos-tahoe` and `macos-latest` are aliases of `macos`. The remaining
|
|
55
|
+
* names are legacy looks kept for backwards compatibility.
|
|
56
|
+
*/
|
|
57
|
+
type MacOSSidebarTheme = 'macos' | 'macos-tahoe' | 'macos-latest' | 'tahoe'
|
|
58
|
+
type LegacySidebarTheme = 'vibrancy' | 'solid' | 'transparent' | 'workspace' | 'desktop'
|
|
59
|
+
export type SidebarThemeChoice = MacOSSidebarTheme | LegacySidebarTheme
|
|
60
|
+
|
|
61
|
+
export interface SidebarProps {
|
|
62
|
+
/** Sections with their items. Omit to compose children via the default slot. */
|
|
63
|
+
sections?: SidebarSectionData[]
|
|
64
|
+
/** Visual theme. Defaults to `macos`. */
|
|
65
|
+
theme?: SidebarThemeChoice
|
|
66
|
+
/** @deprecated Use `theme`. */
|
|
67
|
+
variant?: SidebarThemeChoice
|
|
68
|
+
/** Expanded width in pixels. Defaults to 250 (native macOS default). */
|
|
69
|
+
width?: number
|
|
70
|
+
position?: 'left' | 'right'
|
|
71
|
+
/** Layout placement for app shells. Defaults to `fixed`. */
|
|
72
|
+
placement?: 'fixed' | 'sticky' | 'static'
|
|
73
|
+
bordered?: boolean
|
|
74
|
+
/** Whether the sidebar starts collapsed. */
|
|
75
|
+
collapsed?: boolean
|
|
76
|
+
collapsible?: boolean
|
|
77
|
+
/** `hidden` slides away entirely (macOS); `rail` keeps a compact strip. */
|
|
78
|
+
collapseMode?: 'hidden' | 'rail'
|
|
79
|
+
/** Rail width when `collapseMode` is `rail`. */
|
|
80
|
+
minWidth?: number
|
|
81
|
+
/** localStorage key that persists collapse state. */
|
|
82
|
+
persistKey?: string
|
|
83
|
+
/** App-shell selector whose width CSS variable tracks the sidebar. */
|
|
84
|
+
shellSelector?: string
|
|
85
|
+
widthVar?: string
|
|
86
|
+
collapsedClass?: string
|
|
87
|
+
className?: string
|
|
88
|
+
onCollapse?: (collapsed: boolean) => void
|
|
89
|
+
onSectionToggle?: (sectionId: string) => void
|
|
90
|
+
onItemToggle?: (event: { id: string, expanded: boolean }) => void
|
|
91
|
+
onItemClick?: (item: SidebarItemData, event: Event) => void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SidebarSectionProps {
|
|
95
|
+
id: string
|
|
96
|
+
label?: string
|
|
97
|
+
items: SidebarItemData[]
|
|
98
|
+
collapsible?: boolean
|
|
99
|
+
collapsed?: boolean
|
|
100
|
+
theme?: SidebarThemeChoice
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SidebarItemProps extends SidebarItemData {
|
|
104
|
+
/** Nesting depth — set by SidebarSection when flattening the tree. */
|
|
105
|
+
depth?: number
|
|
106
|
+
/** Slash-joined ancestor ids — set by SidebarSection. */
|
|
107
|
+
parents?: string
|
|
108
|
+
theme?: SidebarThemeChoice
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A floating toolbar or footer action button. */
|
|
112
|
+
export interface SidebarActionData {
|
|
113
|
+
id: string
|
|
114
|
+
/** Iconify utility class. */
|
|
115
|
+
icon: string
|
|
116
|
+
/** Accessible label. */
|
|
117
|
+
label: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface SidebarHeaderProps {
|
|
121
|
+
theme?: SidebarThemeChoice
|
|
122
|
+
/** Render traffic lights (wired to Craft's window API when present). */
|
|
123
|
+
showWindowControls?: boolean
|
|
124
|
+
/** Floating Liquid Glass toolbar buttons on the right. */
|
|
125
|
+
actions?: SidebarActionData[]
|
|
126
|
+
showSearch?: boolean
|
|
127
|
+
searchPlaceholder?: string
|
|
128
|
+
/** Legacy (non-macos) header content. */
|
|
129
|
+
title?: string
|
|
130
|
+
subtitle?: string
|
|
131
|
+
logo?: string
|
|
132
|
+
onAction?: (actionId: string) => void
|
|
133
|
+
onSearch?: (value: string) => void
|
|
134
|
+
onWindowControl?: (action: 'close' | 'minimize' | 'zoom') => void
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SidebarFooterProps {
|
|
138
|
+
theme?: SidebarThemeChoice
|
|
139
|
+
/** Account row, like Music's profile footer. */
|
|
140
|
+
avatar?: string
|
|
141
|
+
name?: string
|
|
142
|
+
detail?: string
|
|
143
|
+
actions?: SidebarActionData[]
|
|
144
|
+
onProfileClick?: () => void
|
|
145
|
+
onAction?: (actionId: string) => void
|
|
146
|
+
}
|