@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,36 @@
|
|
|
1
|
+
# Stepper Component
|
|
2
|
+
|
|
3
|
+
A step-by-step progress indicator for multi-step processes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @stacksjs/components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```stx
|
|
14
|
+
<script>
|
|
15
|
+
export let currentStep = 0
|
|
16
|
+
|
|
17
|
+
export function handleStepClick(step) {
|
|
18
|
+
currentStep = step
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
@component('Stepper', { currentStep })
|
|
23
|
+
@component('StepperStep', { stepNumber: 0, currentStep, label: 'Account', onClick: handleStepClick })
|
|
24
|
+
@component('StepperStep', { stepNumber: 1, currentStep, label: 'Profile', onClick: handleStepClick })
|
|
25
|
+
@component('StepperStep', { stepNumber: 2, currentStep, label: 'Confirmation', onClick: handleStepClick })
|
|
26
|
+
@endcomponent
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- Horizontal and vertical orientation
|
|
32
|
+
- Clickable steps
|
|
33
|
+
- Complete/current/upcoming states
|
|
34
|
+
- Dark mode support
|
|
35
|
+
- Accessible
|
|
36
|
+
- Customizable styling
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const currentStep = $props.currentStep || 0
|
|
3
|
+
export const orientation = $props.orientation || 'horizontal'
|
|
4
|
+
export const className = $props.className || ''
|
|
5
|
+
export const as = $props.as || 'div'
|
|
6
|
+
|
|
7
|
+
const orientationClasses = orientation === 'horizontal'
|
|
8
|
+
? 'flex flex-row'
|
|
9
|
+
: 'flex flex-col'
|
|
10
|
+
|
|
11
|
+
const baseClasses = 'w-full'
|
|
12
|
+
const stepperClasses = `${baseClasses} ${orientationClasses} ${className}`.trim()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<component :is="{{ as }}" class="{{ stepperClasses }}">
|
|
16
|
+
<slot :currentStep="currentStep" />
|
|
17
|
+
</component>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const stepNumber = $props.stepNumber
|
|
3
|
+
export const currentStep = $props.currentStep || 0
|
|
4
|
+
export const label = $props.label || ''
|
|
5
|
+
export const description = $props.description || ''
|
|
6
|
+
export const className = $props.className || ''
|
|
7
|
+
export const as = $props.as || 'div'
|
|
8
|
+
export const onClick = $props.onClick
|
|
9
|
+
|
|
10
|
+
export const isComplete = stepNumber < currentStep
|
|
11
|
+
export const isCurrent = stepNumber === currentStep
|
|
12
|
+
export const isUpcoming = stepNumber > currentStep
|
|
13
|
+
|
|
14
|
+
const baseClasses = 'flex items-center'
|
|
15
|
+
const stepClasses = `${baseClasses} ${className}`.trim()
|
|
16
|
+
|
|
17
|
+
export function handleClick() {
|
|
18
|
+
onClick?.(stepNumber)
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<component :is="{{ as }}" class="{{ stepClasses }}">
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
@click="handleClick"
|
|
26
|
+
class="flex items-center gap-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded-lg p-2"
|
|
27
|
+
>
|
|
28
|
+
<div class="{{ isComplete ? 'bg-blue-600 dark:bg-blue-500' : isCurrent ? 'bg-blue-600 dark:bg-blue-500 ring-4 ring-blue-200 dark:ring-blue-800' : 'bg-neutral-300 dark:bg-neutral-600' }} w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold transition-all">
|
|
29
|
+
@if(isComplete)
|
|
30
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
31
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
32
|
+
</svg>
|
|
33
|
+
@else
|
|
34
|
+
{{ stepNumber + 1 }}
|
|
35
|
+
@endif
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
@if(label)
|
|
39
|
+
<div class="text-left">
|
|
40
|
+
<div class="text-sm font-medium {{ isCurrent ? 'text-blue-600 dark:text-blue-400' : 'text-neutral-900 dark:text-white' }}">
|
|
41
|
+
{{ label }}
|
|
42
|
+
</div>
|
|
43
|
+
@if(description)
|
|
44
|
+
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
45
|
+
{{ description }}
|
|
46
|
+
</div>
|
|
47
|
+
@endif
|
|
48
|
+
</div>
|
|
49
|
+
@endif
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<slot :isComplete="isComplete" :isCurrent="isCurrent" :isUpcoming="isUpcoming" />
|
|
53
|
+
</component>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { default as Stepper } from './Stepper.stx'
|
|
2
|
+
export { default as StepperStep } from './StepperStep.stx'
|
|
3
|
+
|
|
4
|
+
export interface StepperProps {
|
|
5
|
+
currentStep?: number
|
|
6
|
+
orientation?: 'horizontal' | 'vertical'
|
|
7
|
+
className?: string
|
|
8
|
+
as?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StepperStepProps {
|
|
12
|
+
stepNumber: number
|
|
13
|
+
currentStep?: number
|
|
14
|
+
label?: string
|
|
15
|
+
description?: string
|
|
16
|
+
className?: string
|
|
17
|
+
as?: string
|
|
18
|
+
onClick?: (step: number) => void
|
|
19
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Storage Utilities
|
|
2
|
+
|
|
3
|
+
Type-safe utilities for localStorage and sessionStorage with expiration support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @stacksjs/components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { localStorage, sessionStorage } from '@stacksjs/components'
|
|
17
|
+
|
|
18
|
+
// Set a value
|
|
19
|
+
localStorage.set('user', { name: 'John', age: 30 })
|
|
20
|
+
|
|
21
|
+
// Get a value
|
|
22
|
+
const user = localStorage.get('user')
|
|
23
|
+
console.log(user) // { name: 'John', age: 30 }
|
|
24
|
+
|
|
25
|
+
// Remove a value
|
|
26
|
+
localStorage.remove('user')
|
|
27
|
+
|
|
28
|
+
// Clear all
|
|
29
|
+
localStorage.clear()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### With Expiration
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Expire after 1 hour (3600000 ms)
|
|
36
|
+
localStorage.set('token', 'abc123', { expire: 3600000 })
|
|
37
|
+
|
|
38
|
+
// After 1 hour, get() returns null
|
|
39
|
+
const token = localStorage.get('token') // null if expired
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### With Namespace
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createStorage } from '@stacksjs/components'
|
|
46
|
+
|
|
47
|
+
const userStorage = createStorage('local', 'user')
|
|
48
|
+
const appStorage = createStorage('local', 'app')
|
|
49
|
+
|
|
50
|
+
userStorage.set('preferences', { theme: 'dark' })
|
|
51
|
+
appStorage.set('preferences', { language: 'en' })
|
|
52
|
+
|
|
53
|
+
// These are stored as 'user:preferences' and 'app:preferences'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Session Storage
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { sessionStorage } from '@stacksjs/components'
|
|
60
|
+
|
|
61
|
+
// Same API as localStorage
|
|
62
|
+
sessionStorage.set('tempData', { value: 123 })
|
|
63
|
+
const data = sessionStorage.get('tempData')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
### Storage Methods
|
|
69
|
+
|
|
70
|
+
#### `set(key: string, value: any, options?: StorageOptions): void`
|
|
71
|
+
Store a value with optional expiration.
|
|
72
|
+
|
|
73
|
+
#### `get<T>(key: string): T | null`
|
|
74
|
+
Retrieve a value. Returns null if expired or not found.
|
|
75
|
+
|
|
76
|
+
#### `remove(key: string): void`
|
|
77
|
+
Remove a specific key.
|
|
78
|
+
|
|
79
|
+
#### `clear(): void`
|
|
80
|
+
Clear all items (or all namespaced items).
|
|
81
|
+
|
|
82
|
+
#### `has(key: string): boolean`
|
|
83
|
+
Check if a key exists and hasn't expired.
|
|
84
|
+
|
|
85
|
+
#### `keys(): string[]`
|
|
86
|
+
Get all keys (without namespace prefix).
|
|
87
|
+
|
|
88
|
+
#### `size(): number`
|
|
89
|
+
Get the number of stored items.
|
|
90
|
+
|
|
91
|
+
## StorageOptions
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface StorageOptions {
|
|
95
|
+
expire?: number // Expiration in milliseconds
|
|
96
|
+
namespace?: string // Prefix for keys (in createStorage)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- **Type-safe** - Full TypeScript support
|
|
103
|
+
- **Expiration** - Automatic expiration handling
|
|
104
|
+
- **Namespacing** - Prevent key collisions
|
|
105
|
+
- **Error handling** - Graceful error handling
|
|
106
|
+
- **JSON serialization** - Automatic serialization/deserialization
|
|
107
|
+
- **Both storages** - localStorage and sessionStorage support
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
### User Authentication Token
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { localStorage } from '@stacksjs/components'
|
|
115
|
+
|
|
116
|
+
// Store token with 24-hour expiration
|
|
117
|
+
function login(token: string) {
|
|
118
|
+
localStorage.set('auth_token', token, {
|
|
119
|
+
expire: 24 * 60 * 60 * 1000, // 24 hours
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Get token (returns null if expired)
|
|
124
|
+
function getToken(): string | null {
|
|
125
|
+
return localStorage.get('auth_token')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check if logged in
|
|
129
|
+
function isLoggedIn(): boolean {
|
|
130
|
+
return localStorage.has('auth_token')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Logout
|
|
134
|
+
function logout() {
|
|
135
|
+
localStorage.remove('auth_token')
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### User Preferences
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createStorage } from '@stacksjs/components'
|
|
143
|
+
|
|
144
|
+
const prefs = createStorage('local', 'preferences')
|
|
145
|
+
|
|
146
|
+
interface UserPreferences {
|
|
147
|
+
theme: 'light' | 'dark'
|
|
148
|
+
language: string
|
|
149
|
+
notifications: boolean
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Save preferences
|
|
153
|
+
function savePreferences(preferences: UserPreferences) {
|
|
154
|
+
prefs.set('user', preferences)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Load preferences with defaults
|
|
158
|
+
function loadPreferences(): UserPreferences {
|
|
159
|
+
const saved = prefs.get<UserPreferences>('user')
|
|
160
|
+
return saved || {
|
|
161
|
+
theme: 'light',
|
|
162
|
+
language: 'en',
|
|
163
|
+
notifications: true,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Shopping Cart
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { localStorage } from '@stacksjs/components'
|
|
172
|
+
|
|
173
|
+
interface CartItem {
|
|
174
|
+
id: string
|
|
175
|
+
name: string
|
|
176
|
+
quantity: number
|
|
177
|
+
price: number
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function addToCart(item: CartItem) {
|
|
181
|
+
const cart = localStorage.get<CartItem[]>('cart') || []
|
|
182
|
+
cart.push(item)
|
|
183
|
+
localStorage.set('cart', cart)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getCart(): CartItem[] {
|
|
187
|
+
return localStorage.get<CartItem[]>('cart') || []
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function clearCart() {
|
|
191
|
+
localStorage.remove('cart')
|
|
192
|
+
}
|
|
193
|
+
```
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// Storage utilities for localStorage and sessionStorage
|
|
2
|
+
|
|
3
|
+
export interface StorageOptions {
|
|
4
|
+
expire?: number // Expiration in milliseconds
|
|
5
|
+
namespace?: string // Prefix for keys
|
|
6
|
+
encrypt?: boolean // Encrypt data (basic XOR encryption)
|
|
7
|
+
compress?: boolean // Compress large objects
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type StorageWatchCallback = (newValue: any, oldValue: any, key: string) => void
|
|
11
|
+
|
|
12
|
+
export class StorageManager {
|
|
13
|
+
private storage: globalThis.Storage
|
|
14
|
+
private namespace: string
|
|
15
|
+
private watchers: Map<string, Set<StorageWatchCallback>> = new Map()
|
|
16
|
+
private encryptionKey = 'stx-storage-key' // Simple encryption key
|
|
17
|
+
|
|
18
|
+
constructor(type: 'local' | 'session' = 'local', namespace = '') {
|
|
19
|
+
this.storage = type === 'local' ? globalThis.localStorage : globalThis.sessionStorage
|
|
20
|
+
this.namespace = namespace
|
|
21
|
+
|
|
22
|
+
// Listen for storage events from other tabs/windows
|
|
23
|
+
if (typeof window !== 'undefined') {
|
|
24
|
+
window.addEventListener('storage', this.handleStorageEvent.bind(this))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private handleStorageEvent(event: StorageEvent): void {
|
|
29
|
+
if (!event.key)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
const key = this.namespace && event.key.startsWith(`${this.namespace}:`)
|
|
33
|
+
? event.key.slice(this.namespace.length + 1)
|
|
34
|
+
: event.key
|
|
35
|
+
|
|
36
|
+
const callbacks = this.watchers.get(key)
|
|
37
|
+
if (callbacks) {
|
|
38
|
+
const oldValue = event.oldValue ? JSON.parse(event.oldValue).value : null
|
|
39
|
+
const newValue = event.newValue ? JSON.parse(event.newValue).value : null
|
|
40
|
+
callbacks.forEach(callback => callback(newValue, oldValue, key))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private encrypt(data: string): string {
|
|
45
|
+
// Simple XOR encryption (not cryptographically secure, just obfuscation)
|
|
46
|
+
let result = ''
|
|
47
|
+
for (let i = 0; i < data.length; i++) {
|
|
48
|
+
result += String.fromCharCode(data.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length))
|
|
49
|
+
}
|
|
50
|
+
return btoa(result) // Base64 encode
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private decrypt(data: string): string {
|
|
54
|
+
try {
|
|
55
|
+
const decoded = atob(data) // Base64 decode
|
|
56
|
+
let result = ''
|
|
57
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
58
|
+
result += String.fromCharCode(decoded.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length))
|
|
59
|
+
}
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return data // Return as-is if decryption fails
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private compress(data: string): string {
|
|
68
|
+
// Simple LZ-based compression for large strings
|
|
69
|
+
if (data.length < 100)
|
|
70
|
+
return data // Don't compress small strings
|
|
71
|
+
|
|
72
|
+
const dict: Record<string, number> = {}
|
|
73
|
+
let dictSize = 256
|
|
74
|
+
const result: number[] = []
|
|
75
|
+
let w = ''
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < data.length; i++) {
|
|
78
|
+
const c = data.charAt(i)
|
|
79
|
+
const wc = w + c
|
|
80
|
+
|
|
81
|
+
if (dict[wc] !== undefined) {
|
|
82
|
+
w = wc
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
result.push(w.length > 1 ? dict[w] : w.charCodeAt(0))
|
|
86
|
+
dict[wc] = dictSize++
|
|
87
|
+
w = c
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (w !== '') {
|
|
92
|
+
result.push(w.length > 1 ? dict[w] : w.charCodeAt(0))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return JSON.stringify(result)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private decompress(data: string): string {
|
|
99
|
+
try {
|
|
100
|
+
const compressed = JSON.parse(data)
|
|
101
|
+
if (!Array.isArray(compressed))
|
|
102
|
+
return data
|
|
103
|
+
|
|
104
|
+
const dict: Record<number, string> = {}
|
|
105
|
+
let dictSize = 256
|
|
106
|
+
let w = String.fromCharCode(compressed[0])
|
|
107
|
+
let result = w
|
|
108
|
+
|
|
109
|
+
for (let i = 1; i < compressed.length; i++) {
|
|
110
|
+
const k = compressed[i]
|
|
111
|
+
let entry: string
|
|
112
|
+
|
|
113
|
+
if (dict[k] !== undefined) {
|
|
114
|
+
entry = dict[k]
|
|
115
|
+
}
|
|
116
|
+
else if (k === dictSize) {
|
|
117
|
+
entry = w + w.charAt(0)
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
return data // Decompression failed
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
result += entry
|
|
124
|
+
dict[dictSize++] = w + entry.charAt(0)
|
|
125
|
+
w = entry
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return data // Return as-is if decompression fails
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private getKey(key: string): string {
|
|
136
|
+
return this.namespace ? `${this.namespace}:${key}` : key
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
set(key: string, value: any, options?: StorageOptions): void {
|
|
140
|
+
const fullKey = this.getKey(key)
|
|
141
|
+
const oldValue = this.get(key)
|
|
142
|
+
|
|
143
|
+
const data = {
|
|
144
|
+
value,
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
expire: options?.expire,
|
|
147
|
+
encrypted: options?.encrypt || false,
|
|
148
|
+
compressed: options?.compress || false,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
let serialized = JSON.stringify(data)
|
|
153
|
+
|
|
154
|
+
// Compress if enabled
|
|
155
|
+
if (options?.compress) {
|
|
156
|
+
serialized = this.compress(serialized)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Encrypt if enabled
|
|
160
|
+
if (options?.encrypt) {
|
|
161
|
+
serialized = this.encrypt(serialized)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.storage.setItem(fullKey, serialized)
|
|
165
|
+
|
|
166
|
+
// Notify watchers
|
|
167
|
+
this.notifyWatchers(key, value, oldValue)
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('Storage set error:', error)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
get<T = any>(key: string): T | null {
|
|
175
|
+
const fullKey = this.getKey(key)
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
let item = this.storage.getItem(fullKey)
|
|
179
|
+
if (!item)
|
|
180
|
+
return null
|
|
181
|
+
|
|
182
|
+
// Try to decrypt if it looks encrypted (base64)
|
|
183
|
+
if (item.match(/^[A-Z0-9+/]+=*$/i)) {
|
|
184
|
+
try {
|
|
185
|
+
item = this.decrypt(item)
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Not encrypted, continue
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Try to decompress if it looks compressed (starts with [)
|
|
193
|
+
if (item.startsWith('[')) {
|
|
194
|
+
try {
|
|
195
|
+
item = this.decompress(item)
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Not compressed, continue
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const data = JSON.parse(item)
|
|
203
|
+
|
|
204
|
+
// Check expiration
|
|
205
|
+
if (data.expire && Date.now() - data.timestamp > data.expire) {
|
|
206
|
+
this.remove(key)
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return data.value as T
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error('Storage get error:', error)
|
|
214
|
+
return null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
watch(key: string, callback: StorageWatchCallback): () => void {
|
|
219
|
+
if (!this.watchers.has(key)) {
|
|
220
|
+
this.watchers.set(key, new Set())
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.watchers.get(key)!.add(callback)
|
|
224
|
+
|
|
225
|
+
// Return unwatch function
|
|
226
|
+
return () => {
|
|
227
|
+
const callbacks = this.watchers.get(key)
|
|
228
|
+
if (callbacks) {
|
|
229
|
+
callbacks.delete(callback)
|
|
230
|
+
if (callbacks.size === 0) {
|
|
231
|
+
this.watchers.delete(key)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private notifyWatchers(key: string, newValue: any, oldValue: any): void {
|
|
238
|
+
const callbacks = this.watchers.get(key)
|
|
239
|
+
if (callbacks) {
|
|
240
|
+
callbacks.forEach(callback => callback(newValue, oldValue, key))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
remove(key: string): void {
|
|
245
|
+
const fullKey = this.getKey(key)
|
|
246
|
+
const oldValue = this.get(key)
|
|
247
|
+
this.storage.removeItem(fullKey)
|
|
248
|
+
|
|
249
|
+
// Notify watchers
|
|
250
|
+
this.notifyWatchers(key, null, oldValue)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
clear(): void {
|
|
254
|
+
if (this.namespace) {
|
|
255
|
+
// Clear only namespaced items
|
|
256
|
+
const keys = this.keys()
|
|
257
|
+
keys.forEach(key => this.remove(key))
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
this.storage.clear()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
has(key: string): boolean {
|
|
265
|
+
return this.get(key) !== null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
keys(): string[] {
|
|
269
|
+
const allKeys = Object.keys(this.storage)
|
|
270
|
+
|
|
271
|
+
if (this.namespace) {
|
|
272
|
+
const prefix = `${this.namespace}:`
|
|
273
|
+
return allKeys
|
|
274
|
+
.filter(key => key.startsWith(prefix))
|
|
275
|
+
.map(key => key.slice(prefix.length))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return allKeys
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
size(): number {
|
|
282
|
+
return this.keys().length
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Get storage quota information (if available)
|
|
286
|
+
getQuota(): { usage: number, quota: number, percentage: number } | null {
|
|
287
|
+
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
|
288
|
+
// Use async/await pattern but return promise
|
|
289
|
+
return null // This would need to be async, but keeping sync API
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Fallback: estimate based on current data
|
|
293
|
+
try {
|
|
294
|
+
let totalSize = 0
|
|
295
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
296
|
+
const key = this.storage.key(i)
|
|
297
|
+
if (key) {
|
|
298
|
+
const value = this.storage.getItem(key) || ''
|
|
299
|
+
totalSize += key.length + value.length
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Typical localStorage quota is 5-10MB
|
|
304
|
+
const estimatedQuota = 5 * 1024 * 1024 // 5MB
|
|
305
|
+
return {
|
|
306
|
+
usage: totalSize * 2, // Multiply by 2 for UTF-16 encoding
|
|
307
|
+
quota: estimatedQuota,
|
|
308
|
+
percentage: (totalSize * 2 / estimatedQuota) * 100,
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Batch operations
|
|
317
|
+
setMany(items: Record<string, any>, options?: StorageOptions): void {
|
|
318
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
319
|
+
this.set(key, value, options)
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getMany<T = any>(keys: string[]): Record<string, T | null> {
|
|
324
|
+
const result: Record<string, T | null> = {}
|
|
325
|
+
keys.forEach((key) => {
|
|
326
|
+
result[key] = this.get<T>(key)
|
|
327
|
+
})
|
|
328
|
+
return result
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
removeMany(keys: string[]): void {
|
|
332
|
+
keys.forEach(key => this.remove(key))
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Convenience exports
|
|
337
|
+
export const storage: {
|
|
338
|
+
local: StorageManager
|
|
339
|
+
session: StorageManager
|
|
340
|
+
} = {
|
|
341
|
+
local: new StorageManager('local'),
|
|
342
|
+
session: new StorageManager('session'),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function createStorage(type: 'local' | 'session' = 'local', namespace?: string): StorageManager {
|
|
346
|
+
return new StorageManager(type, namespace)
|
|
347
|
+
}
|