cisse-vue-ui 0.8.3 → 0.9.0
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 +666 -4
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs → CheckboxGroup.vue_vue_type_script_setup_true_lang-BC86pBlY.cjs} +70 -70
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-BC86pBlY.cjs.map +1 -0
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js → CheckboxGroup.vue_vue_type_script_setup_true_lang-ZP02bMgY.js} +72 -72
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-ZP02bMgY.js.map +1 -0
- package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-C5KHLMvx.js} +37 -184
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-C5KHLMvx.js.map +1 -0
- package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs → ConfirmDialog.vue_vue_type_script_setup_true_lang-CLfy0-Wb.cjs} +33 -180
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-CLfy0-Wb.cjs.map +1 -0
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs → Dropdown.vue_vue_type_script_setup_true_lang-BAKGRZIb.cjs} +2 -2
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs.map → Dropdown.vue_vue_type_script_setup_true_lang-BAKGRZIb.cjs.map} +1 -1
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js → Dropdown.vue_vue_type_script_setup_true_lang-GLCX7E3C.js} +2 -2
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js.map → Dropdown.vue_vue_type_script_setup_true_lang-GLCX7E3C.js.map} +1 -1
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs → FilterTabs.vue_vue_type_script_setup_true_lang-COkZbeGG.cjs} +1260 -209
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-COkZbeGG.cjs.map +1 -0
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js → FilterTabs.vue_vue_type_script_setup_true_lang-CzpYHtc5.js} +1269 -218
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-CzpYHtc5.js.map +1 -0
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BHopJ9RG.js +298 -0
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BHopJ9RG.js.map +1 -0
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-Bo3HqgX0.cjs +297 -0
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-Bo3HqgX0.cjs.map +1 -0
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs → PageHero.vue_vue_type_script_setup_true_lang-BqwBJlv0.cjs} +2 -2
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs.map → PageHero.vue_vue_type_script_setup_true_lang-BqwBJlv0.cjs.map} +1 -1
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js → PageHero.vue_vue_type_script_setup_true_lang-by-P5wIB.js} +2 -2
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js.map → PageHero.vue_vue_type_script_setup_true_lang-by-P5wIB.js.map} +1 -1
- package/dist/cisse-vue-ui.css +4 -4
- package/dist/components/core/AccordionItem.test.d.ts +1 -0
- package/dist/components/core/Breadcrumb.stories.d.ts +5 -0
- package/dist/components/core/CardWrapper.stories.d.ts +32 -0
- package/dist/components/core/CardWrapper.test.d.ts +1 -0
- package/dist/components/core/CardWrapper.vue.d.ts +129 -0
- package/dist/components/core/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/core/DataTable.stories.d.ts +38 -0
- package/dist/components/core/Dropdown.vue.d.ts +1 -1
- package/dist/components/core/Popover.vue.d.ts +2 -2
- package/dist/components/core/Tooltip.stories.d.ts +3 -0
- package/dist/components/core/index.cjs +40 -23
- package/dist/components/core/index.cjs.map +1 -1
- package/dist/components/core/index.d.ts +4 -1
- package/dist/components/core/index.js +40 -23
- package/dist/components/core/table/DataTable.test.d.ts +1 -0
- package/dist/components/core/{TableComponent.vue.d.ts → table/DataTable.vue.d.ts} +60 -7
- package/dist/components/core/table/Table.stories.d.ts +27 -0
- package/dist/components/core/table/atoms/Caption.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Caption.vue.d.ts +26 -0
- package/dist/components/core/table/atoms/Col.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Col.vue.d.ts +8 -0
- package/dist/components/core/table/atoms/Colgroup.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Colgroup.vue.d.ts +17 -0
- package/dist/components/core/table/atoms/Table.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Table.vue.d.ts +46 -0
- package/dist/components/core/table/atoms/Tbody.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Tbody.vue.d.ts +17 -0
- package/dist/components/core/table/atoms/Td.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Td.vue.d.ts +43 -0
- package/dist/components/core/table/atoms/Tfoot.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Tfoot.vue.d.ts +17 -0
- package/dist/components/core/table/atoms/Th.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Th.vue.d.ts +64 -0
- package/dist/components/core/table/atoms/Thead.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Thead.vue.d.ts +17 -0
- package/dist/components/core/table/atoms/Tr.test.d.ts +1 -0
- package/dist/components/core/table/atoms/Tr.vue.d.ts +35 -0
- package/dist/components/core/table/atoms/index.d.ts +10 -0
- package/dist/components/core/table/index.d.ts +3 -0
- package/dist/components/core/table/molecules/ExpandableRow.test.d.ts +1 -0
- package/dist/components/core/table/molecules/ExpandableRow.vue.d.ts +47 -0
- package/dist/components/core/table/molecules/TableFooter.test.d.ts +1 -0
- package/dist/components/core/table/molecules/TableFooter.vue.d.ts +21 -0
- package/dist/components/core/table/molecules/TableHeader.test.d.ts +1 -0
- package/dist/components/core/table/molecules/TableHeader.vue.d.ts +49 -0
- package/dist/components/core/table/molecules/TableRow.test.d.ts +1 -0
- package/dist/components/core/table/molecules/TableRow.vue.d.ts +59 -0
- package/dist/components/core/table/molecules/index.d.ts +4 -0
- package/dist/components/feedback/Progress.vue.d.ts +1 -1
- package/dist/components/feedback/TableSkeleton.vue.d.ts +1 -1
- package/dist/components/feedback/index.cjs +14 -14
- package/dist/components/feedback/index.js +14 -14
- package/dist/components/form/Combobox.stories.d.ts +2 -0
- package/dist/components/form/Combobox.vue.d.ts +4 -0
- package/dist/components/form/DatePicker.stories.d.ts +2 -0
- package/dist/components/form/DatePicker.vue.d.ts +4 -0
- package/dist/components/form/FormSection.vue.d.ts +1 -1
- package/dist/components/form/FormSelect.stories.d.ts +2 -0
- package/dist/components/form/FormSelect.vue.d.ts +4 -0
- package/dist/components/form/IconPicker.stories.d.ts +19 -0
- package/dist/components/form/IconPicker.test.d.ts +1 -0
- package/dist/components/form/InputWrapper.stories.d.ts +1 -5
- package/dist/components/form/InputWrapper.vue.d.ts +6 -3
- package/dist/components/form/Rating.vue.d.ts +1 -1
- package/dist/components/form/SearchInput.vue.d.ts +1 -1
- package/dist/components/form/TagsInput.stories.d.ts +1 -0
- package/dist/components/form/TagsInput.vue.d.ts +3 -3
- package/dist/components/form/TextArea.stories.d.ts +3 -1
- package/dist/components/form/TextArea.vue.d.ts +4 -0
- package/dist/components/form/index.cjs +1 -1
- package/dist/components/form/index.js +2 -2
- package/dist/components/index.cjs +56 -39
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +66 -49
- package/dist/components/layout/index.cjs +1 -1
- package/dist/components/layout/index.js +1 -1
- package/dist/composables/index.cjs +18 -9
- package/dist/composables/index.cjs.map +1 -1
- package/dist/composables/index.d.ts +8 -0
- package/dist/composables/index.js +15 -6
- package/dist/composables/index.js.map +1 -1
- package/dist/composables/useColumnResize.d.ts +38 -0
- package/dist/composables/useColumnResize.test.d.ts +1 -0
- package/dist/composables/useColumnVisibility.d.ts +44 -0
- package/dist/composables/useColumnVisibility.test.d.ts +1 -0
- package/dist/composables/useEditableCell.d.ts +51 -0
- package/dist/composables/useEditableCell.test.d.ts +1 -0
- package/dist/composables/useInputStyles.d.ts +32 -0
- package/dist/composables/useInputStyles.test.d.ts +1 -0
- package/dist/composables/usePagination.d.ts +44 -0
- package/dist/composables/usePagination.test.d.ts +1 -0
- package/dist/composables/usePinnedRows.d.ts +41 -0
- package/dist/composables/usePinnedRows.test.d.ts +1 -0
- package/dist/composables/useTableKeyboardNavigation.d.ts +52 -0
- package/dist/composables/useTableKeyboardNavigation.test.d.ts +1 -0
- package/dist/composables/useVirtualScroll.d.ts +32 -0
- package/dist/composables/useVirtualScroll.test.d.ts +1 -0
- package/dist/index-0kwQORZJ.js +114 -0
- package/dist/index-0kwQORZJ.js.map +1 -0
- package/dist/{index-5dQNEzd8.cjs → index-BMSH4AOz.cjs} +57 -40
- package/dist/{index-5dQNEzd8.cjs.map → index-BMSH4AOz.cjs.map} +1 -1
- package/dist/{index-SNefWfX0.js → index-BaWpldIJ.js} +3 -3
- package/dist/{index-SNefWfX0.js.map → index-BaWpldIJ.js.map} +1 -1
- package/dist/index.cjs +75 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +83 -57
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/components.d.ts +1 -1
- package/dist/types/property.d.ts +8 -0
- package/dist/{useDropdown-DK4c5JGL.cjs → useDropdown-HI7ABBLe.cjs} +5 -4
- package/dist/{useDropdown-DK4c5JGL.cjs.map → useDropdown-HI7ABBLe.cjs.map} +1 -1
- package/dist/{useDropdown-De0cKI83.js → useDropdown-XITCE_SM.js} +5 -4
- package/dist/{useDropdown-De0cKI83.js.map → useDropdown-XITCE_SM.js.map} +1 -1
- package/dist/useInputStyles-BFTJdXHL.js +127 -0
- package/dist/useInputStyles-BFTJdXHL.js.map +1 -0
- package/dist/useInputStyles-DMfvW6N5.cjs +126 -0
- package/dist/useInputStyles-DMfvW6N5.cjs.map +1 -0
- package/dist/usePagination-BGwbICFC.js +135 -0
- package/dist/usePagination-BGwbICFC.js.map +1 -0
- package/dist/usePagination-gvvh1zqA.cjs +134 -0
- package/dist/usePagination-gvvh1zqA.cjs.map +1 -0
- package/dist/useVirtualScroll-BivP86fA.cjs +869 -0
- package/dist/useVirtualScroll-BivP86fA.cjs.map +1 -0
- package/dist/useVirtualScroll-YeZru2Eo.js +870 -0
- package/dist/useVirtualScroll-YeZru2Eo.js.map +1 -0
- package/package.json +1 -1
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs.map +0 -1
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js.map +0 -1
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs.map +0 -1
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js.map +0 -1
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js.map +0 -1
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs.map +0 -1
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BwtEbaiT.js +0 -150
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BwtEbaiT.js.map +0 -1
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-DtwwmfWr.cjs +0 -149
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-DtwwmfWr.cjs.map +0 -1
- package/dist/components/core/TableComponent.stories.d.ts +0 -16
- package/dist/index-CDDUEkXf.js +0 -97
- package/dist/index-CDDUEkXf.js.map +0 -1
- package/dist/useDarkMode-Cl5QWTlC.js +0 -53
- package/dist/useDarkMode-Cl5QWTlC.js.map +0 -1
- package/dist/useDarkMode-DLZcJEUQ.cjs +0 -52
- package/dist/useDarkMode-DLZcJEUQ.cjs.map +0 -1
- package/dist/useToast-Bk60GArg.cjs +0 -176
- package/dist/useToast-Bk60GArg.cjs.map +0 -1
- package/dist/useToast-ina5g3mj.js +0 -177
- package/dist/useToast-ina5g3mj.js.map +0 -1
|
@@ -133,8 +133,9 @@ function useDropdown(triggerRef, dropdownRef, options = {}) {
|
|
|
133
133
|
};
|
|
134
134
|
const dropdownStyle = computed(() => {
|
|
135
135
|
if (!teleport) return {};
|
|
136
|
-
const [side] = placement.split("-");
|
|
136
|
+
const [side, alignment] = placement.split("-");
|
|
137
137
|
const isHorizontal = side === "left" || side === "right";
|
|
138
|
+
const isRightAligned = align === "right" || alignment === "end";
|
|
138
139
|
const style = {
|
|
139
140
|
position: "absolute",
|
|
140
141
|
top: `${dropdownPosition.value.top}px`
|
|
@@ -146,8 +147,8 @@ function useDropdown(triggerRef, dropdownRef, options = {}) {
|
|
|
146
147
|
style.left = `${dropdownPosition.value.left}px`;
|
|
147
148
|
}
|
|
148
149
|
} else {
|
|
149
|
-
style.left =
|
|
150
|
-
style.right =
|
|
150
|
+
style.left = isRightAligned ? "auto" : `${dropdownPosition.value.left}px`;
|
|
151
|
+
style.right = isRightAligned ? `${dropdownPosition.value.right}px` : "auto";
|
|
151
152
|
style.width = `${dropdownPosition.value.width}px`;
|
|
152
153
|
}
|
|
153
154
|
return style;
|
|
@@ -184,4 +185,4 @@ function useDropdown(triggerRef, dropdownRef, options = {}) {
|
|
|
184
185
|
export {
|
|
185
186
|
useDropdown as u
|
|
186
187
|
};
|
|
187
|
-
//# sourceMappingURL=useDropdown-
|
|
188
|
+
//# sourceMappingURL=useDropdown-XITCE_SM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDropdown-De0cKI83.js","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type DropdownPlacement =\r\n | 'bottom'\r\n | 'bottom-start'\r\n | 'bottom-end'\r\n | 'top'\r\n | 'top-start'\r\n | 'top-end'\r\n | 'right'\r\n | 'right-start'\r\n | 'right-end'\r\n | 'left'\r\n | 'left-start'\r\n | 'left-end'\r\n\r\nexport interface UseDropdownOptions {\r\n /** Whether teleport is enabled (affects position calculation) */\r\n teleport?: boolean\r\n /** Alignment for position calculation (deprecated, use placement instead) */\r\n align?: 'left' | 'right'\r\n /** Placement of the dropdown relative to the trigger */\r\n placement?: DropdownPlacement\r\n /** Gap between trigger and dropdown in pixels */\r\n gap?: number\r\n /** Callback when dropdown opens */\r\n onOpen?: () => void\r\n /** Callback when dropdown closes */\r\n onClose?: () => void\r\n}\r\n\r\nexport interface UseDropdownReturn {\r\n /** Whether the dropdown is currently open */\r\n isOpen: Ref<boolean>\r\n /** Current highlighted index for keyboard navigation */\r\n highlightedIndex: Ref<number>\r\n /** Calculated position for teleported dropdown */\r\n dropdownPosition: Ref<{ top: number; left: number; right: number; width: number }>\r\n /** Computed style object for teleported dropdown */\r\n dropdownStyle: ComputedRef<Record<string, string>>\r\n /** Open the dropdown */\r\n open: () => void\r\n /** Close the dropdown */\r\n close: () => void\r\n /** Toggle the dropdown */\r\n toggle: () => void\r\n /** Update position (call after DOM changes) */\r\n updatePosition: () => void\r\n /** Handle keyboard navigation */\r\n handleKeydown: (event: KeyboardEvent, options: KeyboardNavigationOptions) => void\r\n /** Scroll to highlighted item */\r\n scrollToHighlighted: (dropdownEl: HTMLElement | null) => void\r\n}\r\n\r\nexport interface KeyboardNavigationOptions {\r\n /** Total number of items to navigate */\r\n itemCount: number\r\n /** Called when Enter is pressed on a highlighted item */\r\n onSelect?: (index: number) => void\r\n /** Called when the dropdown should open (Space/Enter/ArrowDown when closed) */\r\n onOpen?: () => void\r\n /** Whether to handle open keys (Space/Enter/ArrowDown) when closed */\r\n handleOpenKeys?: boolean\r\n}\r\n\r\nexport function useDropdown(\r\n triggerRef: Ref<HTMLElement | null | undefined>,\r\n dropdownRef: Ref<HTMLElement | null | undefined>,\r\n options: UseDropdownOptions = {}\r\n): UseDropdownReturn {\r\n const { teleport = true, align = 'left', placement = 'bottom-start', gap = 8, onOpen, onClose } = options\r\n\r\n const isOpen = ref(false)\r\n const highlightedIndex = ref(-1)\r\n const dropdownPosition = ref({ top: 0, left: 0, right: 0, width: 0 })\r\n\r\n const updatePosition = () => {\r\n if (!triggerRef.value || !teleport) return\r\n const rect = triggerRef.value.getBoundingClientRect()\r\n\r\n // Calculate position based on placement\r\n const [side, alignment = 'start'] = placement.split('-') as [string, string?]\r\n\r\n let top = 0\r\n let left = 0\r\n let right = 0\r\n\r\n // Vertical positioning\r\n switch (side) {\r\n case 'bottom':\r\n top = rect.bottom + window.scrollY + gap\r\n break\r\n case 'top':\r\n top = rect.top + window.scrollY - gap\r\n break\r\n case 'left':\r\n case 'right':\r\n // Vertical alignment for horizontal placements\r\n if (alignment === 'start') {\r\n top = rect.top + window.scrollY\r\n } else if (alignment === 'end') {\r\n top = rect.bottom + window.scrollY\r\n } else {\r\n // center\r\n top = rect.top + window.scrollY + rect.height / 2\r\n }\r\n break\r\n }\r\n\r\n // Horizontal positioning\r\n switch (side) {\r\n case 'left':\r\n right = window.innerWidth - rect.left + gap\r\n left = 0\r\n break\r\n case 'right':\r\n left = rect.right + window.scrollX + gap\r\n right = 0\r\n break\r\n case 'top':\r\n case 'bottom':\r\n // Horizontal alignment for vertical placements\r\n if (alignment === 'start' || align === 'left') {\r\n left = rect.left + window.scrollX\r\n right = 0\r\n } else if (alignment === 'end' || align === 'right') {\r\n right = window.innerWidth - rect.right - window.scrollX\r\n left = 0\r\n } else {\r\n // center\r\n left = rect.left + window.scrollX + rect.width / 2\r\n right = 0\r\n }\r\n break\r\n }\r\n\r\n dropdownPosition.value = {\r\n top,\r\n left,\r\n right,\r\n width: rect.width,\r\n }\r\n }\r\n\r\n const open = () => {\r\n isOpen.value = true\r\n nextTick(updatePosition)\r\n onOpen?.()\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n highlightedIndex.value = -1\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n const handleClickOutside = (event: MouseEvent) => {\r\n const target = event.target as Node\r\n const isInsideTrigger = triggerRef.value?.contains(target)\r\n const isInsideDropdown = dropdownRef.value?.contains(target)\r\n if (!isInsideTrigger && !isInsideDropdown) {\r\n close()\r\n }\r\n }\r\n\r\n const scrollToHighlighted = (dropdownEl: HTMLElement | null) => {\r\n nextTick(() => {\r\n if (dropdownEl) {\r\n const highlighted = dropdownEl.querySelector(\r\n `[data-index=\"${highlightedIndex.value}\"]`\r\n ) as HTMLElement\r\n if (highlighted) {\r\n highlighted.scrollIntoView({ block: 'nearest' })\r\n }\r\n }\r\n })\r\n }\r\n\r\n const handleKeydown = (event: KeyboardEvent, navOptions: KeyboardNavigationOptions) => {\r\n const { itemCount, onSelect, onOpen: onOpenNav, handleOpenKeys = false } = navOptions\r\n\r\n if (!isOpen.value) {\r\n if (handleOpenKeys && (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown')) {\r\n event.preventDefault()\r\n onOpenNav?.()\r\n open()\r\n }\r\n return\r\n }\r\n\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, itemCount - 1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n if (highlightedIndex.value >= 0) {\r\n onSelect?.(highlightedIndex.value)\r\n }\r\n break\r\n case 'Escape':\r\n event.preventDefault()\r\n close()\r\n break\r\n case 'Tab':\r\n close()\r\n break\r\n }\r\n }\r\n\r\n const dropdownStyle = computed(() => {\r\n if (!teleport) return {} as Record<string, string>\r\n\r\n const [side] = placement.split('-')\r\n const isHorizontal = side === 'left' || side === 'right'\r\n\r\n const style: Record<string, string> = {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n }\r\n\r\n // For horizontal placements, don't set width\r\n if (isHorizontal) {\r\n if (side === 'left') {\r\n style.right = `${dropdownPosition.value.right}px`\r\n } else {\r\n style.left = `${dropdownPosition.value.left}px`\r\n }\r\n } else {\r\n // Vertical placements (bottom/top)\r\n style.left = dropdownPosition.value.right ? 'auto' : `${dropdownPosition.value.left}px`\r\n style.right = dropdownPosition.value.right ? `${dropdownPosition.value.right}px` : 'auto'\r\n style.width = `${dropdownPosition.value.width}px`\r\n }\r\n\r\n return style\r\n })\r\n\r\n // Event listener management\r\n watch(isOpen, (newValue) => {\r\n if (newValue) {\r\n document.addEventListener('click', handleClickOutside)\r\n window.addEventListener('scroll', updatePosition, true)\r\n window.addEventListener('resize', updatePosition)\r\n } else {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n })\r\n\r\n return {\r\n isOpen,\r\n highlightedIndex,\r\n dropdownPosition,\r\n dropdownStyle,\r\n open,\r\n close,\r\n toggle,\r\n updatePosition,\r\n handleKeydown,\r\n scrollToHighlighted,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useDropdown-XITCE_SM.js","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type DropdownPlacement =\r\n | 'bottom'\r\n | 'bottom-start'\r\n | 'bottom-end'\r\n | 'top'\r\n | 'top-start'\r\n | 'top-end'\r\n | 'right'\r\n | 'right-start'\r\n | 'right-end'\r\n | 'left'\r\n | 'left-start'\r\n | 'left-end'\r\n\r\nexport interface UseDropdownOptions {\r\n /** Whether teleport is enabled (affects position calculation) */\r\n teleport?: boolean\r\n /** Alignment for position calculation (deprecated, use placement instead) */\r\n align?: 'left' | 'right'\r\n /** Placement of the dropdown relative to the trigger */\r\n placement?: DropdownPlacement\r\n /** Gap between trigger and dropdown in pixels */\r\n gap?: number\r\n /** Callback when dropdown opens */\r\n onOpen?: () => void\r\n /** Callback when dropdown closes */\r\n onClose?: () => void\r\n}\r\n\r\nexport interface UseDropdownReturn {\r\n /** Whether the dropdown is currently open */\r\n isOpen: Ref<boolean>\r\n /** Current highlighted index for keyboard navigation */\r\n highlightedIndex: Ref<number>\r\n /** Calculated position for teleported dropdown */\r\n dropdownPosition: Ref<{ top: number; left: number; right: number; width: number }>\r\n /** Computed style object for teleported dropdown */\r\n dropdownStyle: ComputedRef<Record<string, string>>\r\n /** Open the dropdown */\r\n open: () => void\r\n /** Close the dropdown */\r\n close: () => void\r\n /** Toggle the dropdown */\r\n toggle: () => void\r\n /** Update position (call after DOM changes) */\r\n updatePosition: () => void\r\n /** Handle keyboard navigation */\r\n handleKeydown: (event: KeyboardEvent, options: KeyboardNavigationOptions) => void\r\n /** Scroll to highlighted item */\r\n scrollToHighlighted: (dropdownEl: HTMLElement | null) => void\r\n}\r\n\r\nexport interface KeyboardNavigationOptions {\r\n /** Total number of items to navigate */\r\n itemCount: number\r\n /** Called when Enter is pressed on a highlighted item */\r\n onSelect?: (index: number) => void\r\n /** Called when the dropdown should open (Space/Enter/ArrowDown when closed) */\r\n onOpen?: () => void\r\n /** Whether to handle open keys (Space/Enter/ArrowDown) when closed */\r\n handleOpenKeys?: boolean\r\n}\r\n\r\nexport function useDropdown(\r\n triggerRef: Ref<HTMLElement | null | undefined>,\r\n dropdownRef: Ref<HTMLElement | null | undefined>,\r\n options: UseDropdownOptions = {}\r\n): UseDropdownReturn {\r\n const { teleport = true, align = 'left', placement = 'bottom-start', gap = 8, onOpen, onClose } = options\r\n\r\n const isOpen = ref(false)\r\n const highlightedIndex = ref(-1)\r\n const dropdownPosition = ref({ top: 0, left: 0, right: 0, width: 0 })\r\n\r\n const updatePosition = () => {\r\n if (!triggerRef.value || !teleport) return\r\n const rect = triggerRef.value.getBoundingClientRect()\r\n\r\n // Calculate position based on placement\r\n const [side, alignment = 'start'] = placement.split('-') as [string, string?]\r\n\r\n let top = 0\r\n let left = 0\r\n let right = 0\r\n\r\n // Vertical positioning\r\n switch (side) {\r\n case 'bottom':\r\n top = rect.bottom + window.scrollY + gap\r\n break\r\n case 'top':\r\n top = rect.top + window.scrollY - gap\r\n break\r\n case 'left':\r\n case 'right':\r\n // Vertical alignment for horizontal placements\r\n if (alignment === 'start') {\r\n top = rect.top + window.scrollY\r\n } else if (alignment === 'end') {\r\n top = rect.bottom + window.scrollY\r\n } else {\r\n // center\r\n top = rect.top + window.scrollY + rect.height / 2\r\n }\r\n break\r\n }\r\n\r\n // Horizontal positioning\r\n switch (side) {\r\n case 'left':\r\n right = window.innerWidth - rect.left + gap\r\n left = 0\r\n break\r\n case 'right':\r\n left = rect.right + window.scrollX + gap\r\n right = 0\r\n break\r\n case 'top':\r\n case 'bottom':\r\n // Horizontal alignment for vertical placements\r\n if (alignment === 'start' || align === 'left') {\r\n left = rect.left + window.scrollX\r\n right = 0\r\n } else if (alignment === 'end' || align === 'right') {\r\n right = window.innerWidth - rect.right - window.scrollX\r\n left = 0\r\n } else {\r\n // center\r\n left = rect.left + window.scrollX + rect.width / 2\r\n right = 0\r\n }\r\n break\r\n }\r\n\r\n dropdownPosition.value = {\r\n top,\r\n left,\r\n right,\r\n width: rect.width,\r\n }\r\n }\r\n\r\n const open = () => {\r\n isOpen.value = true\r\n nextTick(updatePosition)\r\n onOpen?.()\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n highlightedIndex.value = -1\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n const handleClickOutside = (event: MouseEvent) => {\r\n const target = event.target as Node\r\n const isInsideTrigger = triggerRef.value?.contains(target)\r\n const isInsideDropdown = dropdownRef.value?.contains(target)\r\n if (!isInsideTrigger && !isInsideDropdown) {\r\n close()\r\n }\r\n }\r\n\r\n const scrollToHighlighted = (dropdownEl: HTMLElement | null) => {\r\n nextTick(() => {\r\n if (dropdownEl) {\r\n const highlighted = dropdownEl.querySelector(\r\n `[data-index=\"${highlightedIndex.value}\"]`\r\n ) as HTMLElement\r\n if (highlighted) {\r\n highlighted.scrollIntoView({ block: 'nearest' })\r\n }\r\n }\r\n })\r\n }\r\n\r\n const handleKeydown = (event: KeyboardEvent, navOptions: KeyboardNavigationOptions) => {\r\n const { itemCount, onSelect, onOpen: onOpenNav, handleOpenKeys = false } = navOptions\r\n\r\n if (!isOpen.value) {\r\n if (handleOpenKeys && (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown')) {\r\n event.preventDefault()\r\n onOpenNav?.()\r\n open()\r\n }\r\n return\r\n }\r\n\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, itemCount - 1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n if (highlightedIndex.value >= 0) {\r\n onSelect?.(highlightedIndex.value)\r\n }\r\n break\r\n case 'Escape':\r\n event.preventDefault()\r\n close()\r\n break\r\n case 'Tab':\r\n close()\r\n break\r\n }\r\n }\r\n\r\n const dropdownStyle = computed(() => {\r\n if (!teleport) return {} as Record<string, string>\r\n\r\n const [side, alignment] = placement.split('-')\r\n const isHorizontal = side === 'left' || side === 'right'\r\n const isRightAligned = align === 'right' || alignment === 'end'\r\n\r\n const style: Record<string, string> = {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n }\r\n\r\n // For horizontal placements, don't set width\r\n if (isHorizontal) {\r\n if (side === 'left') {\r\n style.right = `${dropdownPosition.value.right}px`\r\n } else {\r\n style.left = `${dropdownPosition.value.left}px`\r\n }\r\n } else {\r\n // Vertical placements (bottom/top)\r\n style.left = isRightAligned ? 'auto' : `${dropdownPosition.value.left}px`\r\n style.right = isRightAligned ? `${dropdownPosition.value.right}px` : 'auto'\r\n style.width = `${dropdownPosition.value.width}px`\r\n }\r\n\r\n return style\r\n })\r\n\r\n // Event listener management\r\n watch(isOpen, (newValue) => {\r\n if (newValue) {\r\n document.addEventListener('click', handleClickOutside)\r\n window.addEventListener('scroll', updatePosition, true)\r\n window.addEventListener('resize', updatePosition)\r\n } else {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n })\r\n\r\n return {\r\n isOpen,\r\n highlightedIndex,\r\n dropdownPosition,\r\n dropdownStyle,\r\n open,\r\n close,\r\n toggle,\r\n updatePosition,\r\n handleKeydown,\r\n scrollToHighlighted,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
const unwrap = (value) => {
|
|
3
|
+
return typeof value === "object" && value !== null && "value" in value ? value.value : value;
|
|
4
|
+
};
|
|
5
|
+
function useInputStyles(options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
disabled = false,
|
|
8
|
+
invalid = false,
|
|
9
|
+
focused = false,
|
|
10
|
+
size = "md",
|
|
11
|
+
hasLeftIcon = false,
|
|
12
|
+
hasRightIcon = false,
|
|
13
|
+
hasValue = true
|
|
14
|
+
} = options;
|
|
15
|
+
const baseClasses = [
|
|
16
|
+
"w-full rounded-md border text-sm transition-all",
|
|
17
|
+
"focus:outline-none"
|
|
18
|
+
];
|
|
19
|
+
const stateClasses = computed(() => {
|
|
20
|
+
const isDisabled = unwrap(disabled);
|
|
21
|
+
const isInvalid = unwrap(invalid);
|
|
22
|
+
const isFocused = unwrap(focused);
|
|
23
|
+
if (isDisabled) {
|
|
24
|
+
return [
|
|
25
|
+
"cursor-not-allowed",
|
|
26
|
+
"border-gray-200 bg-gray-50 text-gray-500",
|
|
27
|
+
"dark:border-gray-800 dark:bg-gray-950 dark:text-gray-500"
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
if (isInvalid) {
|
|
31
|
+
return [
|
|
32
|
+
"border-red-500 bg-white text-gray-800",
|
|
33
|
+
"focus:border-red-500 focus:ring-2 focus:ring-red-500/20",
|
|
34
|
+
"dark:border-red-500 dark:bg-gray-900 dark:text-gray-200"
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
if (isFocused) {
|
|
38
|
+
return [
|
|
39
|
+
"border-primary ring-2 ring-primary/20 bg-white text-gray-800",
|
|
40
|
+
"dark:bg-gray-900 dark:text-gray-200"
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
return [
|
|
44
|
+
"border-gray-300 bg-white text-gray-800",
|
|
45
|
+
"hover:border-gray-400",
|
|
46
|
+
"focus:border-primary focus:ring-2 focus:ring-primary/20",
|
|
47
|
+
"dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:border-gray-600"
|
|
48
|
+
];
|
|
49
|
+
});
|
|
50
|
+
const sizeClasses = computed(() => {
|
|
51
|
+
const currentSize = unwrap(size);
|
|
52
|
+
switch (currentSize) {
|
|
53
|
+
case "sm":
|
|
54
|
+
return ["py-1.5", "text-xs"];
|
|
55
|
+
case "lg":
|
|
56
|
+
return ["py-3", "text-base"];
|
|
57
|
+
default:
|
|
58
|
+
return ["py-2", "text-sm"];
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const paddingClasses = computed(() => {
|
|
62
|
+
const hasLeft = unwrap(hasLeftIcon);
|
|
63
|
+
const hasRight = unwrap(hasRightIcon);
|
|
64
|
+
return [
|
|
65
|
+
hasLeft ? "pl-10" : "px-3",
|
|
66
|
+
hasRight ? "pr-10" : "px-3"
|
|
67
|
+
];
|
|
68
|
+
});
|
|
69
|
+
const textColorClass = computed(() => {
|
|
70
|
+
const hasVal = unwrap(hasValue);
|
|
71
|
+
if (!hasVal) {
|
|
72
|
+
return ["text-gray-400", "dark:text-gray-500"];
|
|
73
|
+
}
|
|
74
|
+
return ["text-gray-800", "dark:text-gray-200"];
|
|
75
|
+
});
|
|
76
|
+
const inputClass = computed(() => [
|
|
77
|
+
...baseClasses,
|
|
78
|
+
...stateClasses.value,
|
|
79
|
+
...sizeClasses.value,
|
|
80
|
+
...paddingClasses.value,
|
|
81
|
+
"placeholder-gray-400 dark:placeholder-gray-500"
|
|
82
|
+
]);
|
|
83
|
+
const triggerClass = computed(() => [
|
|
84
|
+
"flex items-center justify-between gap-2 text-left",
|
|
85
|
+
...baseClasses,
|
|
86
|
+
...stateClasses.value,
|
|
87
|
+
...sizeClasses.value,
|
|
88
|
+
"px-3",
|
|
89
|
+
// triggers always have px-3
|
|
90
|
+
...textColorClass.value
|
|
91
|
+
]);
|
|
92
|
+
const wrapperClass = computed(() => {
|
|
93
|
+
const isDisabled = unwrap(disabled);
|
|
94
|
+
const currentSize = unwrap(size);
|
|
95
|
+
const sizeWrapperClasses = {
|
|
96
|
+
sm: "min-h-9 py-1.5 px-2 gap-1",
|
|
97
|
+
md: "min-h-11 py-2 px-3 gap-1.5",
|
|
98
|
+
lg: "min-h-13 py-2.5 px-4 gap-2"
|
|
99
|
+
}[currentSize];
|
|
100
|
+
if (isDisabled) {
|
|
101
|
+
return [
|
|
102
|
+
"flex flex-wrap items-center rounded-md border transition-all cursor-not-allowed",
|
|
103
|
+
"border-gray-200 bg-gray-50",
|
|
104
|
+
"dark:border-gray-800 dark:bg-gray-950",
|
|
105
|
+
sizeWrapperClasses
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
return [
|
|
109
|
+
"flex flex-wrap items-center rounded-md border transition-all cursor-text",
|
|
110
|
+
"border-gray-300 bg-white hover:border-gray-400",
|
|
111
|
+
"focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20",
|
|
112
|
+
"dark:border-gray-700 dark:bg-gray-900 dark:hover:border-gray-600",
|
|
113
|
+
sizeWrapperClasses
|
|
114
|
+
];
|
|
115
|
+
});
|
|
116
|
+
const iconClass = computed(() => ["size-4", "text-gray-400"]);
|
|
117
|
+
return {
|
|
118
|
+
inputClass,
|
|
119
|
+
triggerClass,
|
|
120
|
+
wrapperClass,
|
|
121
|
+
iconClass
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
useInputStyles as u
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=useInputStyles-BFTJdXHL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInputStyles-BFTJdXHL.js","sources":["../src/composables/useInputStyles.ts"],"sourcesContent":["import { computed, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface UseInputStylesOptions {\r\n /** Disabled state */\r\n disabled?: Ref<boolean> | boolean\r\n /** Invalid/error state */\r\n invalid?: Ref<boolean> | boolean\r\n /** Focused state (for trigger buttons like select) */\r\n focused?: Ref<boolean> | boolean\r\n /** Input size */\r\n size?: Ref<InputSize> | InputSize\r\n /** Has left icon/content */\r\n hasLeftIcon?: Ref<boolean> | boolean\r\n /** Has right icon/content */\r\n hasRightIcon?: Ref<boolean> | boolean\r\n /** Has value selected (for placeholder color) */\r\n hasValue?: Ref<boolean> | boolean\r\n}\r\n\r\nexport interface UseInputStylesReturn {\r\n /** Base input classes (for <input>, <textarea>) */\r\n inputClass: ComputedRef<string[]>\r\n /** Trigger button classes (for select, datepicker, combobox triggers) */\r\n triggerClass: ComputedRef<string[]>\r\n /** Container/wrapper classes */\r\n wrapperClass: ComputedRef<string[]>\r\n /** Icon classes */\r\n iconClass: ComputedRef<string[]>\r\n}\r\n\r\n// Helper to unwrap ref or return value directly\r\nconst unwrap = <T>(value: Ref<T> | T): T => {\r\n return typeof value === 'object' && value !== null && 'value' in value\r\n ? (value as Ref<T>).value\r\n : value as T\r\n}\r\n\r\n/**\r\n * Composable for consistent input styling across all form components\r\n */\r\nexport function useInputStyles(options: UseInputStylesOptions = {}): UseInputStylesReturn {\r\n const {\r\n disabled = false,\r\n invalid = false,\r\n focused = false,\r\n size = 'md',\r\n hasLeftIcon = false,\r\n hasRightIcon = false,\r\n hasValue = true,\r\n } = options\r\n\r\n // Base classes shared by inputs and triggers\r\n const baseClasses = [\r\n 'w-full rounded-md border text-sm transition-all',\r\n 'focus:outline-none',\r\n ]\r\n\r\n // State classes\r\n const stateClasses = computed(() => {\r\n const isDisabled = unwrap(disabled)\r\n const isInvalid = unwrap(invalid)\r\n const isFocused = unwrap(focused)\r\n\r\n if (isDisabled) {\r\n return [\r\n 'cursor-not-allowed',\r\n 'border-gray-200 bg-gray-50 text-gray-500',\r\n 'dark:border-gray-800 dark:bg-gray-950 dark:text-gray-500',\r\n ]\r\n }\r\n\r\n if (isInvalid) {\r\n return [\r\n 'border-red-500 bg-white text-gray-800',\r\n 'focus:border-red-500 focus:ring-2 focus:ring-red-500/20',\r\n 'dark:border-red-500 dark:bg-gray-900 dark:text-gray-200',\r\n ]\r\n }\r\n\r\n if (isFocused) {\r\n return [\r\n 'border-primary ring-2 ring-primary/20 bg-white text-gray-800',\r\n 'dark:bg-gray-900 dark:text-gray-200',\r\n ]\r\n }\r\n\r\n return [\r\n 'border-gray-300 bg-white text-gray-800',\r\n 'hover:border-gray-400',\r\n 'focus:border-primary focus:ring-2 focus:ring-primary/20',\r\n 'dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:border-gray-600',\r\n ]\r\n })\r\n\r\n // Size classes\r\n const sizeClasses = computed(() => {\r\n const currentSize = unwrap(size)\r\n switch (currentSize) {\r\n case 'sm':\r\n return ['py-1.5', 'text-xs']\r\n case 'lg':\r\n return ['py-3', 'text-base']\r\n default:\r\n return ['py-2', 'text-sm']\r\n }\r\n })\r\n\r\n // Padding classes based on icons\r\n const paddingClasses = computed(() => {\r\n const hasLeft = unwrap(hasLeftIcon)\r\n const hasRight = unwrap(hasRightIcon)\r\n\r\n return [\r\n hasLeft ? 'pl-10' : 'px-3',\r\n hasRight ? 'pr-10' : 'px-3',\r\n ]\r\n })\r\n\r\n // Text color for placeholder state (select, datepicker)\r\n const textColorClass = computed(() => {\r\n const hasVal = unwrap(hasValue)\r\n if (!hasVal) {\r\n return ['text-gray-400', 'dark:text-gray-500']\r\n }\r\n return ['text-gray-800', 'dark:text-gray-200']\r\n })\r\n\r\n // Full input classes (for <input>, <textarea>)\r\n const inputClass = computed(() => [\r\n ...baseClasses,\r\n ...stateClasses.value,\r\n ...sizeClasses.value,\r\n ...paddingClasses.value,\r\n 'placeholder-gray-400 dark:placeholder-gray-500',\r\n ])\r\n\r\n // Trigger button classes (for select, datepicker, combobox)\r\n const triggerClass = computed(() => [\r\n 'flex items-center justify-between gap-2 text-left',\r\n ...baseClasses,\r\n ...stateClasses.value,\r\n ...sizeClasses.value,\r\n 'px-3', // triggers always have px-3\r\n ...textColorClass.value,\r\n ])\r\n\r\n // Wrapper/container classes (for TagsInput, etc.)\r\n const wrapperClass = computed(() => {\r\n const isDisabled = unwrap(disabled)\r\n const currentSize = unwrap(size)\r\n\r\n const sizeWrapperClasses = {\r\n sm: 'min-h-9 py-1.5 px-2 gap-1',\r\n md: 'min-h-11 py-2 px-3 gap-1.5',\r\n lg: 'min-h-13 py-2.5 px-4 gap-2',\r\n }[currentSize]\r\n\r\n if (isDisabled) {\r\n return [\r\n 'flex flex-wrap items-center rounded-md border transition-all cursor-not-allowed',\r\n 'border-gray-200 bg-gray-50',\r\n 'dark:border-gray-800 dark:bg-gray-950',\r\n sizeWrapperClasses,\r\n ]\r\n }\r\n\r\n return [\r\n 'flex flex-wrap items-center rounded-md border transition-all cursor-text',\r\n 'border-gray-300 bg-white hover:border-gray-400',\r\n 'focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20',\r\n 'dark:border-gray-700 dark:bg-gray-900 dark:hover:border-gray-600',\r\n sizeWrapperClasses,\r\n ]\r\n })\r\n\r\n // Icon classes\r\n const iconClass = computed(() => ['size-4', 'text-gray-400'])\r\n\r\n return {\r\n inputClass,\r\n triggerClass,\r\n wrapperClass,\r\n iconClass,\r\n }\r\n}\r\n"],"names":[],"mappings":";AAiCA,MAAM,SAAS,CAAI,UAAyB;AAC1C,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,WAAW,QAC5D,MAAiB,QAClB;AACN;AAKO,SAAS,eAAe,UAAiC,IAA0B;AACxF,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,IACP,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW;AAAA,EAAA,IACT;AAGJ,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,EAAA;AAIF,QAAM,eAAe,SAAS,MAAM;AAClC,UAAM,aAAa,OAAO,QAAQ;AAClC,UAAM,YAAY,OAAO,OAAO;AAChC,UAAM,YAAY,OAAO,OAAO;AAEhC,QAAI,YAAY;AACd,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,WAAW;AACb,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,WAAW;AACb,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,CAAC;AAGD,QAAM,cAAc,SAAS,MAAM;AACjC,UAAM,cAAc,OAAO,IAAI;AAC/B,YAAQ,aAAA;AAAA,MACN,KAAK;AACH,eAAO,CAAC,UAAU,SAAS;AAAA,MAC7B,KAAK;AACH,eAAO,CAAC,QAAQ,WAAW;AAAA,MAC7B;AACE,eAAO,CAAC,QAAQ,SAAS;AAAA,IAAA;AAAA,EAE/B,CAAC;AAGD,QAAM,iBAAiB,SAAS,MAAM;AACpC,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,WAAW,OAAO,YAAY;AAEpC,WAAO;AAAA,MACL,UAAU,UAAU;AAAA,MACpB,WAAW,UAAU;AAAA,IAAA;AAAA,EAEzB,CAAC;AAGD,QAAM,iBAAiB,SAAS,MAAM;AACpC,UAAM,SAAS,OAAO,QAAQ;AAC9B,QAAI,CAAC,QAAQ;AACX,aAAO,CAAC,iBAAiB,oBAAoB;AAAA,IAC/C;AACA,WAAO,CAAC,iBAAiB,oBAAoB;AAAA,EAC/C,CAAC;AAGD,QAAM,aAAa,SAAS,MAAM;AAAA,IAChC,GAAG;AAAA,IACH,GAAG,aAAa;AAAA,IAChB,GAAG,YAAY;AAAA,IACf,GAAG,eAAe;AAAA,IAClB;AAAA,EAAA,CACD;AAGD,QAAM,eAAe,SAAS,MAAM;AAAA,IAClC;AAAA,IACA,GAAG;AAAA,IACH,GAAG,aAAa;AAAA,IAChB,GAAG,YAAY;AAAA,IACf;AAAA;AAAA,IACA,GAAG,eAAe;AAAA,EAAA,CACnB;AAGD,QAAM,eAAe,SAAS,MAAM;AAClC,UAAM,aAAa,OAAO,QAAQ;AAClC,UAAM,cAAc,OAAO,IAAI;AAE/B,UAAM,qBAAqB;AAAA,MACzB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IAAA,EACJ,WAAW;AAEb,QAAI,YAAY;AACd,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,CAAC;AAGD,QAAM,YAAY,SAAS,MAAM,CAAC,UAAU,eAAe,CAAC;AAE5D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const vue = require("vue");
|
|
3
|
+
const unwrap = (value) => {
|
|
4
|
+
return typeof value === "object" && value !== null && "value" in value ? value.value : value;
|
|
5
|
+
};
|
|
6
|
+
function useInputStyles(options = {}) {
|
|
7
|
+
const {
|
|
8
|
+
disabled = false,
|
|
9
|
+
invalid = false,
|
|
10
|
+
focused = false,
|
|
11
|
+
size = "md",
|
|
12
|
+
hasLeftIcon = false,
|
|
13
|
+
hasRightIcon = false,
|
|
14
|
+
hasValue = true
|
|
15
|
+
} = options;
|
|
16
|
+
const baseClasses = [
|
|
17
|
+
"w-full rounded-md border text-sm transition-all",
|
|
18
|
+
"focus:outline-none"
|
|
19
|
+
];
|
|
20
|
+
const stateClasses = vue.computed(() => {
|
|
21
|
+
const isDisabled = unwrap(disabled);
|
|
22
|
+
const isInvalid = unwrap(invalid);
|
|
23
|
+
const isFocused = unwrap(focused);
|
|
24
|
+
if (isDisabled) {
|
|
25
|
+
return [
|
|
26
|
+
"cursor-not-allowed",
|
|
27
|
+
"border-gray-200 bg-gray-50 text-gray-500",
|
|
28
|
+
"dark:border-gray-800 dark:bg-gray-950 dark:text-gray-500"
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
if (isInvalid) {
|
|
32
|
+
return [
|
|
33
|
+
"border-red-500 bg-white text-gray-800",
|
|
34
|
+
"focus:border-red-500 focus:ring-2 focus:ring-red-500/20",
|
|
35
|
+
"dark:border-red-500 dark:bg-gray-900 dark:text-gray-200"
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
if (isFocused) {
|
|
39
|
+
return [
|
|
40
|
+
"border-primary ring-2 ring-primary/20 bg-white text-gray-800",
|
|
41
|
+
"dark:bg-gray-900 dark:text-gray-200"
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
"border-gray-300 bg-white text-gray-800",
|
|
46
|
+
"hover:border-gray-400",
|
|
47
|
+
"focus:border-primary focus:ring-2 focus:ring-primary/20",
|
|
48
|
+
"dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:border-gray-600"
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
const sizeClasses = vue.computed(() => {
|
|
52
|
+
const currentSize = unwrap(size);
|
|
53
|
+
switch (currentSize) {
|
|
54
|
+
case "sm":
|
|
55
|
+
return ["py-1.5", "text-xs"];
|
|
56
|
+
case "lg":
|
|
57
|
+
return ["py-3", "text-base"];
|
|
58
|
+
default:
|
|
59
|
+
return ["py-2", "text-sm"];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const paddingClasses = vue.computed(() => {
|
|
63
|
+
const hasLeft = unwrap(hasLeftIcon);
|
|
64
|
+
const hasRight = unwrap(hasRightIcon);
|
|
65
|
+
return [
|
|
66
|
+
hasLeft ? "pl-10" : "px-3",
|
|
67
|
+
hasRight ? "pr-10" : "px-3"
|
|
68
|
+
];
|
|
69
|
+
});
|
|
70
|
+
const textColorClass = vue.computed(() => {
|
|
71
|
+
const hasVal = unwrap(hasValue);
|
|
72
|
+
if (!hasVal) {
|
|
73
|
+
return ["text-gray-400", "dark:text-gray-500"];
|
|
74
|
+
}
|
|
75
|
+
return ["text-gray-800", "dark:text-gray-200"];
|
|
76
|
+
});
|
|
77
|
+
const inputClass = vue.computed(() => [
|
|
78
|
+
...baseClasses,
|
|
79
|
+
...stateClasses.value,
|
|
80
|
+
...sizeClasses.value,
|
|
81
|
+
...paddingClasses.value,
|
|
82
|
+
"placeholder-gray-400 dark:placeholder-gray-500"
|
|
83
|
+
]);
|
|
84
|
+
const triggerClass = vue.computed(() => [
|
|
85
|
+
"flex items-center justify-between gap-2 text-left",
|
|
86
|
+
...baseClasses,
|
|
87
|
+
...stateClasses.value,
|
|
88
|
+
...sizeClasses.value,
|
|
89
|
+
"px-3",
|
|
90
|
+
// triggers always have px-3
|
|
91
|
+
...textColorClass.value
|
|
92
|
+
]);
|
|
93
|
+
const wrapperClass = vue.computed(() => {
|
|
94
|
+
const isDisabled = unwrap(disabled);
|
|
95
|
+
const currentSize = unwrap(size);
|
|
96
|
+
const sizeWrapperClasses = {
|
|
97
|
+
sm: "min-h-9 py-1.5 px-2 gap-1",
|
|
98
|
+
md: "min-h-11 py-2 px-3 gap-1.5",
|
|
99
|
+
lg: "min-h-13 py-2.5 px-4 gap-2"
|
|
100
|
+
}[currentSize];
|
|
101
|
+
if (isDisabled) {
|
|
102
|
+
return [
|
|
103
|
+
"flex flex-wrap items-center rounded-md border transition-all cursor-not-allowed",
|
|
104
|
+
"border-gray-200 bg-gray-50",
|
|
105
|
+
"dark:border-gray-800 dark:bg-gray-950",
|
|
106
|
+
sizeWrapperClasses
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
"flex flex-wrap items-center rounded-md border transition-all cursor-text",
|
|
111
|
+
"border-gray-300 bg-white hover:border-gray-400",
|
|
112
|
+
"focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20",
|
|
113
|
+
"dark:border-gray-700 dark:bg-gray-900 dark:hover:border-gray-600",
|
|
114
|
+
sizeWrapperClasses
|
|
115
|
+
];
|
|
116
|
+
});
|
|
117
|
+
const iconClass = vue.computed(() => ["size-4", "text-gray-400"]);
|
|
118
|
+
return {
|
|
119
|
+
inputClass,
|
|
120
|
+
triggerClass,
|
|
121
|
+
wrapperClass,
|
|
122
|
+
iconClass
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
exports.useInputStyles = useInputStyles;
|
|
126
|
+
//# sourceMappingURL=useInputStyles-DMfvW6N5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useInputStyles-DMfvW6N5.cjs","sources":["../src/composables/useInputStyles.ts"],"sourcesContent":["import { computed, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface UseInputStylesOptions {\r\n /** Disabled state */\r\n disabled?: Ref<boolean> | boolean\r\n /** Invalid/error state */\r\n invalid?: Ref<boolean> | boolean\r\n /** Focused state (for trigger buttons like select) */\r\n focused?: Ref<boolean> | boolean\r\n /** Input size */\r\n size?: Ref<InputSize> | InputSize\r\n /** Has left icon/content */\r\n hasLeftIcon?: Ref<boolean> | boolean\r\n /** Has right icon/content */\r\n hasRightIcon?: Ref<boolean> | boolean\r\n /** Has value selected (for placeholder color) */\r\n hasValue?: Ref<boolean> | boolean\r\n}\r\n\r\nexport interface UseInputStylesReturn {\r\n /** Base input classes (for <input>, <textarea>) */\r\n inputClass: ComputedRef<string[]>\r\n /** Trigger button classes (for select, datepicker, combobox triggers) */\r\n triggerClass: ComputedRef<string[]>\r\n /** Container/wrapper classes */\r\n wrapperClass: ComputedRef<string[]>\r\n /** Icon classes */\r\n iconClass: ComputedRef<string[]>\r\n}\r\n\r\n// Helper to unwrap ref or return value directly\r\nconst unwrap = <T>(value: Ref<T> | T): T => {\r\n return typeof value === 'object' && value !== null && 'value' in value\r\n ? (value as Ref<T>).value\r\n : value as T\r\n}\r\n\r\n/**\r\n * Composable for consistent input styling across all form components\r\n */\r\nexport function useInputStyles(options: UseInputStylesOptions = {}): UseInputStylesReturn {\r\n const {\r\n disabled = false,\r\n invalid = false,\r\n focused = false,\r\n size = 'md',\r\n hasLeftIcon = false,\r\n hasRightIcon = false,\r\n hasValue = true,\r\n } = options\r\n\r\n // Base classes shared by inputs and triggers\r\n const baseClasses = [\r\n 'w-full rounded-md border text-sm transition-all',\r\n 'focus:outline-none',\r\n ]\r\n\r\n // State classes\r\n const stateClasses = computed(() => {\r\n const isDisabled = unwrap(disabled)\r\n const isInvalid = unwrap(invalid)\r\n const isFocused = unwrap(focused)\r\n\r\n if (isDisabled) {\r\n return [\r\n 'cursor-not-allowed',\r\n 'border-gray-200 bg-gray-50 text-gray-500',\r\n 'dark:border-gray-800 dark:bg-gray-950 dark:text-gray-500',\r\n ]\r\n }\r\n\r\n if (isInvalid) {\r\n return [\r\n 'border-red-500 bg-white text-gray-800',\r\n 'focus:border-red-500 focus:ring-2 focus:ring-red-500/20',\r\n 'dark:border-red-500 dark:bg-gray-900 dark:text-gray-200',\r\n ]\r\n }\r\n\r\n if (isFocused) {\r\n return [\r\n 'border-primary ring-2 ring-primary/20 bg-white text-gray-800',\r\n 'dark:bg-gray-900 dark:text-gray-200',\r\n ]\r\n }\r\n\r\n return [\r\n 'border-gray-300 bg-white text-gray-800',\r\n 'hover:border-gray-400',\r\n 'focus:border-primary focus:ring-2 focus:ring-primary/20',\r\n 'dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:border-gray-600',\r\n ]\r\n })\r\n\r\n // Size classes\r\n const sizeClasses = computed(() => {\r\n const currentSize = unwrap(size)\r\n switch (currentSize) {\r\n case 'sm':\r\n return ['py-1.5', 'text-xs']\r\n case 'lg':\r\n return ['py-3', 'text-base']\r\n default:\r\n return ['py-2', 'text-sm']\r\n }\r\n })\r\n\r\n // Padding classes based on icons\r\n const paddingClasses = computed(() => {\r\n const hasLeft = unwrap(hasLeftIcon)\r\n const hasRight = unwrap(hasRightIcon)\r\n\r\n return [\r\n hasLeft ? 'pl-10' : 'px-3',\r\n hasRight ? 'pr-10' : 'px-3',\r\n ]\r\n })\r\n\r\n // Text color for placeholder state (select, datepicker)\r\n const textColorClass = computed(() => {\r\n const hasVal = unwrap(hasValue)\r\n if (!hasVal) {\r\n return ['text-gray-400', 'dark:text-gray-500']\r\n }\r\n return ['text-gray-800', 'dark:text-gray-200']\r\n })\r\n\r\n // Full input classes (for <input>, <textarea>)\r\n const inputClass = computed(() => [\r\n ...baseClasses,\r\n ...stateClasses.value,\r\n ...sizeClasses.value,\r\n ...paddingClasses.value,\r\n 'placeholder-gray-400 dark:placeholder-gray-500',\r\n ])\r\n\r\n // Trigger button classes (for select, datepicker, combobox)\r\n const triggerClass = computed(() => [\r\n 'flex items-center justify-between gap-2 text-left',\r\n ...baseClasses,\r\n ...stateClasses.value,\r\n ...sizeClasses.value,\r\n 'px-3', // triggers always have px-3\r\n ...textColorClass.value,\r\n ])\r\n\r\n // Wrapper/container classes (for TagsInput, etc.)\r\n const wrapperClass = computed(() => {\r\n const isDisabled = unwrap(disabled)\r\n const currentSize = unwrap(size)\r\n\r\n const sizeWrapperClasses = {\r\n sm: 'min-h-9 py-1.5 px-2 gap-1',\r\n md: 'min-h-11 py-2 px-3 gap-1.5',\r\n lg: 'min-h-13 py-2.5 px-4 gap-2',\r\n }[currentSize]\r\n\r\n if (isDisabled) {\r\n return [\r\n 'flex flex-wrap items-center rounded-md border transition-all cursor-not-allowed',\r\n 'border-gray-200 bg-gray-50',\r\n 'dark:border-gray-800 dark:bg-gray-950',\r\n sizeWrapperClasses,\r\n ]\r\n }\r\n\r\n return [\r\n 'flex flex-wrap items-center rounded-md border transition-all cursor-text',\r\n 'border-gray-300 bg-white hover:border-gray-400',\r\n 'focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20',\r\n 'dark:border-gray-700 dark:bg-gray-900 dark:hover:border-gray-600',\r\n sizeWrapperClasses,\r\n ]\r\n })\r\n\r\n // Icon classes\r\n const iconClass = computed(() => ['size-4', 'text-gray-400'])\r\n\r\n return {\r\n inputClass,\r\n triggerClass,\r\n wrapperClass,\r\n iconClass,\r\n }\r\n}\r\n"],"names":["computed"],"mappings":";;AAiCA,MAAM,SAAS,CAAI,UAAyB;AAC1C,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,WAAW,QAC5D,MAAiB,QAClB;AACN;AAKO,SAAS,eAAe,UAAiC,IAA0B;AACxF,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,IACP,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW;AAAA,EAAA,IACT;AAGJ,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,EAAA;AAIF,QAAM,eAAeA,IAAAA,SAAS,MAAM;AAClC,UAAM,aAAa,OAAO,QAAQ;AAClC,UAAM,YAAY,OAAO,OAAO;AAChC,UAAM,YAAY,OAAO,OAAO;AAEhC,QAAI,YAAY;AACd,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,WAAW;AACb,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,WAAW;AACb,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,CAAC;AAGD,QAAM,cAAcA,IAAAA,SAAS,MAAM;AACjC,UAAM,cAAc,OAAO,IAAI;AAC/B,YAAQ,aAAA;AAAA,MACN,KAAK;AACH,eAAO,CAAC,UAAU,SAAS;AAAA,MAC7B,KAAK;AACH,eAAO,CAAC,QAAQ,WAAW;AAAA,MAC7B;AACE,eAAO,CAAC,QAAQ,SAAS;AAAA,IAAA;AAAA,EAE/B,CAAC;AAGD,QAAM,iBAAiBA,IAAAA,SAAS,MAAM;AACpC,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,WAAW,OAAO,YAAY;AAEpC,WAAO;AAAA,MACL,UAAU,UAAU;AAAA,MACpB,WAAW,UAAU;AAAA,IAAA;AAAA,EAEzB,CAAC;AAGD,QAAM,iBAAiBA,IAAAA,SAAS,MAAM;AACpC,UAAM,SAAS,OAAO,QAAQ;AAC9B,QAAI,CAAC,QAAQ;AACX,aAAO,CAAC,iBAAiB,oBAAoB;AAAA,IAC/C;AACA,WAAO,CAAC,iBAAiB,oBAAoB;AAAA,EAC/C,CAAC;AAGD,QAAM,aAAaA,IAAAA,SAAS,MAAM;AAAA,IAChC,GAAG;AAAA,IACH,GAAG,aAAa;AAAA,IAChB,GAAG,YAAY;AAAA,IACf,GAAG,eAAe;AAAA,IAClB;AAAA,EAAA,CACD;AAGD,QAAM,eAAeA,IAAAA,SAAS,MAAM;AAAA,IAClC;AAAA,IACA,GAAG;AAAA,IACH,GAAG,aAAa;AAAA,IAChB,GAAG,YAAY;AAAA,IACf;AAAA;AAAA,IACA,GAAG,eAAe;AAAA,EAAA,CACnB;AAGD,QAAM,eAAeA,IAAAA,SAAS,MAAM;AAClC,UAAM,aAAa,OAAO,QAAQ;AAClC,UAAM,cAAc,OAAO,IAAI;AAE/B,UAAM,qBAAqB;AAAA,MACzB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IAAA,EACJ,WAAW;AAEb,QAAI,YAAY;AACd,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,CAAC;AAGD,QAAM,YAAYA,IAAAA,SAAS,MAAM,CAAC,UAAU,eAAe,CAAC;AAE5D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ref, watch, onMounted, computed } from "vue";
|
|
2
|
+
function useDarkMode(options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
selector = "html",
|
|
5
|
+
attribute = "class",
|
|
6
|
+
storageKey = "dark-mode",
|
|
7
|
+
defaultValue = false
|
|
8
|
+
} = options;
|
|
9
|
+
const isDark = ref(defaultValue);
|
|
10
|
+
const getInitialValue = () => {
|
|
11
|
+
if (typeof window === "undefined") return defaultValue;
|
|
12
|
+
const stored = localStorage.getItem(storageKey);
|
|
13
|
+
if (stored !== null) {
|
|
14
|
+
return stored === "true";
|
|
15
|
+
}
|
|
16
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
17
|
+
};
|
|
18
|
+
const updateDOM = (dark) => {
|
|
19
|
+
if (typeof document === "undefined") return;
|
|
20
|
+
const element = document.querySelector(selector);
|
|
21
|
+
if (!element) return;
|
|
22
|
+
if (attribute === "class") {
|
|
23
|
+
element.classList.toggle("dark", dark);
|
|
24
|
+
} else {
|
|
25
|
+
element.setAttribute(attribute, dark ? "dark" : "light");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const toggle = () => {
|
|
29
|
+
isDark.value = !isDark.value;
|
|
30
|
+
};
|
|
31
|
+
const set = (value) => {
|
|
32
|
+
isDark.value = value;
|
|
33
|
+
};
|
|
34
|
+
watch(isDark, (newValue) => {
|
|
35
|
+
updateDOM(newValue);
|
|
36
|
+
if (typeof localStorage !== "undefined") {
|
|
37
|
+
localStorage.setItem(storageKey, String(newValue));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
onMounted(() => {
|
|
41
|
+
isDark.value = getInitialValue();
|
|
42
|
+
updateDOM(isDark.value);
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
isDark,
|
|
46
|
+
toggle,
|
|
47
|
+
set
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function usePagination(options) {
|
|
51
|
+
const {
|
|
52
|
+
items: itemsOption,
|
|
53
|
+
pageSize: initialPageSize = 10,
|
|
54
|
+
initialPage = 1
|
|
55
|
+
} = options;
|
|
56
|
+
const currentPage = ref(initialPage);
|
|
57
|
+
const pageSize = ref(initialPageSize);
|
|
58
|
+
const allItems = computed(() => {
|
|
59
|
+
return "value" in itemsOption ? itemsOption.value : itemsOption;
|
|
60
|
+
});
|
|
61
|
+
const totalItems = computed(() => allItems.value.length);
|
|
62
|
+
const totalPages = computed(() => {
|
|
63
|
+
if (totalItems.value === 0) return 1;
|
|
64
|
+
return Math.ceil(totalItems.value / pageSize.value);
|
|
65
|
+
});
|
|
66
|
+
const startIndex = computed(() => {
|
|
67
|
+
return (currentPage.value - 1) * pageSize.value;
|
|
68
|
+
});
|
|
69
|
+
const endIndex = computed(() => {
|
|
70
|
+
return Math.min(startIndex.value + pageSize.value - 1, totalItems.value - 1);
|
|
71
|
+
});
|
|
72
|
+
const paginatedItems = computed(() => {
|
|
73
|
+
const start = startIndex.value;
|
|
74
|
+
const end = start + pageSize.value;
|
|
75
|
+
return allItems.value.slice(start, end);
|
|
76
|
+
});
|
|
77
|
+
const hasPrevious = computed(() => currentPage.value > 1);
|
|
78
|
+
const hasNext = computed(() => currentPage.value < totalPages.value);
|
|
79
|
+
watch(totalPages, (newTotalPages) => {
|
|
80
|
+
if (currentPage.value > newTotalPages) {
|
|
81
|
+
currentPage.value = Math.max(1, newTotalPages);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const goToPage = (page) => {
|
|
85
|
+
const validPage = Math.max(1, Math.min(page, totalPages.value));
|
|
86
|
+
currentPage.value = validPage;
|
|
87
|
+
};
|
|
88
|
+
const nextPage = () => {
|
|
89
|
+
if (hasNext.value) {
|
|
90
|
+
currentPage.value++;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const previousPage = () => {
|
|
94
|
+
if (hasPrevious.value) {
|
|
95
|
+
currentPage.value--;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const firstPage = () => {
|
|
99
|
+
currentPage.value = 1;
|
|
100
|
+
};
|
|
101
|
+
const lastPage = () => {
|
|
102
|
+
currentPage.value = totalPages.value;
|
|
103
|
+
};
|
|
104
|
+
const setPageSize = (size) => {
|
|
105
|
+
pageSize.value = size;
|
|
106
|
+
currentPage.value = 1;
|
|
107
|
+
};
|
|
108
|
+
const reset = () => {
|
|
109
|
+
currentPage.value = initialPage;
|
|
110
|
+
pageSize.value = initialPageSize;
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
currentPage,
|
|
114
|
+
pageSize,
|
|
115
|
+
totalPages,
|
|
116
|
+
totalItems,
|
|
117
|
+
paginatedItems,
|
|
118
|
+
startIndex,
|
|
119
|
+
endIndex,
|
|
120
|
+
hasPrevious,
|
|
121
|
+
hasNext,
|
|
122
|
+
goToPage,
|
|
123
|
+
nextPage,
|
|
124
|
+
previousPage,
|
|
125
|
+
firstPage,
|
|
126
|
+
lastPage,
|
|
127
|
+
setPageSize,
|
|
128
|
+
reset
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
usePagination as a,
|
|
133
|
+
useDarkMode as u
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=usePagination-BGwbICFC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePagination-BGwbICFC.js","sources":["../src/composables/useDarkMode.ts","../src/composables/usePagination.ts"],"sourcesContent":["import { ref, watch, onMounted } from 'vue'\n\nexport interface DarkModeOptions {\n selector?: string\n attribute?: string\n storageKey?: string\n defaultValue?: boolean\n}\n\n/**\n * Composable for managing dark mode state\n */\nexport function useDarkMode(options: DarkModeOptions = {}) {\n const {\n selector = 'html',\n attribute = 'class',\n storageKey = 'dark-mode',\n defaultValue = false,\n } = options\n\n const isDark = ref(defaultValue)\n\n const getInitialValue = (): boolean => {\n if (typeof window === 'undefined') return defaultValue\n\n const stored = localStorage.getItem(storageKey)\n if (stored !== null) {\n return stored === 'true'\n }\n\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n }\n\n const updateDOM = (dark: boolean) => {\n if (typeof document === 'undefined') return\n\n const element = document.querySelector(selector)\n if (!element) return\n\n if (attribute === 'class') {\n element.classList.toggle('dark', dark)\n } else {\n element.setAttribute(attribute, dark ? 'dark' : 'light')\n }\n }\n\n const toggle = () => {\n isDark.value = !isDark.value\n }\n\n const set = (value: boolean) => {\n isDark.value = value\n }\n\n watch(isDark, (newValue) => {\n updateDOM(newValue)\n if (typeof localStorage !== 'undefined') {\n localStorage.setItem(storageKey, String(newValue))\n }\n })\n\n onMounted(() => {\n isDark.value = getInitialValue()\n updateDOM(isDark.value)\n })\n\n return {\n isDark,\n toggle,\n set,\n }\n}\n","import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport interface UsePaginationOptions<T> {\r\n /** The full array of items to paginate */\r\n items: T[] | Ref<T[]>\r\n /** Initial page size */\r\n pageSize?: number\r\n /** Initial page (1-indexed) */\r\n initialPage?: number\r\n}\r\n\r\nexport interface UsePaginationReturn<T> {\r\n /** Current page number (1-indexed) */\r\n currentPage: Ref<number>\r\n /** Current page size */\r\n pageSize: Ref<number>\r\n /** Total number of pages */\r\n totalPages: ComputedRef<number>\r\n /** Total number of items */\r\n totalItems: ComputedRef<number>\r\n /** Items for the current page */\r\n paginatedItems: ComputedRef<T[]>\r\n /** Index of the first item on current page (0-indexed) */\r\n startIndex: ComputedRef<number>\r\n /** Index of the last item on current page (0-indexed) */\r\n endIndex: ComputedRef<number>\r\n /** Whether there is a previous page */\r\n hasPrevious: ComputedRef<boolean>\r\n /** Whether there is a next page */\r\n hasNext: ComputedRef<boolean>\r\n /** Go to a specific page */\r\n goToPage: (page: number) => void\r\n /** Go to the next page */\r\n nextPage: () => void\r\n /** Go to the previous page */\r\n previousPage: () => void\r\n /** Go to the first page */\r\n firstPage: () => void\r\n /** Go to the last page */\r\n lastPage: () => void\r\n /** Set page size (resets to page 1) */\r\n setPageSize: (size: number) => void\r\n /** Reset to initial state */\r\n reset: () => void\r\n}\r\n\r\nexport function usePagination<T>(\r\n options: UsePaginationOptions<T>\r\n): UsePaginationReturn<T> {\r\n const {\r\n items: itemsOption,\r\n pageSize: initialPageSize = 10,\r\n initialPage = 1,\r\n } = options\r\n\r\n const currentPage = ref(initialPage)\r\n const pageSize = ref(initialPageSize)\r\n\r\n const allItems = computed(() => {\r\n return 'value' in itemsOption ? itemsOption.value : itemsOption\r\n })\r\n\r\n const totalItems = computed(() => allItems.value.length)\r\n\r\n const totalPages = computed(() => {\r\n if (totalItems.value === 0) return 1\r\n return Math.ceil(totalItems.value / pageSize.value)\r\n })\r\n\r\n const startIndex = computed(() => {\r\n return (currentPage.value - 1) * pageSize.value\r\n })\r\n\r\n const endIndex = computed(() => {\r\n return Math.min(startIndex.value + pageSize.value - 1, totalItems.value - 1)\r\n })\r\n\r\n const paginatedItems = computed(() => {\r\n const start = startIndex.value\r\n const end = start + pageSize.value\r\n return allItems.value.slice(start, end)\r\n })\r\n\r\n const hasPrevious = computed(() => currentPage.value > 1)\r\n const hasNext = computed(() => currentPage.value < totalPages.value)\r\n\r\n // Ensure current page is valid when items change\r\n watch(totalPages, (newTotalPages) => {\r\n if (currentPage.value > newTotalPages) {\r\n currentPage.value = Math.max(1, newTotalPages)\r\n }\r\n })\r\n\r\n const goToPage = (page: number) => {\r\n const validPage = Math.max(1, Math.min(page, totalPages.value))\r\n currentPage.value = validPage\r\n }\r\n\r\n const nextPage = () => {\r\n if (hasNext.value) {\r\n currentPage.value++\r\n }\r\n }\r\n\r\n const previousPage = () => {\r\n if (hasPrevious.value) {\r\n currentPage.value--\r\n }\r\n }\r\n\r\n const firstPage = () => {\r\n currentPage.value = 1\r\n }\r\n\r\n const lastPage = () => {\r\n currentPage.value = totalPages.value\r\n }\r\n\r\n const setPageSize = (size: number) => {\r\n pageSize.value = size\r\n currentPage.value = 1 // Reset to first page when changing page size\r\n }\r\n\r\n const reset = () => {\r\n currentPage.value = initialPage\r\n pageSize.value = initialPageSize\r\n }\r\n\r\n return {\r\n currentPage,\r\n pageSize,\r\n totalPages,\r\n totalItems,\r\n paginatedItems,\r\n startIndex,\r\n endIndex,\r\n hasPrevious,\r\n hasNext,\r\n goToPage,\r\n nextPage,\r\n previousPage,\r\n firstPage,\r\n lastPage,\r\n setPageSize,\r\n reset,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|