@stacksjs/components 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +477 -0
- package/dist/Accordion-x2gjedap.stx +108 -0
- package/dist/Audio-0fk6p9tn.stx +50 -0
- package/dist/Avatar-5m0v0s4x.stx +88 -0
- package/dist/Badge-ynes82vv.stx +70 -0
- package/dist/Breadcrumb-h9n427sk.stx +79 -0
- package/dist/Button-7xtrzn14.stx +89 -0
- package/dist/Calendar-egvx1pma.stx +232 -0
- package/dist/Card-r9w7vy1a.stx +74 -0
- package/dist/Checkbox-m8v46pmx.stx +92 -0
- package/dist/Checkout-vcnnr176.stx +39 -0
- package/dist/CodeBlock-mwex82cm.stx +57 -0
- package/dist/Combobox-npb08vkp.stx +13 -0
- package/dist/ComboboxButton-vcdyq4zw.stx +13 -0
- package/dist/ComboboxInput-c9d2p02m.stx +25 -0
- package/dist/ComboboxOption-7d0h9ash.stx +52 -0
- package/dist/ComboboxOptions-4dtqc0qa.stx +12 -0
- package/dist/CommandPalette-ah00bf8v.stx +39 -0
- package/dist/CommandPaletteItem-2yx0gvtj.stx +32 -0
- package/dist/DefaultPaymentMethod-emw5fg8s.stx +62 -0
- package/dist/Dialog-1w4rz7x0.stx +19 -0
- package/dist/DialogBackdrop-qbm3y5ab.stx +9 -0
- package/dist/DialogDescription-01w5j74b.stx +11 -0
- package/dist/DialogPanel-brsszrrs.stx +16 -0
- package/dist/DialogTitle-3rxhwg21.stx +11 -0
- package/dist/Drawer-yfaz8q7f.stx +137 -0
- package/dist/Dropdown-ng7nx556.stx +24 -0
- package/dist/DropdownButton-vc4zpyrb.stx +23 -0
- package/dist/DropdownItem-z491y23c.stx +43 -0
- package/dist/DropdownItems-7ykf29sj.stx +18 -0
- package/dist/EmailInput-bfshpj92.stx +41 -0
- package/dist/Footer-xdaf02nr.stx +66 -0
- package/dist/Form-z4f7w6r1.stx +209 -0
- package/dist/Hero-9vvp7pxf.stx +25 -0
- package/dist/Image-ktk0451c.stx +197 -0
- package/dist/Installation-33bj4rm7.stx +39 -0
- package/dist/Listbox-xcejcmyc.stx +14 -0
- package/dist/ListboxButton-nx5kmw19.stx +17 -0
- package/dist/ListboxLabel-aky8a73n.stx +11 -0
- package/dist/ListboxOption-6h4hsdz5.stx +57 -0
- package/dist/ListboxOptions-2sacdtm5.stx +16 -0
- package/dist/Login-s2jkvd9y.stx +163 -0
- package/dist/Navigator-tqqdgdkw.stx +138 -0
- package/dist/Notification-sry8egea.stx +71 -0
- package/dist/NumberInput-zm93ddfw.stx +138 -0
- package/dist/Pagination-yza9v4qw.stx +183 -0
- package/dist/PasswordInput-4xnndp5y.stx +155 -0
- package/dist/PaymentMethods-jxba6kek.stx +113 -0
- package/dist/Popover-scbqatjq.stx +24 -0
- package/dist/PopoverButton-rbxhzj4k.stx +23 -0
- package/dist/PopoverPanel-9ng1rm6e.stx +23 -0
- package/dist/Portal-j111yvqp.stx +74 -0
- package/dist/Progress-gbmw3akr.stx +142 -0
- package/dist/Radio-1n9qamv1.stx +81 -0
- package/dist/RadioGroup-12dw2vhk.stx +13 -0
- package/dist/RadioGroupDescription-bjm3mdht.stx +11 -0
- package/dist/RadioGroupLabel-aky8a73n.stx +11 -0
- package/dist/RadioGroupOption-9pcxgwmd.stx +51 -0
- package/dist/SearchInput-g240e2nq.stx +63 -0
- package/dist/Select-p9f94b20.stx +89 -0
- package/dist/Sidebar-gwxw8q38.stx +288 -0
- package/dist/SidebarFooter-887kxret.stx +95 -0
- package/dist/SidebarHeader-qjeyx0hv.stx +100 -0
- package/dist/SidebarItem-4dpwtmb4.stx +118 -0
- package/dist/SidebarSection-kh92fk4m.stx +108 -0
- package/dist/Signup-3nxq5zag.stx +161 -0
- package/dist/Skeleton-g0nx1njm.stx +48 -0
- package/dist/Spinner-w4zj58xp.stx +109 -0
- package/dist/Stepper-rwc5actp.stx +17 -0
- package/dist/StepperStep-h0k657nm.stx +53 -0
- package/dist/SubscriptionCheckout-4cbxdg7w.stx +353 -0
- package/dist/Switch-kjq8qd2p.stx +74 -0
- package/dist/Table-59j9qwp0.stx +14 -0
- package/dist/TableBody-reka2gwv.stx +10 -0
- package/dist/TableCell-j7nh4sgr.stx +10 -0
- package/dist/TableHead-ecbrwpqm.stx +10 -0
- package/dist/TableHeader-jn2shwsv.stx +13 -0
- package/dist/TableRow-xz8rrxhc.stx +11 -0
- package/dist/Tabs-kb6ex6m7.stx +145 -0
- package/dist/Teleport-819cg9zm.stx +101 -0
- package/dist/TextInput-zx9kgr79.stx +146 -0
- package/dist/Textarea-5xvmrb0z.stx +119 -0
- package/dist/Tooltip-5ehqsy49.stx +76 -0
- package/dist/Transition-2j39s044.stx +174 -0
- package/dist/TwoFactorChallenge-2q4gg89m.stx +125 -0
- package/dist/Video-6aq4shh4.stx +45 -0
- package/dist/VirtualList-weh5qtm1.stx +83 -0
- package/dist/VirtualTable-59a1eskc.stx +113 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/composables/index.d.ts +4 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/useCopyCode.d.ts +19 -0
- package/dist/composables/useDarkMode.d.ts +25 -0
- package/dist/composables/useSEO.d.ts +36 -0
- package/dist/composables/utilities/index.d.ts +5 -0
- package/dist/composables/utilities/useClickOutside.d.ts +28 -0
- package/dist/composables/utilities/useFocusTrap.d.ts +34 -0
- package/dist/composables/utilities/useKeyboard.d.ts +55 -0
- package/dist/composables/utilities/useLocalStorage.d.ts +33 -0
- package/dist/composables/utilities/useMediaQuery.d.ts +64 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +19 -0
- package/dist/ui/accordion/index.d.ts +12 -0
- package/dist/ui/audio/index.d.ts +12 -0
- package/dist/ui/auth/index.d.ts +29 -0
- package/dist/ui/avatar/index.d.ts +11 -0
- package/dist/ui/badge/index.d.ts +9 -0
- package/dist/ui/breadcrumb/index.d.ts +13 -0
- package/dist/ui/button/index.d.ts +45 -0
- package/dist/ui/calendar/index.d.ts +13 -0
- package/dist/ui/card/index.d.ts +12 -0
- package/dist/ui/checkbox/index.d.ts +15 -0
- package/dist/ui/combobox/index.d.ts +35 -0
- package/dist/ui/command-palette/index.d.ts +13 -0
- package/dist/ui/dialog/index.d.ts +27 -0
- package/dist/ui/drawer/index.d.ts +8 -0
- package/dist/ui/dropdown/index.d.ts +25 -0
- package/dist/ui/form/index.d.ts +48 -0
- package/dist/ui/image/index.d.ts +21 -0
- package/dist/ui/input/index.d.ts +73 -0
- package/dist/ui/listbox/index.d.ts +34 -0
- package/dist/ui/navigator/index.d.ts +18 -0
- package/dist/ui/notification/index.d.ts +11 -0
- package/dist/ui/pagination/index.d.ts +10 -0
- package/dist/ui/payment/index.d.ts +53 -0
- package/dist/ui/popover/index.d.ts +19 -0
- package/dist/ui/portal/index.d.ts +5 -0
- package/dist/ui/progress/index.d.ts +11 -0
- package/dist/ui/radio/index.d.ts +14 -0
- package/dist/ui/radio-group/index.d.ts +26 -0
- package/dist/ui/select/index.d.ts +19 -0
- package/dist/ui/sidebar/index.d.ts +129 -0
- package/dist/ui/skeleton/index.d.ts +10 -0
- package/dist/ui/spinner/index.d.ts +8 -0
- package/dist/ui/stepper/index.d.ts +17 -0
- package/dist/ui/storage/index.d.ts +40 -0
- package/dist/ui/switch/index.d.ts +9 -0
- package/dist/ui/table/index.d.ts +29 -0
- package/dist/ui/tabs/index.d.ts +14 -0
- package/dist/ui/teleport/index.d.ts +6 -0
- package/dist/ui/textarea/index.d.ts +19 -0
- package/dist/ui/tooltip/index.d.ts +9 -0
- package/dist/ui/transition/index.d.ts +24 -0
- package/dist/ui/video/index.d.ts +15 -0
- package/dist/ui/virtual-list/index.d.ts +9 -0
- package/dist/ui/virtual-table/index.d.ts +19 -0
- package/dist/utils/accessibility.d.ts +285 -0
- package/dist/utils/animation/easing.d.ts +112 -0
- package/dist/utils/animation/helpers.d.ts +119 -0
- package/dist/utils/animation/index.d.ts +3 -0
- package/dist/utils/animation/keyframes.d.ts +334 -0
- package/dist/utils/error-handling.d.ts +182 -0
- package/dist/utils/highlighter.d.ts +27 -0
- package/dist/utils/highlighter.js +2 -0
- package/dist/utils/i18n.d.ts +245 -0
- package/dist/utils/keep-alive.d.ts +171 -0
- package/dist/utils/lazy-loading.d.ts +174 -0
- package/dist/utils/prop-validation.d.ts +106 -0
- package/dist/utils/theme.d.ts +234 -0
- package/package.json +69 -0
- package/src/components/CodeBlock.stx +57 -0
- package/src/components/Footer.stx +66 -0
- package/src/components/Hero.stx +25 -0
- package/src/components/Installation.stx +39 -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 +229 -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 +108 -0
- package/src/ui/accordion/index.ts +14 -0
- package/src/ui/audio/Audio.stx +50 -0
- package/src/ui/audio/README.md +42 -0
- package/src/ui/audio/index.ts +13 -0
- package/src/ui/auth/Login.stx +163 -0
- package/src/ui/auth/README.md +145 -0
- package/src/ui/auth/Signup.stx +161 -0
- package/src/ui/auth/TwoFactorChallenge.stx +125 -0
- package/src/ui/auth/index.ts +32 -0
- package/src/ui/avatar/Avatar.stx +88 -0
- package/src/ui/avatar/index.ts +12 -0
- package/src/ui/badge/Badge.stx +70 -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 +89 -0
- package/src/ui/button/README.md +100 -0
- package/src/ui/button/index.ts +85 -0
- package/src/ui/calendar/Calendar.stx +232 -0
- package/src/ui/calendar/README.md +254 -0
- package/src/ui/calendar/index.ts +14 -0
- package/src/ui/card/Card.stx +74 -0
- package/src/ui/card/index.ts +13 -0
- package/src/ui/checkbox/Checkbox.stx +92 -0
- package/src/ui/checkbox/index.ts +16 -0
- package/src/ui/combobox/Combobox.stx +13 -0
- package/src/ui/combobox/ComboboxButton.stx +13 -0
- package/src/ui/combobox/ComboboxInput.stx +25 -0
- package/src/ui/combobox/ComboboxOption.stx +52 -0
- package/src/ui/combobox/ComboboxOptions.stx +12 -0
- package/src/ui/combobox/README.md +154 -0
- package/src/ui/combobox/index.ts +40 -0
- package/src/ui/command-palette/CommandPalette.stx +39 -0
- package/src/ui/command-palette/CommandPaletteItem.stx +32 -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 +19 -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 +133 -0
- package/src/ui/dialog/index.ts +32 -0
- package/src/ui/drawer/Drawer.stx +137 -0
- package/src/ui/drawer/README.md +110 -0
- package/src/ui/drawer/index.ts +9 -0
- package/src/ui/dropdown/Dropdown.stx +24 -0
- package/src/ui/dropdown/DropdownButton.stx +23 -0
- package/src/ui/dropdown/DropdownItem.stx +43 -0
- package/src/ui/dropdown/DropdownItems.stx +18 -0
- package/src/ui/dropdown/README.md +142 -0
- package/src/ui/dropdown/index.ts +29 -0
- package/src/ui/form/Form.stx +209 -0
- package/src/ui/form/index.ts +67 -0
- package/src/ui/heatmap/Heatmap.stx +315 -0
- package/src/ui/image/Image.stx +197 -0
- package/src/ui/image/README.md +171 -0
- package/src/ui/image/index.ts +22 -0
- package/src/ui/input/EmailInput.stx +41 -0
- package/src/ui/input/NumberInput.stx +138 -0
- package/src/ui/input/PasswordInput.stx +155 -0
- package/src/ui/input/SearchInput.stx +63 -0
- package/src/ui/input/TextInput.stx +146 -0
- package/src/ui/input/index.ts +180 -0
- package/src/ui/listbox/Listbox.stx +14 -0
- package/src/ui/listbox/ListboxButton.stx +17 -0
- package/src/ui/listbox/ListboxLabel.stx +11 -0
- package/src/ui/listbox/ListboxOption.stx +57 -0
- package/src/ui/listbox/ListboxOptions.stx +16 -0
- package/src/ui/listbox/README.md +143 -0
- package/src/ui/listbox/index.ts +39 -0
- package/src/ui/navigator/Navigator.stx +138 -0
- package/src/ui/navigator/README.md +352 -0
- package/src/ui/navigator/index.ts +20 -0
- package/src/ui/notification/Notification.stx +71 -0
- package/src/ui/notification/README.md +118 -0
- package/src/ui/notification/index.ts +12 -0
- package/src/ui/pagination/Pagination.stx +183 -0
- package/src/ui/pagination/index.ts +11 -0
- package/src/ui/payment/Checkout.stx +39 -0
- package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
- package/src/ui/payment/PaymentMethods.stx +113 -0
- package/src/ui/payment/README.md +427 -0
- package/src/ui/payment/SubscriptionCheckout.stx +353 -0
- package/src/ui/payment/index.ts +59 -0
- package/src/ui/popover/Popover.stx +24 -0
- package/src/ui/popover/PopoverButton.stx +23 -0
- package/src/ui/popover/PopoverPanel.stx +23 -0
- package/src/ui/popover/README.md +95 -0
- package/src/ui/popover/index.ts +22 -0
- package/src/ui/portal/Portal.stx +74 -0
- package/src/ui/portal/index.ts +6 -0
- package/src/ui/progress/Progress.stx +142 -0
- package/src/ui/progress/index.ts +12 -0
- package/src/ui/radio/Radio.stx +81 -0
- package/src/ui/radio/index.ts +15 -0
- package/src/ui/radio-group/README.md +145 -0
- package/src/ui/radio-group/RadioGroup.stx +13 -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 +51 -0
- package/src/ui/radio-group/index.ts +30 -0
- package/src/ui/select/README.md +204 -0
- package/src/ui/select/Select.stx +89 -0
- package/src/ui/select/index.ts +21 -0
- package/src/ui/sidebar/Sidebar.stx +288 -0
- package/src/ui/sidebar/SidebarFooter.stx +95 -0
- package/src/ui/sidebar/SidebarHeader.stx +100 -0
- package/src/ui/sidebar/SidebarItem.stx +118 -0
- package/src/ui/sidebar/SidebarSection.stx +108 -0
- package/src/ui/sidebar/index.ts +175 -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 +53 -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 +133 -0
- package/src/ui/switch/Switch.stx +74 -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 +13 -0
- package/src/ui/table/TableRow.stx +11 -0
- package/src/ui/table/index.ts +35 -0
- package/src/ui/tabs/Tabs.stx +145 -0
- package/src/ui/tabs/index.ts +16 -0
- package/src/ui/teleport/Teleport.stx +101 -0
- package/src/ui/teleport/index.ts +7 -0
- package/src/ui/textarea/Textarea.stx +119 -0
- package/src/ui/textarea/index.ts +20 -0
- package/src/ui/tooltip/Tooltip.stx +76 -0
- package/src/ui/tooltip/index.ts +10 -0
- package/src/ui/transition/README.md +30 -0
- package/src/ui/transition/Transition.stx +174 -0
- package/src/ui/transition/index.ts +25 -0
- package/src/ui/video/README.md +48 -0
- package/src/ui/video/Video.stx +45 -0
- package/src/ui/video/index.ts +16 -0
- package/src/ui/virtual-list/VirtualList.stx +83 -0
- package/src/ui/virtual-list/index.ts +10 -0
- package/src/ui/virtual-table/VirtualTable.stx +113 -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 +473 -0
- package/src/utils/highlighter.ts +93 -0
- package/src/utils/i18n.ts +634 -0
- package/src/utils/keep-alive.ts +818 -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,100 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* SidebarHeader Component
|
|
4
|
+
*
|
|
5
|
+
* A header component for the sidebar with logo, title, and optional search.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <SidebarHeader
|
|
9
|
+
* title="My App"
|
|
10
|
+
* subtitle="Professional"
|
|
11
|
+
* :showSearch="true"
|
|
12
|
+
* />
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Props
|
|
16
|
+
export const title = $props.title || ''
|
|
17
|
+
export const subtitle = $props.subtitle || ''
|
|
18
|
+
export const logo = $props.logo || ''
|
|
19
|
+
export const logoIcon = $props.logoIcon || ''
|
|
20
|
+
export const showSearch = $props.showSearch ?? false
|
|
21
|
+
export const searchPlaceholder = $props.searchPlaceholder || 'Search...'
|
|
22
|
+
export const searchValue = $props.searchValue || ''
|
|
23
|
+
export const collapsed = $props.collapsed ?? false
|
|
24
|
+
export const onSearch = $props.onSearch
|
|
25
|
+
|
|
26
|
+
// Handle search input
|
|
27
|
+
function handleSearchInput(event) {
|
|
28
|
+
if (onSearch) {
|
|
29
|
+
onSearch(event.target.value)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div class="flex-shrink-0">
|
|
35
|
+
<!-- Title bar area -->
|
|
36
|
+
<div class="h-13 flex items-center justify-between px-4 border-b border-black/5 dark:border-white/10">
|
|
37
|
+
@if(!collapsed)
|
|
38
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
39
|
+
<!-- Logo -->
|
|
40
|
+
@if(logo)
|
|
41
|
+
<img src="{{ logo }}" alt="{{ title }}" class="w-8 h-8 rounded-lg object-cover" />
|
|
42
|
+
@elseif(logoIcon)
|
|
43
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
|
|
44
|
+
<div class="{{ logoIcon }} w-5 h-5 text-white"></div>
|
|
45
|
+
</div>
|
|
46
|
+
@else
|
|
47
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-sm">
|
|
48
|
+
{{ title.charAt(0) }}
|
|
49
|
+
</div>
|
|
50
|
+
@endif
|
|
51
|
+
|
|
52
|
+
<!-- Title & Subtitle -->
|
|
53
|
+
<div class="min-w-0">
|
|
54
|
+
@if(title)
|
|
55
|
+
<div class="text-sm font-semibold text-black/90 dark:text-white truncate">
|
|
56
|
+
{{ title }}
|
|
57
|
+
</div>
|
|
58
|
+
@endif
|
|
59
|
+
@if(subtitle)
|
|
60
|
+
<div class="text-xs text-black/50 dark:text-white/50 truncate">
|
|
61
|
+
{{ subtitle }}
|
|
62
|
+
</div>
|
|
63
|
+
@endif
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
@else
|
|
67
|
+
<!-- Collapsed state - just logo -->
|
|
68
|
+
@if(logo)
|
|
69
|
+
<img src="{{ logo }}" alt="{{ title }}" class="w-8 h-8 rounded-lg object-cover mx-auto" />
|
|
70
|
+
@elseif(logoIcon)
|
|
71
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center mx-auto">
|
|
72
|
+
<div class="{{ logoIcon }} w-5 h-5 text-white"></div>
|
|
73
|
+
</div>
|
|
74
|
+
@else
|
|
75
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-sm mx-auto">
|
|
76
|
+
{{ title.charAt(0) }}
|
|
77
|
+
</div>
|
|
78
|
+
@endif
|
|
79
|
+
@endif
|
|
80
|
+
|
|
81
|
+
<!-- Slot for custom header actions -->
|
|
82
|
+
<slot name="actions" />
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Search (only when expanded) -->
|
|
86
|
+
@if(showSearch && !collapsed)
|
|
87
|
+
<div class="px-3 py-3">
|
|
88
|
+
<div class="relative">
|
|
89
|
+
<div class="i-hugeicons-search-01 absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-black/40 dark:text-white/40"></div>
|
|
90
|
+
<input
|
|
91
|
+
type="text"
|
|
92
|
+
placeholder="{{ searchPlaceholder }}"
|
|
93
|
+
value="{{ searchValue }}"
|
|
94
|
+
class="w-full pl-9 pr-3 py-2 text-sm bg-black/5 dark:bg-white/10 border border-black/10 dark:border-white/10 rounded-lg text-black/90 dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-400/50 transition-colors"
|
|
95
|
+
@input="handleSearchInput"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
@endif
|
|
100
|
+
</div>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* SidebarItem Component
|
|
4
|
+
*
|
|
5
|
+
* An individual navigation item within a sidebar section.
|
|
6
|
+
* Supports icons, badges, active states, and keyboard navigation.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <SidebarItem
|
|
10
|
+
* id="home"
|
|
11
|
+
* label="Home"
|
|
12
|
+
* icon="i-hugeicons-home-02"
|
|
13
|
+
* href="/"
|
|
14
|
+
* :active="true"
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Props
|
|
19
|
+
export const id = $props.id || ''
|
|
20
|
+
export const label = $props.label || ''
|
|
21
|
+
export const icon = $props.icon || ''
|
|
22
|
+
export const href = $props.href || ''
|
|
23
|
+
export const badge = $props.badge || ''
|
|
24
|
+
export const active = $props.active ?? false
|
|
25
|
+
export const disabled = $props.disabled ?? false
|
|
26
|
+
export const indent = $props.indent ?? false
|
|
27
|
+
export const onClick = $props.onClick
|
|
28
|
+
|
|
29
|
+
// Handle click
|
|
30
|
+
function handleClick(event) {
|
|
31
|
+
if (disabled) {
|
|
32
|
+
event.preventDefault()
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (onClick) {
|
|
37
|
+
onClick(event)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build item classes - macOS Tahoe style with translucent-friendly colors
|
|
42
|
+
function getItemClasses() {
|
|
43
|
+
const baseClasses = [
|
|
44
|
+
'flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-[13px]',
|
|
45
|
+
'transition-all duration-150',
|
|
46
|
+
indent ? 'ml-2' : ''
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
if (disabled) {
|
|
50
|
+
baseClasses.push('opacity-50 cursor-not-allowed')
|
|
51
|
+
} else if (active) {
|
|
52
|
+
// Active state - subtle blue highlight like macOS
|
|
53
|
+
baseClasses.push(
|
|
54
|
+
'bg-blue-500/20 text-blue-600 dark:bg-blue-400/25 dark:text-blue-300 font-medium'
|
|
55
|
+
)
|
|
56
|
+
} else {
|
|
57
|
+
// Default state - works with translucent backgrounds
|
|
58
|
+
baseClasses.push(
|
|
59
|
+
'text-black/80 dark:text-white/80',
|
|
60
|
+
'hover:bg-black/5 dark:hover:bg-white/10'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return baseClasses.filter(Boolean).join(' ')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Icon classes
|
|
68
|
+
function getIconClasses() {
|
|
69
|
+
const baseClasses = 'w-4 h-4 flex-shrink-0'
|
|
70
|
+
|
|
71
|
+
if (active) {
|
|
72
|
+
return `${icon} ${baseClasses} text-blue-600 dark:text-blue-300`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return `${icon} ${baseClasses} text-black/50 dark:text-white/50`
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
@if(href && !disabled)
|
|
80
|
+
<a
|
|
81
|
+
href="{{ href }}"
|
|
82
|
+
class="{{ getItemClasses() }}"
|
|
83
|
+
@click="handleClick"
|
|
84
|
+
@if(active)
|
|
85
|
+
aria-current="page"
|
|
86
|
+
@endif
|
|
87
|
+
>
|
|
88
|
+
@if(icon)
|
|
89
|
+
<div class="{{ getIconClasses() }}"></div>
|
|
90
|
+
@endif
|
|
91
|
+
<span class="flex-1 truncate">{{ label }}</span>
|
|
92
|
+
@if(badge)
|
|
93
|
+
<span class="ml-auto px-2 py-0.5 text-xs font-medium rounded-full bg-black/10 text-black/70 dark:bg-white/15 dark:text-white/70">
|
|
94
|
+
{{ badge }}
|
|
95
|
+
</span>
|
|
96
|
+
@endif
|
|
97
|
+
</a>
|
|
98
|
+
@else
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="{{ getItemClasses() }}"
|
|
102
|
+
@click="handleClick"
|
|
103
|
+
@if(disabled)
|
|
104
|
+
disabled
|
|
105
|
+
aria-disabled="true"
|
|
106
|
+
@endif
|
|
107
|
+
>
|
|
108
|
+
@if(icon)
|
|
109
|
+
<div class="{{ getIconClasses() }}"></div>
|
|
110
|
+
@endif
|
|
111
|
+
<span class="flex-1 truncate text-left">{{ label }}</span>
|
|
112
|
+
@if(badge)
|
|
113
|
+
<span class="ml-auto px-2 py-0.5 text-xs font-medium rounded-full bg-black/10 text-black/70 dark:bg-white/15 dark:text-white/70">
|
|
114
|
+
{{ badge }}
|
|
115
|
+
</span>
|
|
116
|
+
@endif
|
|
117
|
+
</button>
|
|
118
|
+
@endif
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* SidebarSection Component
|
|
4
|
+
*
|
|
5
|
+
* A collapsible section within the sidebar containing navigation items.
|
|
6
|
+
* Follows macOS Finder sidebar conventions.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <SidebarSection
|
|
10
|
+
* id="favorites"
|
|
11
|
+
* label="Favorites"
|
|
12
|
+
* icon="i-hugeicons-star"
|
|
13
|
+
* :items="favoriteItems"
|
|
14
|
+
* :expanded="true"
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Props
|
|
19
|
+
export const id = $props.id || ''
|
|
20
|
+
export const label = $props.label || ''
|
|
21
|
+
export const icon = $props.icon || ''
|
|
22
|
+
export const items = $props.items || []
|
|
23
|
+
export const expanded = $props.expanded ?? true
|
|
24
|
+
export const collapsible = $props.collapsible ?? true
|
|
25
|
+
export const showLabel = $props.showLabel ?? true
|
|
26
|
+
export const onToggle = $props.onToggle
|
|
27
|
+
export const onItemClick = $props.onItemClick
|
|
28
|
+
|
|
29
|
+
// Handle section header click
|
|
30
|
+
function handleHeaderClick() {
|
|
31
|
+
if (collapsible && onToggle) {
|
|
32
|
+
onToggle(id)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle item click
|
|
37
|
+
function handleItemClick(item, event) {
|
|
38
|
+
if (onItemClick) {
|
|
39
|
+
onItemClick(item, event)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Section header classes - macOS Finder style
|
|
44
|
+
const headerClasses = [
|
|
45
|
+
'w-full flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-left',
|
|
46
|
+
'transition-all duration-150',
|
|
47
|
+
'text-black/60 dark:text-white/60',
|
|
48
|
+
collapsible ? 'hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer' : ''
|
|
49
|
+
].filter(Boolean).join(' ')
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class="py-1">
|
|
53
|
+
<!-- Section header -->
|
|
54
|
+
@if(collapsible)
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
class="{{ headerClasses }}"
|
|
58
|
+
@click="handleHeaderClick"
|
|
59
|
+
aria-expanded="{{ expanded }}"
|
|
60
|
+
aria-controls="sidebar-section-{{ id }}"
|
|
61
|
+
>
|
|
62
|
+
@if(icon)
|
|
63
|
+
<div class="{{ icon }} w-4 h-4 flex-shrink-0 opacity-70"></div>
|
|
64
|
+
@endif
|
|
65
|
+
@if(showLabel)
|
|
66
|
+
<span class="flex-1 text-[13px] font-semibold tracking-tight truncate">
|
|
67
|
+
{{ label }}
|
|
68
|
+
</span>
|
|
69
|
+
<div class="i-hugeicons-arrow-right-01 w-3 h-3 transition-transform duration-200 opacity-50 {{ expanded ? 'rotate-90' : '' }}"></div>
|
|
70
|
+
@endif
|
|
71
|
+
</button>
|
|
72
|
+
@else
|
|
73
|
+
<div class="{{ headerClasses }}">
|
|
74
|
+
@if(icon)
|
|
75
|
+
<div class="{{ icon }} w-4 h-4 flex-shrink-0 opacity-70"></div>
|
|
76
|
+
@endif
|
|
77
|
+
@if(showLabel)
|
|
78
|
+
<span class="flex-1 text-[13px] font-semibold tracking-tight truncate">
|
|
79
|
+
{{ label }}
|
|
80
|
+
</span>
|
|
81
|
+
@endif
|
|
82
|
+
</div>
|
|
83
|
+
@endif
|
|
84
|
+
|
|
85
|
+
<!-- Section items -->
|
|
86
|
+
@if(expanded && showLabel && items.length > 0)
|
|
87
|
+
<div
|
|
88
|
+
id="sidebar-section-{{ id }}"
|
|
89
|
+
class="mt-1 space-y-0.5 overflow-hidden"
|
|
90
|
+
role="group"
|
|
91
|
+
aria-label="{{ label }}"
|
|
92
|
+
>
|
|
93
|
+
@foreach(items as item)
|
|
94
|
+
<SidebarItem
|
|
95
|
+
:id="item.id"
|
|
96
|
+
:label="item.label"
|
|
97
|
+
:icon="item.icon"
|
|
98
|
+
:href="item.href"
|
|
99
|
+
:badge="item.badge"
|
|
100
|
+
:active="item.active"
|
|
101
|
+
:disabled="item.disabled"
|
|
102
|
+
:indent="true"
|
|
103
|
+
@click="handleItemClick(item, $event)"
|
|
104
|
+
/>
|
|
105
|
+
@endforeach
|
|
106
|
+
</div>
|
|
107
|
+
@endif
|
|
108
|
+
</div>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export { default as Sidebar } from './Sidebar.stx'
|
|
2
|
+
export { default as SidebarSection } from './SidebarSection.stx'
|
|
3
|
+
export { default as SidebarItem } from './SidebarItem.stx'
|
|
4
|
+
export { default as SidebarHeader } from './SidebarHeader.stx'
|
|
5
|
+
export { default as SidebarFooter } from './SidebarFooter.stx'
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export interface SidebarItemData {
|
|
9
|
+
id: string
|
|
10
|
+
label: string
|
|
11
|
+
icon?: string
|
|
12
|
+
href?: string
|
|
13
|
+
badge?: string | number
|
|
14
|
+
active?: boolean
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SidebarSectionData {
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
icon?: string
|
|
22
|
+
items: SidebarItemData[]
|
|
23
|
+
expanded?: boolean
|
|
24
|
+
collapsible?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SidebarProps {
|
|
28
|
+
/** Array of sidebar sections with their items */
|
|
29
|
+
sections?: SidebarSectionData[]
|
|
30
|
+
/** Whether the sidebar is collapsed */
|
|
31
|
+
collapsed?: boolean
|
|
32
|
+
/** Whether the sidebar can be collapsed */
|
|
33
|
+
collapsible?: boolean
|
|
34
|
+
/** Width of the expanded sidebar in pixels */
|
|
35
|
+
width?: number
|
|
36
|
+
/** Minimum width when collapsed in pixels */
|
|
37
|
+
minWidth?: number
|
|
38
|
+
/** Visual style variant */
|
|
39
|
+
variant?: 'tahoe' | 'vibrancy' | 'solid' | 'transparent'
|
|
40
|
+
/** Position of the sidebar */
|
|
41
|
+
position?: 'left' | 'right'
|
|
42
|
+
/** Whether to show border */
|
|
43
|
+
bordered?: boolean
|
|
44
|
+
/** Additional CSS classes */
|
|
45
|
+
className?: string
|
|
46
|
+
/** Callback when collapse state changes */
|
|
47
|
+
onCollapse?: (collapsed: boolean) => void
|
|
48
|
+
/** Callback when a section is toggled */
|
|
49
|
+
onSectionToggle?: (sectionId: string) => void
|
|
50
|
+
/** Callback when an item is clicked */
|
|
51
|
+
onItemClick?: (item: SidebarItemData, event: Event) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SidebarSectionProps {
|
|
55
|
+
id: string
|
|
56
|
+
label: string
|
|
57
|
+
icon?: string
|
|
58
|
+
items: SidebarItemData[]
|
|
59
|
+
expanded?: boolean
|
|
60
|
+
collapsible?: boolean
|
|
61
|
+
showLabel?: boolean
|
|
62
|
+
onToggle?: (id: string) => void
|
|
63
|
+
onItemClick?: (item: SidebarItemData, event: Event) => void
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SidebarItemProps {
|
|
67
|
+
id: string
|
|
68
|
+
label: string
|
|
69
|
+
icon?: string
|
|
70
|
+
href?: string
|
|
71
|
+
badge?: string | number
|
|
72
|
+
active?: boolean
|
|
73
|
+
disabled?: boolean
|
|
74
|
+
indent?: boolean
|
|
75
|
+
onClick?: (event: Event) => void
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SidebarHeaderProps {
|
|
79
|
+
title?: string
|
|
80
|
+
subtitle?: string
|
|
81
|
+
logo?: string
|
|
82
|
+
logoIcon?: string
|
|
83
|
+
showSearch?: boolean
|
|
84
|
+
searchPlaceholder?: string
|
|
85
|
+
searchValue?: string
|
|
86
|
+
collapsed?: boolean
|
|
87
|
+
onSearch?: (value: string) => void
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SidebarFooterProps {
|
|
91
|
+
showSettings?: boolean
|
|
92
|
+
showThemeToggle?: boolean
|
|
93
|
+
settingsHref?: string
|
|
94
|
+
settingsLabel?: string
|
|
95
|
+
collapsed?: boolean
|
|
96
|
+
actions?: Array<{
|
|
97
|
+
label: string
|
|
98
|
+
icon?: string
|
|
99
|
+
href?: string
|
|
100
|
+
onClick?: () => void
|
|
101
|
+
}>
|
|
102
|
+
onThemeToggle?: () => void
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Native Sidebar Configuration
|
|
107
|
+
*
|
|
108
|
+
* Used to configure the native macOS sidebar when running with Craft's --native-sidebar flag.
|
|
109
|
+
* The sidebar uses NSOutlineView with vibrancy for a true Tahoe-style appearance.
|
|
110
|
+
*/
|
|
111
|
+
export interface NativeSidebarConfig {
|
|
112
|
+
/** Header configuration */
|
|
113
|
+
header?: {
|
|
114
|
+
title?: string
|
|
115
|
+
subtitle?: string
|
|
116
|
+
icon?: string // SF Symbol name (e.g., 'house.fill', 'message.fill')
|
|
117
|
+
}
|
|
118
|
+
/** Search placeholder text */
|
|
119
|
+
searchPlaceholder?: string
|
|
120
|
+
/** Array of sidebar sections */
|
|
121
|
+
sections: NativeSidebarSection[]
|
|
122
|
+
/** Minimum width when collapsed */
|
|
123
|
+
minWidth?: number
|
|
124
|
+
/** Maximum width */
|
|
125
|
+
maxWidth?: number
|
|
126
|
+
/** Whether the sidebar can be collapsed */
|
|
127
|
+
canCollapse?: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface NativeSidebarSection {
|
|
131
|
+
/** Unique section ID */
|
|
132
|
+
id: string
|
|
133
|
+
/** Section title (shown as header) */
|
|
134
|
+
title: string
|
|
135
|
+
/** Whether section is collapsible */
|
|
136
|
+
collapsible?: boolean
|
|
137
|
+
/** Whether section starts collapsed */
|
|
138
|
+
collapsed?: boolean
|
|
139
|
+
/** Items in this section */
|
|
140
|
+
items: NativeSidebarItem[]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface NativeSidebarItem {
|
|
144
|
+
/** Unique item ID */
|
|
145
|
+
id: string
|
|
146
|
+
/** Display label */
|
|
147
|
+
label: string
|
|
148
|
+
/** SF Symbol icon name (e.g., 'tray.fill', 'envelope.badge.fill') */
|
|
149
|
+
icon?: string
|
|
150
|
+
/** Badge count or text */
|
|
151
|
+
badge?: string | number
|
|
152
|
+
/** Tint color for the icon (hex) */
|
|
153
|
+
tintColor?: string
|
|
154
|
+
/** Whether this item is selected */
|
|
155
|
+
selected?: boolean
|
|
156
|
+
/** Whether this item is disabled */
|
|
157
|
+
disabled?: boolean
|
|
158
|
+
/** Nested children items */
|
|
159
|
+
children?: NativeSidebarItem[]
|
|
160
|
+
/** Custom data to pass to selection handler */
|
|
161
|
+
data?: Record<string, unknown>
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Craft sidebar selection event
|
|
166
|
+
* Passed to window.craft._sidebarSelectHandler when user clicks a sidebar item
|
|
167
|
+
*/
|
|
168
|
+
export interface SidebarSelectEvent {
|
|
169
|
+
/** ID of the selected item */
|
|
170
|
+
itemId: string
|
|
171
|
+
/** The full item object */
|
|
172
|
+
item: NativeSidebarItem
|
|
173
|
+
/** Parent section ID */
|
|
174
|
+
sectionId?: string
|
|
175
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const variant = $props.variant || 'text'
|
|
3
|
+
export const count = $props.count || 1
|
|
4
|
+
export const width = $props.width || ''
|
|
5
|
+
export const height = $props.height || ''
|
|
6
|
+
export const circle = $props.circle || false
|
|
7
|
+
export const className = $props.className || ''
|
|
8
|
+
export const animate = $props.animate ?? true
|
|
9
|
+
|
|
10
|
+
// Variant classes
|
|
11
|
+
const variantClasses = {
|
|
12
|
+
text: 'h-4 rounded',
|
|
13
|
+
title: 'h-8 rounded',
|
|
14
|
+
avatar: 'w-10 h-10 rounded-full',
|
|
15
|
+
thumbnail: 'w-24 h-24 rounded-md',
|
|
16
|
+
button: 'h-10 w-32 rounded-md',
|
|
17
|
+
card: 'h-64 rounded-lg',
|
|
18
|
+
rect: 'rounded-md',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Animation classes
|
|
22
|
+
const animationClasses = animate ? 'animate-pulse' : ''
|
|
23
|
+
|
|
24
|
+
// Build base classes
|
|
25
|
+
const baseClasses = circle ? 'rounded-full' : variantClasses[variant]
|
|
26
|
+
|
|
27
|
+
// Build style object
|
|
28
|
+
let styleString = ''
|
|
29
|
+
if (width) {
|
|
30
|
+
styleString += `width: ${typeof width === 'number' ? width + 'px' : width};`
|
|
31
|
+
}
|
|
32
|
+
if (height) {
|
|
33
|
+
styleString += `height: ${typeof height === 'number' ? height + 'px' : height};`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const skeletonClasses = `bg-gray-200 dark:bg-gray-700 ${baseClasses} ${animationClasses} ${className}`.trim()
|
|
37
|
+
export const containerClasses = count > 1 ? 'space-y-3' : ''
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
@if(count > 1)
|
|
41
|
+
<div class="{{ containerClasses }}">
|
|
42
|
+
@for(i = 0; i < count; i++)
|
|
43
|
+
<div class="{{ skeletonClasses }}" style="{{ styleString }}" aria-busy="true" aria-live="polite"></div>
|
|
44
|
+
@endfor
|
|
45
|
+
</div>
|
|
46
|
+
@else
|
|
47
|
+
<div class="{{ skeletonClasses }}" style="{{ styleString }}" aria-busy="true" aria-live="polite"></div>
|
|
48
|
+
@endif
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as Skeleton } from './Skeleton.stx'
|
|
2
|
+
|
|
3
|
+
export interface SkeletonProps {
|
|
4
|
+
variant?: 'text' | 'title' | 'avatar' | 'thumbnail' | 'button' | 'card' | 'rect'
|
|
5
|
+
count?: number
|
|
6
|
+
width?: string | number
|
|
7
|
+
height?: string | number
|
|
8
|
+
circle?: boolean
|
|
9
|
+
animate?: boolean
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const variant = $props.variant || 'circle'
|
|
3
|
+
export const size = $props.size || 'md'
|
|
4
|
+
export const color = $props.color || 'primary'
|
|
5
|
+
export const label = $props.label || ''
|
|
6
|
+
export const className = $props.className || ''
|
|
7
|
+
|
|
8
|
+
// Size classes
|
|
9
|
+
const sizeClasses = {
|
|
10
|
+
xs: 'w-3 h-3',
|
|
11
|
+
sm: 'w-4 h-4',
|
|
12
|
+
md: 'w-6 h-6',
|
|
13
|
+
lg: 'w-8 h-8',
|
|
14
|
+
xl: 'w-12 h-12',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Color classes
|
|
18
|
+
const colorClasses = {
|
|
19
|
+
primary: 'text-blue-600 dark:text-blue-500',
|
|
20
|
+
secondary: 'text-purple-600 dark:text-purple-500',
|
|
21
|
+
success: 'text-green-600 dark:text-green-500',
|
|
22
|
+
warning: 'text-yellow-600 dark:text-yellow-500',
|
|
23
|
+
danger: 'text-red-600 dark:text-red-500',
|
|
24
|
+
info: 'text-cyan-600 dark:text-cyan-500',
|
|
25
|
+
white: 'text-white',
|
|
26
|
+
gray: 'text-gray-600 dark:text-gray-400',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const containerClasses = `inline-flex flex-col items-center gap-2 ${className}`.trim()
|
|
30
|
+
export const spinnerClasses = `${sizeClasses[size]} ${colorClasses[color]} animate-spin`.trim()
|
|
31
|
+
export const labelClasses = `text-sm font-medium text-gray-700 dark:text-gray-300`.trim()
|
|
32
|
+
|
|
33
|
+
// Dots size classes
|
|
34
|
+
const dotsSizeClasses = {
|
|
35
|
+
xs: 'w-1 h-1',
|
|
36
|
+
sm: 'w-1.5 h-1.5',
|
|
37
|
+
md: 'w-2 h-2',
|
|
38
|
+
lg: 'w-2.5 h-2.5',
|
|
39
|
+
xl: 'w-3 h-3',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dotsColorClasses = {
|
|
43
|
+
primary: 'bg-blue-600 dark:bg-blue-500',
|
|
44
|
+
secondary: 'bg-purple-600 dark:bg-purple-500',
|
|
45
|
+
success: 'bg-green-600 dark:bg-green-500',
|
|
46
|
+
warning: 'bg-yellow-600 dark:bg-yellow-500',
|
|
47
|
+
danger: 'bg-red-600 dark:bg-red-500',
|
|
48
|
+
info: 'bg-cyan-600 dark:bg-cyan-500',
|
|
49
|
+
white: 'bg-white',
|
|
50
|
+
gray: 'bg-gray-600 dark:bg-gray-400',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const dotClasses = `${dotsSizeClasses[size]} ${dotsColorClasses[color]} rounded-full`.trim()
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<div class="{{ containerClasses }}" role="status" aria-live="polite" aria-busy="true">
|
|
57
|
+
@if(variant === 'circle')
|
|
58
|
+
<!-- Circle spinner (default) -->
|
|
59
|
+
<svg class="{{ spinnerClasses }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
60
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
61
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
62
|
+
</svg>
|
|
63
|
+
@elseif(variant === 'dots')
|
|
64
|
+
<!-- Dots spinner -->
|
|
65
|
+
<div class="flex items-center gap-1">
|
|
66
|
+
<div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both; animation-delay: -0.32s"></div>
|
|
67
|
+
<div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both; animation-delay: -0.16s"></div>
|
|
68
|
+
<div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both"></div>
|
|
69
|
+
</div>
|
|
70
|
+
@elseif(variant === 'bars')
|
|
71
|
+
<!-- Bars spinner -->
|
|
72
|
+
<div class="flex items-center gap-1">
|
|
73
|
+
<div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out; animation-delay: -0.24s"></div>
|
|
74
|
+
<div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out; animation-delay: -0.12s"></div>
|
|
75
|
+
<div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out"></div>
|
|
76
|
+
</div>
|
|
77
|
+
@elseif(variant === 'ring')
|
|
78
|
+
<!-- Ring spinner -->
|
|
79
|
+
<div class="{{ spinnerClasses }}" style="border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;"></div>
|
|
80
|
+
@endif
|
|
81
|
+
|
|
82
|
+
@if(label)
|
|
83
|
+
<span class="{{ labelClasses }}">{{ label }}</span>
|
|
84
|
+
@endif
|
|
85
|
+
|
|
86
|
+
<span class="sr-only">{{ label || 'Loading...' }}</span>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<style>
|
|
90
|
+
@keyframes bounce {
|
|
91
|
+
0%, 80%, 100% {
|
|
92
|
+
transform: scale(0);
|
|
93
|
+
opacity: 0.5;
|
|
94
|
+
}
|
|
95
|
+
40% {
|
|
96
|
+
transform: scale(1);
|
|
97
|
+
opacity: 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes bars {
|
|
102
|
+
0%, 40%, 100% {
|
|
103
|
+
transform: scaleY(0.4);
|
|
104
|
+
}
|
|
105
|
+
20% {
|
|
106
|
+
transform: scaleY(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Spinner } from './Spinner.stx'
|
|
2
|
+
|
|
3
|
+
export interface SpinnerProps {
|
|
4
|
+
variant?: 'circle' | 'dots' | 'bars' | 'ring'
|
|
5
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
6
|
+
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'white' | 'gray'
|
|
7
|
+
label?: string
|
|
8
|
+
className?: string
|
|
9
|
+
}
|