@stacksjs/defaults 0.70.54
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/LICENSE.md +21 -0
- package/README.md +2 -0
- package/package.json +20 -0
- package/resources/assets/fonts/013717e9.woff +0 -0
- package/resources/assets/fonts/03355817.woff2 +0 -0
- package/resources/assets/fonts/0380de7e.woff2 +0 -0
- package/resources/assets/fonts/0ba5d32a.woff +0 -0
- package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
- package/resources/assets/fonts/19de0a60.woff2 +0 -0
- package/resources/assets/fonts/20b87e84.woff +0 -0
- package/resources/assets/fonts/4bbc67e1.woff +0 -0
- package/resources/assets/fonts/576fb0d7.woff +0 -0
- package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
- package/resources/assets/fonts/6413fc0a.woff +0 -0
- package/resources/assets/fonts/72c114cf.woff +0 -0
- package/resources/assets/fonts/73c57e73.woff2 +0 -0
- package/resources/assets/fonts/813dca6c.woff +0 -0
- package/resources/assets/fonts/8a9b0995.woff2 +0 -0
- package/resources/assets/fonts/90b4832f.woff +0 -0
- package/resources/assets/fonts/9394ab85.woff2 +0 -0
- package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
- package/resources/assets/fonts/Monaco.ttf +0 -0
- package/resources/assets/fonts/b1b36d74.woff +0 -0
- package/resources/assets/fonts/b5d74798.woff2 +0 -0
- package/resources/assets/fonts/b9f81a4d.woff +0 -0
- package/resources/assets/fonts/c9784008.woff2 +0 -0
- package/resources/assets/fonts/cf692227.woff +0 -0
- package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
- package/resources/assets/fonts/e2eb62fc.woff +0 -0
- package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
- package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
- package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
- package/resources/assets/images/app-icon.png +0 -0
- package/resources/assets/images/bg-img.png +0 -0
- package/resources/assets/images/park-ridge.svg +15 -0
- package/resources/assets/images/topography.svg +1 -0
- package/resources/assets/scripts/main.ts +2153 -0
- package/resources/assets/styles/README.md +19 -0
- package/resources/assets/styles/docs/main.css +1 -0
- package/resources/assets/styles/docs/overrides.css +1 -0
- package/resources/assets/styles/docs/vars.css +8 -0
- package/resources/assets/styles/main.css +1419 -0
- package/resources/components/Button.stx +70 -0
- package/resources/components/Buttons/BaseButton.stx +39 -0
- package/resources/components/Buttons/Counter.stx +28 -0
- package/resources/components/Buttons/ToggleDark.stx +11 -0
- package/resources/components/Container.stx +10 -0
- package/resources/components/Dashboard/.gitkeep +0 -0
- package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
- package/resources/components/Dashboard/Auth/Login.stx +212 -0
- package/resources/components/Dashboard/Auth/Register.stx +306 -0
- package/resources/components/Dashboard/Avatar.stx +123 -0
- package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
- package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
- package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
- package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
- package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
- package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
- package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
- package/resources/components/Dashboard/Billing/Plans.stx +136 -0
- package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
- package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
- package/resources/components/Dashboard/Card.stx +78 -0
- package/resources/components/Dashboard/Chart.stx +75 -0
- package/resources/components/Dashboard/CodeEditor.stx +41 -0
- package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
- package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
- package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
- package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
- package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
- package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
- package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
- package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
- package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
- package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
- package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
- package/resources/components/Dashboard/DashboardLayout.stx +155 -0
- package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
- package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
- package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
- package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
- package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
- package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
- package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
- package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
- package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
- package/resources/components/Dashboard/Email/EmailList.stx +117 -0
- package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
- package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
- package/resources/components/Dashboard/GlobalSearch.stx +124 -0
- package/resources/components/Dashboard/MobileSidebar.stx +47 -0
- package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
- package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
- package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
- package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
- package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
- package/resources/components/Dashboard/Navbar.stx +194 -0
- package/resources/components/Dashboard/NavbarModern.stx +188 -0
- package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
- package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
- package/resources/components/Dashboard/Pagination.stx +90 -0
- package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
- package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
- package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
- package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
- package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
- package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
- package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
- package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
- package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
- package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
- package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
- package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
- package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
- package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
- package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
- package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
- package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
- package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
- package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
- package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
- package/resources/components/Dashboard/SettingsHeader.stx +88 -0
- package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
- package/resources/components/Dashboard/Sidebar.stx +23 -0
- package/resources/components/Dashboard/SidebarModern.stx +249 -0
- package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
- package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
- package/resources/components/Dashboard/Table.stx +82 -0
- package/resources/components/Dashboard/TerminalContainer.stx +97 -0
- package/resources/components/Dashboard/Tooltip.stx +166 -0
- package/resources/components/Dashboard/Transaction/index.stx +88 -0
- package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
- package/resources/components/Dashboard/UI/Avatar.stx +102 -0
- package/resources/components/Dashboard/UI/Badge.stx +66 -0
- package/resources/components/Dashboard/UI/Button.stx +73 -0
- package/resources/components/Dashboard/UI/Card.stx +51 -0
- package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
- package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
- package/resources/components/Dashboard/UI/DataTable.stx +45 -0
- package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
- package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
- package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
- package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
- package/resources/components/Dashboard/UI/Input.stx +77 -0
- package/resources/components/Dashboard/UI/Modal.stx +75 -0
- package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
- package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
- package/resources/components/Dashboard/UI/Pagination.stx +126 -0
- package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
- package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
- package/resources/components/Dashboard/UI/Select.stx +84 -0
- package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
- package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
- package/resources/components/Dashboard/UI/Table.stx +143 -0
- package/resources/components/Dashboard/UI/Tabs.stx +45 -0
- package/resources/components/Dashboard/UI/Textarea.stx +93 -0
- package/resources/components/Dashboard/UI/Toggle.stx +84 -0
- package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
- package/resources/components/Dashboard/Widget.stx +89 -0
- package/resources/components/Dashboard/WindowControls.stx +119 -0
- package/resources/components/Dashboard/index.ts +106 -0
- package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
- package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
- package/resources/components/Docs/Demo/DialogCode.md +42 -0
- package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
- package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
- package/resources/components/Docs/Demo/DropdownCode.md +109 -0
- package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
- package/resources/components/Docs/Demo/Hero.stx +100 -0
- package/resources/components/Docs/Demo/ListboxCode.md +87 -0
- package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
- package/resources/components/Docs/Demo/NotificationCode.md +173 -0
- package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
- package/resources/components/Docs/Demo/PopoverCode.md +161 -0
- package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
- package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
- package/resources/components/Docs/Demo/StepperCode.md +216 -0
- package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
- package/resources/components/Docs/Demo/SwitchCode.md +24 -0
- package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
- package/resources/components/Docs/Home.stx +7 -0
- package/resources/components/Docs/HomeContributors.stx +23 -0
- package/resources/components/Docs/HomeSponsors.stx +17 -0
- package/resources/components/Docs/HomeTeam.stx +18 -0
- package/resources/components/Docs/TeamMember.stx +79 -0
- package/resources/components/Email/EmailButton.stx +32 -0
- package/resources/components/Email/EmailDivider.stx +20 -0
- package/resources/components/Email/EmailImage.stx +24 -0
- package/resources/components/Email/EmailLayout.stx +55 -0
- package/resources/components/Email/EmailLink.stx +22 -0
- package/resources/components/Email/EmailSection.stx +20 -0
- package/resources/components/Email/EmailText.stx +30 -0
- package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
- package/resources/components/Forum/ForumCategory.stx +16 -0
- package/resources/components/Forum/ForumCategoryItem.stx +46 -0
- package/resources/components/Forum/ForumLayout.stx +36 -0
- package/resources/components/Forum/ForumPagination.stx +39 -0
- package/resources/components/Forum/ForumPost.stx +71 -0
- package/resources/components/Forum/ForumReplyForm.stx +76 -0
- package/resources/components/Forum/ForumSidebar.stx +86 -0
- package/resources/components/Forum/ForumTopicItem.stx +77 -0
- package/resources/components/Forum/ForumTopicList.stx +23 -0
- package/resources/components/HelloWorld.stx +253 -0
- package/resources/components/Logo.stx +10 -0
- package/resources/components/Marketing/CallToAction.stx +30 -0
- package/resources/components/Marketing/ComingSoon.stx +155 -0
- package/resources/components/Marketing/Faqs.stx +99 -0
- package/resources/components/Marketing/Feature.stx +32 -0
- package/resources/components/Marketing/Fields.stx +28 -0
- package/resources/components/Marketing/Footer.stx +33 -0
- package/resources/components/Marketing/Header.stx +51 -0
- package/resources/components/Marketing/Hero.stx +46 -0
- package/resources/components/Marketing/Layout.stx +26 -0
- package/resources/components/Marketing/NavLink.stx +13 -0
- package/resources/components/Marketing/Plan.stx +43 -0
- package/resources/components/Marketing/Pricing.stx +120 -0
- package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
- package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
- package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
- package/resources/components/Marketing/Testimonials.stx +120 -0
- package/resources/components/Pagination.stx +149 -0
- package/resources/components/Playground.stx +9 -0
- package/resources/components/README.md +87 -0
- package/resources/components/Storefront/AnnouncementBar.stx +17 -0
- package/resources/components/Storefront/CartDrawer.stx +227 -0
- package/resources/components/Storefront/CheckoutSteps.stx +50 -0
- package/resources/components/Storefront/Footer.stx +81 -0
- package/resources/components/Storefront/Header.stx +66 -0
- package/resources/components/Storefront/OrderSummary.stx +79 -0
- package/resources/components/Storefront/ProductCard.stx +63 -0
- package/resources/components/Storefront/ProductGallery.stx +88 -0
- package/resources/components/Storefront/QuantitySelector.stx +97 -0
- package/resources/components/index.ts +13 -0
- package/resources/emails/email-verification.stx +46 -0
- package/resources/emails/layouts/base.html +113 -0
- package/resources/emails/password-changed.stx +55 -0
- package/resources/emails/password-reset.stx +43 -0
- package/resources/emails/subscription-confirmation.stx +99 -0
- package/resources/emails/welcome.stx +48 -0
- package/resources/functions/README.md +28 -0
- package/resources/functions/counter.ts +13 -0
- package/resources/functions/dark.ts +4 -0
- package/resources/functions/dashboard/analytics.ts +238 -0
- package/resources/functions/dashboard/config-io.ts +316 -0
- package/resources/functions/dashboard/data.ts +375 -0
- package/resources/functions/dashboard/library.ts +250 -0
- package/resources/functions/dashboard/sidebar.ts +537 -0
- package/resources/functions/dashboard/system.ts +150 -0
- package/resources/layouts/403.stx +188 -0
- package/resources/layouts/404.stx +34 -0
- package/resources/layouts/README.md +14 -0
- package/resources/layouts/dashboard/default.stx +20 -0
- package/resources/layouts/dashboard/guest.stx +10 -0
- package/resources/layouts/default.stx +3 -0
- package/resources/layouts/maintenance.stx +11 -0
- package/resources/layouts/storefront.stx +73 -0
- package/resources/layouts/system-tray/default.stx +3 -0
- package/resources/modules/.gitkeep +0 -0
- package/resources/plugins/.gitkeep +0 -0
- package/resources/plugins/preloader.ts +308 -0
- package/resources/types/README.md +3 -0
- package/resources/views/cart.stx +149 -0
- package/resources/views/checkout/contact.stx +101 -0
- package/resources/views/checkout/payment.stx +114 -0
- package/resources/views/checkout/shipping.stx +120 -0
- package/resources/views/coming-soon.stx +9 -0
- package/resources/views/dashboard/custom-page.stx +45 -0
- package/resources/views/dashboard/index.stx +24 -0
- package/resources/views/emails/welcome.stx +0 -0
- package/resources/views/errors/tester.stx +26 -0
- package/resources/views/index.stx +2865 -0
- package/resources/views/orders/[id].stx +130 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface DropdownProps {
|
|
3
|
+
align?: 'left' | 'right'
|
|
4
|
+
width?: 'auto' | 'sm' | 'md' | 'lg' | 'full'
|
|
5
|
+
disabled?: boolean
|
|
6
|
+
id?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { align = 'left', width = 'auto', disabled = false, id = 'dropdown-' + Math.random().toString(36).slice(2, 11) } = defineProps<DropdownProps>()
|
|
10
|
+
|
|
11
|
+
const widthClasses = {
|
|
12
|
+
auto: 'min-w-[12rem]',
|
|
13
|
+
sm: 'w-48',
|
|
14
|
+
md: 'w-56',
|
|
15
|
+
lg: 'w-64',
|
|
16
|
+
full: 'w-full',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const alignClass = align === 'right' ? 'right-0' : 'left-0'
|
|
20
|
+
</script>
|
|
21
|
+
<div ref="pageRoot" class="inline-block relative text-left" data-dropdown="{{ id }}">
|
|
22
|
+
<!-- Trigger -->
|
|
23
|
+
<div data-dropdown-trigger>
|
|
24
|
+
{{ triggerSlot }}
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Menu - macOS popover style -->
|
|
28
|
+
<div
|
|
29
|
+
data-dropdown-menu
|
|
30
|
+
class="absolute z-50 mt-1.5 {{ widthClasses[width] }} {{ alignClass }} origin-top-right bg-white/95 dark:bg-neutral-800/95 backdrop-blur-xl backdrop-saturate-150 rounded-lg shadow-lg shadow-black/10 dark:shadow-black/30 border border-black/10 dark:border-white/10 py-1 focus:outline-none hidden"
|
|
31
|
+
>
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<script client>
|
|
36
|
+
var _root = useRef('pageRoot')
|
|
37
|
+
onMount(function() {
|
|
38
|
+
var el = _root.current;
|
|
39
|
+
if (!el) return;
|
|
40
|
+
const dropdown = el
|
|
41
|
+
if (!dropdown) return
|
|
42
|
+
|
|
43
|
+
const trigger = dropdown.querySelector('[data-dropdown-trigger]')
|
|
44
|
+
const menu = dropdown.querySelector('[data-dropdown-menu]')
|
|
45
|
+
let isOpen = false
|
|
46
|
+
|
|
47
|
+
function toggle() {
|
|
48
|
+
if ({{ disabled ? 'true' : 'false' }}) return
|
|
49
|
+
isOpen = !isOpen
|
|
50
|
+
menu.classList.toggle('hidden', !isOpen)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function close() {
|
|
54
|
+
isOpen = false
|
|
55
|
+
menu.classList.add('hidden')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
trigger?.addEventListener('click', toggle)
|
|
59
|
+
|
|
60
|
+
document.body.addEventListener('click', function(e) {
|
|
61
|
+
if (isOpen && !dropdown.contains(e.target)) {
|
|
62
|
+
close()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
document.body.addEventListener('keydown', function(e) {
|
|
67
|
+
if (e.key === 'Escape' && isOpen) {
|
|
68
|
+
close()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface DropdownItemProps {
|
|
3
|
+
href?: string
|
|
4
|
+
disabled?: boolean
|
|
5
|
+
danger?: boolean
|
|
6
|
+
active?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { href = '', disabled = false, danger = false, active = false } = defineProps<DropdownItemProps>()
|
|
10
|
+
|
|
11
|
+
function getClasses() {
|
|
12
|
+
const base = [
|
|
13
|
+
'w-[calc(100%-8px)] mx-1 text-left px-2.5 py-1.5 text-[13px]',
|
|
14
|
+
'flex items-center gap-2.5',
|
|
15
|
+
'transition-colors duration-100',
|
|
16
|
+
'focus:outline-none',
|
|
17
|
+
'rounded-md',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
if (disabled) {
|
|
21
|
+
base.push('opacity-40 cursor-not-allowed')
|
|
22
|
+
} else if (danger) {
|
|
23
|
+
base.push(
|
|
24
|
+
'text-red-600 dark:text-red-400',
|
|
25
|
+
'hover:bg-red-500 hover:text-white',
|
|
26
|
+
'focus:bg-red-500 focus:text-white',
|
|
27
|
+
)
|
|
28
|
+
} else if (active) {
|
|
29
|
+
base.push('bg-blue-500 text-white')
|
|
30
|
+
} else {
|
|
31
|
+
base.push(
|
|
32
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
33
|
+
'hover:bg-black/5 dark:hover:bg-white/10',
|
|
34
|
+
'focus:bg-black/5 dark:focus:bg-white/10',
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return base.join(' ')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const classes = getClasses()
|
|
42
|
+
</script>
|
|
43
|
+
@if(href)
|
|
44
|
+
<a
|
|
45
|
+
href="{{ href }}"
|
|
46
|
+
class="{{ classes }}"
|
|
47
|
+
>
|
|
48
|
+
<!-- Leading icon slot -->
|
|
49
|
+
<span class="flex items-center justify-center h-4 w-4 text-current opacity-70">
|
|
50
|
+
{{ iconSlot }}
|
|
51
|
+
</span>
|
|
52
|
+
|
|
53
|
+
<!-- Content -->
|
|
54
|
+
<span class="flex-1 truncate">
|
|
55
|
+
<slot />
|
|
56
|
+
</span>
|
|
57
|
+
|
|
58
|
+
<!-- Trailing content slot (for shortcuts, badges, etc.) -->
|
|
59
|
+
{{ trailingSlot }}
|
|
60
|
+
</a>
|
|
61
|
+
@else
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
class="{{ classes }}"
|
|
65
|
+
@if(disabled) disabled @endif
|
|
66
|
+
>
|
|
67
|
+
<!-- Leading icon slot -->
|
|
68
|
+
<span class="flex items-center justify-center h-4 w-4 text-current opacity-70">
|
|
69
|
+
{{ iconSlot }}
|
|
70
|
+
</span>
|
|
71
|
+
|
|
72
|
+
<!-- Content -->
|
|
73
|
+
<span class="flex-1 truncate">
|
|
74
|
+
<slot />
|
|
75
|
+
</span>
|
|
76
|
+
|
|
77
|
+
<!-- Trailing content slot (for shortcuts, badges, etc.) -->
|
|
78
|
+
{{ trailingSlot }}
|
|
79
|
+
</button>
|
|
80
|
+
@endif
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface EmptyStateProps {
|
|
3
|
+
icon?: string
|
|
4
|
+
title?: string
|
|
5
|
+
description?: string
|
|
6
|
+
actionLabel?: string
|
|
7
|
+
actionIcon?: string
|
|
8
|
+
actionHref?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { icon = 'i-hugeicons-folder-02', title = '', description = '', actionLabel = '', actionIcon = '', actionHref = '' } = defineProps<EmptyStateProps>()
|
|
12
|
+
</script>
|
|
13
|
+
<div class="flex flex-col items-center justify-center px-4 py-12 text-center">
|
|
14
|
+
<!-- Icon -->
|
|
15
|
+
<div class="mb-4 p-4 bg-neutral-100 dark:bg-neutral-800 rounded-full">
|
|
16
|
+
@if(iconSlot)
|
|
17
|
+
{{ iconSlot }}
|
|
18
|
+
@else
|
|
19
|
+
<div class="{{ icon }} w-8 h-8 text-neutral-400 dark:text-neutral-500"></div>
|
|
20
|
+
@endif
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Title -->
|
|
24
|
+
<h3 class="mb-1 font-medium text-lg text-neutral-900 dark:text-white">
|
|
25
|
+
{{ title }}
|
|
26
|
+
</h3>
|
|
27
|
+
|
|
28
|
+
<!-- Description -->
|
|
29
|
+
@if(description)
|
|
30
|
+
<p class="mb-6 max-w-sm text-neutral-500 text-sm dark:text-neutral-400">
|
|
31
|
+
{{ description }}
|
|
32
|
+
</p>
|
|
33
|
+
@endif
|
|
34
|
+
|
|
35
|
+
<!-- Action button -->
|
|
36
|
+
@if(actionLabel)
|
|
37
|
+
<a
|
|
38
|
+
href="{{ actionHref || '#' }}"
|
|
39
|
+
class="inline-flex gap-2 items-center px-4 py-2 font-medium text-sm text-white bg-blue-600 hover:bg-blue-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-neutral-900 transition-colors"
|
|
40
|
+
>
|
|
41
|
+
@if(actionIcon)
|
|
42
|
+
<div class="{{ actionIcon }} w-4 h-4"></div>
|
|
43
|
+
@endif
|
|
44
|
+
{{ actionLabel }}
|
|
45
|
+
</a>
|
|
46
|
+
@endif
|
|
47
|
+
|
|
48
|
+
<!-- Custom action slot -->
|
|
49
|
+
{{ actionSlot }}
|
|
50
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface FilterBarProps {
|
|
3
|
+
searchPlaceholder?: string
|
|
4
|
+
searchValue?: string
|
|
5
|
+
itemsPerPage?: number
|
|
6
|
+
itemsPerPageOptions?: string
|
|
7
|
+
showItemsPerPage?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { searchPlaceholder = 'Search...', searchValue = '', itemsPerPage = 10, itemsPerPageOptions = [10, 25, 50, 100], showItemsPerPage = true } = defineProps<FilterBarProps>()
|
|
11
|
+
</script>
|
|
12
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
13
|
+
<!-- Search -->
|
|
14
|
+
<div class="relative flex-1 max-w-sm">
|
|
15
|
+
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
|
16
|
+
<div class="h-4 w-4 text-neutral-400 dark:text-neutral-500 i-hugeicons-search-01"></div>
|
|
17
|
+
</div>
|
|
18
|
+
<input
|
|
19
|
+
type="search"
|
|
20
|
+
value="{{ searchValue }}"
|
|
21
|
+
placeholder="{{ searchPlaceholder }}"
|
|
22
|
+
class="block pl-9 pr-3 py-1.5 w-full text-[13px] text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-400 dark:placeholder:text-neutral-500 bg-white dark:bg-white/10 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 hover:border-black/20 dark:hover:border-white/25 duration-150 transition-all"
|
|
23
|
+
data-filter-search
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Filter slots -->
|
|
28
|
+
<div class="flex flex-wrap gap-2 items-center">
|
|
29
|
+
<slot />
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Items per page -->
|
|
33
|
+
@if(showItemsPerPage)
|
|
34
|
+
<div class="flex gap-2 items-center ml-auto text-[12px] text-neutral-500 dark:text-neutral-400">
|
|
35
|
+
<span>Show</span>
|
|
36
|
+
<select
|
|
37
|
+
class="px-2 h-7 text-[12px] text-neutral-700 dark:text-neutral-200 bg-white dark:bg-white/10 border border-black/10 rounded-md dark:border-white/15 focus:border-blue-500/50 focus:ring-blue-500/40"
|
|
38
|
+
data-items-per-page
|
|
39
|
+
>
|
|
40
|
+
@foreach(itemsPerPageOptions as option)
|
|
41
|
+
<option value="{{ option }}" @if(option == itemsPerPage) selected @endif>
|
|
42
|
+
{{ option }}
|
|
43
|
+
</option>
|
|
44
|
+
@endforeach
|
|
45
|
+
</select>
|
|
46
|
+
</div>
|
|
47
|
+
@endif
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface InputProps {
|
|
3
|
+
value?: string
|
|
4
|
+
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'
|
|
5
|
+
placeholder?: string
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
readonly?: boolean
|
|
8
|
+
required?: boolean
|
|
9
|
+
error?: string
|
|
10
|
+
hint?: string
|
|
11
|
+
label?: string
|
|
12
|
+
id?: string
|
|
13
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
14
|
+
name?: string
|
|
15
|
+
fullWidth?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { value = '', type = 'text', placeholder = '', disabled = false, readonly = false, required = false, error = '', hint = '', label = '', id = '', size = 'md', name = '', fullWidth = true } = defineProps<InputProps>()
|
|
19
|
+
|
|
20
|
+
const sizeClasses = {
|
|
21
|
+
xs: 'px-2 py-1 text-[11px] h-6',
|
|
22
|
+
sm: 'px-2.5 py-1 text-[12px] h-7',
|
|
23
|
+
md: 'px-3 py-1.5 text-[13px] h-8',
|
|
24
|
+
lg: 'px-3.5 py-2 text-[14px] h-9'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const borderClass = error
|
|
28
|
+
? 'border-red-400/60 dark:border-red-500/60 focus:border-red-500 focus:ring-red-500/30'
|
|
29
|
+
: 'border-stone-200/80 dark:border-white/15 focus:border-blue-400 hover:border-stone-300 dark:hover:border-white/25'
|
|
30
|
+
|
|
31
|
+
const disabledClass = disabled ? 'opacity-40 cursor-not-allowed bg-stone-50 dark:bg-white/5' : ''
|
|
32
|
+
|
|
33
|
+
const inputClass = [
|
|
34
|
+
'block w-full',
|
|
35
|
+
'bg-white dark:bg-white/10',
|
|
36
|
+
'border rounded-lg',
|
|
37
|
+
'text-stone-900 dark:text-neutral-100',
|
|
38
|
+
'placeholder:text-stone-400 dark:placeholder:text-neutral-500',
|
|
39
|
+
'transition-all duration-150',
|
|
40
|
+
'focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:ring-offset-0',
|
|
41
|
+
sizeClasses[size],
|
|
42
|
+
borderClass,
|
|
43
|
+
disabledClass
|
|
44
|
+
].join(' ')
|
|
45
|
+
|
|
46
|
+
const inputId = id || 'input-' + Math.random().toString(36).slice(2, 11)
|
|
47
|
+
</script>
|
|
48
|
+
<div class="{{ fullWidth ? 'w-full' : '' }}">
|
|
49
|
+
@if(label)
|
|
50
|
+
<label for="{{ inputId }}" class="block text-[13px] font-medium mb-1.5 tracking-tight {{ error ? 'text-red-600 dark:text-red-400' : 'text-stone-600 dark:text-neutral-400' }}">
|
|
51
|
+
{{ label }}
|
|
52
|
+
@if(required)
|
|
53
|
+
<span class="ml-0.5 text-red-500">*</span>
|
|
54
|
+
@endif
|
|
55
|
+
</label>
|
|
56
|
+
@endif
|
|
57
|
+
|
|
58
|
+
<div class="relative">
|
|
59
|
+
<input
|
|
60
|
+
id="{{ inputId }}"
|
|
61
|
+
type="{{ type }}"
|
|
62
|
+
value="{{ value }}"
|
|
63
|
+
placeholder="{{ placeholder }}"
|
|
64
|
+
name="{{ name }}"
|
|
65
|
+
class="{{ inputClass }}"
|
|
66
|
+
@if(disabled) disabled @endif
|
|
67
|
+
@if(readonly) readonly @endif
|
|
68
|
+
@if(required) required @endif
|
|
69
|
+
>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
@if(error)
|
|
73
|
+
<p class="mt-1.5 text-red-600 text-sm dark:text-red-400">{{ error }}</p>
|
|
74
|
+
@elseif(hint)
|
|
75
|
+
<p class="mt-1.5 text-sm text-stone-500 dark:text-neutral-400">{{ hint }}</p>
|
|
76
|
+
@endif
|
|
77
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface ModalProps {
|
|
3
|
+
isOpen?: boolean
|
|
4
|
+
title?: string
|
|
5
|
+
description?: string
|
|
6
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
7
|
+
closable?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { isOpen = false, title = '', description = '', size = 'md', closable = true } = defineProps<ModalProps>()
|
|
11
|
+
|
|
12
|
+
const sizeClasses = {
|
|
13
|
+
sm: 'max-w-sm',
|
|
14
|
+
md: 'max-w-lg',
|
|
15
|
+
lg: 'max-w-2xl',
|
|
16
|
+
xl: 'max-w-4xl',
|
|
17
|
+
full: 'max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)]'
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
@if(isOpen)
|
|
21
|
+
<div ref="pageRoot" class="fixed inset-0 overflow-y-auto z-50">
|
|
22
|
+
<!-- Overlay -->
|
|
23
|
+
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm" data-modal-overlay></div>
|
|
24
|
+
|
|
25
|
+
<!-- Modal container -->
|
|
26
|
+
<div class="flex items-center justify-center p-4 min-h-full">
|
|
27
|
+
<div class="relative w-full rounded-xl bg-white dark:bg-neutral-900 shadow-2xl {{ sizeClasses[size] }}">
|
|
28
|
+
<!-- Header -->
|
|
29
|
+
@if(title || closable)
|
|
30
|
+
<div class="flex gap-4 items-start justify-between p-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
31
|
+
<div class="min-w-0">
|
|
32
|
+
@if(title)
|
|
33
|
+
<h3 class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
34
|
+
{{ title }}
|
|
35
|
+
</h3>
|
|
36
|
+
@endif
|
|
37
|
+
@if(description)
|
|
38
|
+
<p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">
|
|
39
|
+
{{ description }}
|
|
40
|
+
</p>
|
|
41
|
+
@endif
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
@if(closable)
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="-m-2 p-2 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors"
|
|
48
|
+
data-modal-close
|
|
49
|
+
>
|
|
50
|
+
<div class="h-5 w-5 i-hugeicons-cancel-01"></div>
|
|
51
|
+
</button>
|
|
52
|
+
@endif
|
|
53
|
+
</div>
|
|
54
|
+
@endif
|
|
55
|
+
|
|
56
|
+
<!-- Body -->
|
|
57
|
+
<div class="p-6">
|
|
58
|
+
<slot />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
@endif
|
|
64
|
+
<script client>
|
|
65
|
+
var _root = useRef('pageRoot')
|
|
66
|
+
onMount(function() {
|
|
67
|
+
var el = _root.current;
|
|
68
|
+
if (!el) return;
|
|
69
|
+
el.querySelectorAll('[data-modal-overlay], [data-modal-close]').forEach(item => {
|
|
70
|
+
item.addEventListener('click', () => {
|
|
71
|
+
item.closest('.fixed.inset-0')?.remove();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface PageHeaderProps {
|
|
3
|
+
title?: string
|
|
4
|
+
description?: string
|
|
5
|
+
backLink?: string
|
|
6
|
+
backLabel?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { title = '', description = '', backLink = '', backLabel = 'Back' } = defineProps<PageHeaderProps>()
|
|
10
|
+
</script>
|
|
11
|
+
<div class="mb-6">
|
|
12
|
+
<!-- Back link -->
|
|
13
|
+
@if(backLink)
|
|
14
|
+
<a
|
|
15
|
+
href="{{ backLink }}"
|
|
16
|
+
class="inline-flex gap-1.5 items-center mb-3 text-sm text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200 transition-colors"
|
|
17
|
+
>
|
|
18
|
+
<div class="h-4 w-4 i-hugeicons-arrow-left-01"></div>
|
|
19
|
+
<span>{{ backLabel }}</span>
|
|
20
|
+
</a>
|
|
21
|
+
@endif
|
|
22
|
+
|
|
23
|
+
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
24
|
+
<div class="flex-1 min-w-0">
|
|
25
|
+
<!-- Title row -->
|
|
26
|
+
<div class="flex gap-3 items-center">
|
|
27
|
+
<slot name="icon" />
|
|
28
|
+
<div>
|
|
29
|
+
<h1 class="font-semibold text-2xl text-stone-900 truncate dark:text-white">
|
|
30
|
+
{{ title }}
|
|
31
|
+
</h1>
|
|
32
|
+
@if(description)
|
|
33
|
+
<p class="mt-1 text-sm text-stone-500 dark:text-neutral-400">
|
|
34
|
+
{{ description }}
|
|
35
|
+
</p>
|
|
36
|
+
@endif
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Tags/badges slot -->
|
|
41
|
+
@if($slots.tags)
|
|
42
|
+
<div class="flex flex-wrap gap-2 mt-2">
|
|
43
|
+
<slot name="tags" />
|
|
44
|
+
</div>
|
|
45
|
+
@endif
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Actions -->
|
|
49
|
+
@if($slots.actions)
|
|
50
|
+
<div class="flex flex-shrink-0 gap-2 items-center">
|
|
51
|
+
<slot name="actions" />
|
|
52
|
+
</div>
|
|
53
|
+
@endif
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Tabs slot -->
|
|
57
|
+
@if($slots.tabs)
|
|
58
|
+
<div class="mt-4 border-b border-stone-200 dark:border-neutral-700">
|
|
59
|
+
<slot name="tabs" />
|
|
60
|
+
</div>
|
|
61
|
+
@endif
|
|
62
|
+
|
|
63
|
+
<!-- Filters slot -->
|
|
64
|
+
@if($slots.filters)
|
|
65
|
+
<div class="mt-4">
|
|
66
|
+
<slot name="filters" />
|
|
67
|
+
</div>
|
|
68
|
+
@endif
|
|
69
|
+
</div>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface PageLayoutProps {
|
|
3
|
+
/**
|
|
4
|
+
* Visual variant.
|
|
5
|
+
*
|
|
6
|
+
* - `default` — standard centered column with `mx-auto` + max-width
|
|
7
|
+
* gutter. Use for almost every page.
|
|
8
|
+
* - `centered` — vertically centered hero block on a min-height
|
|
9
|
+
* container. Use for splash / 404 / empty-route pages.
|
|
10
|
+
* - `bare` — no container at all. Use when the page wants to own
|
|
11
|
+
* its own gutter (e.g. a settings page with side-by-side panels
|
|
12
|
+
* that already pad themselves).
|
|
13
|
+
*/
|
|
14
|
+
variant?: 'default' | 'centered' | 'bare'
|
|
15
|
+
maxWidth?: 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | 'full'
|
|
16
|
+
padding?: 'none' | 'sm' | 'md' | 'lg'
|
|
17
|
+
spacing?: 'none' | 'sm' | 'md' | 'lg'
|
|
18
|
+
/** Optional min-height utility for the `centered` variant. */
|
|
19
|
+
minHeight?: 'screen' | '70vh' | '80vh' | '90vh'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
variant = 'default',
|
|
24
|
+
maxWidth = '7xl',
|
|
25
|
+
padding = 'md',
|
|
26
|
+
spacing = 'md',
|
|
27
|
+
minHeight = '80vh',
|
|
28
|
+
} = defineProps<PageLayoutProps>()
|
|
29
|
+
|
|
30
|
+
const widthClasses = {
|
|
31
|
+
md: 'max-w-md',
|
|
32
|
+
lg: 'max-w-lg',
|
|
33
|
+
xl: 'max-w-xl',
|
|
34
|
+
'2xl': 'max-w-2xl',
|
|
35
|
+
'3xl': 'max-w-3xl',
|
|
36
|
+
'4xl': 'max-w-4xl',
|
|
37
|
+
'5xl': 'max-w-5xl',
|
|
38
|
+
'6xl': 'max-w-6xl',
|
|
39
|
+
'7xl': 'max-w-7xl',
|
|
40
|
+
full: 'max-w-full',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const paddingClasses = {
|
|
44
|
+
none: '',
|
|
45
|
+
sm: 'px-4 pt-4 pb-8',
|
|
46
|
+
md: 'px-6 pt-6 pb-12',
|
|
47
|
+
lg: 'px-8 pt-8 pb-16',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const spacingClasses = {
|
|
51
|
+
none: '',
|
|
52
|
+
sm: 'space-y-3',
|
|
53
|
+
md: 'space-y-6',
|
|
54
|
+
lg: 'space-y-8',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const minHeightClasses = {
|
|
58
|
+
'screen': 'min-h-screen',
|
|
59
|
+
'70vh': 'min-h-[70vh]',
|
|
60
|
+
'80vh': 'min-h-[80vh]',
|
|
61
|
+
'90vh': 'min-h-[90vh]',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const widthClass = widthClasses[maxWidth] || widthClasses['7xl']
|
|
65
|
+
const paddingClass = paddingClasses[padding] || paddingClasses.md
|
|
66
|
+
const spacingClass = spacingClasses[spacing] || spacingClasses.md
|
|
67
|
+
const minHeightClass = minHeightClasses[minHeight] || minHeightClasses['80vh']
|
|
68
|
+
|
|
69
|
+
const defaultClass = ['mx-auto', widthClass, paddingClass, spacingClass].filter(Boolean).join(' ')
|
|
70
|
+
const centeredClass = ['flex items-center justify-center', minHeightClass, paddingClass].filter(Boolean).join(' ')
|
|
71
|
+
</script>
|
|
72
|
+
@if(variant === 'bare')
|
|
73
|
+
<slot />
|
|
74
|
+
@elseif(variant === 'centered')
|
|
75
|
+
<div class="{{ centeredClass }}">
|
|
76
|
+
<div class="mx-auto w-full {{ widthClass }} {{ spacingClass }}">
|
|
77
|
+
<slot />
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
@else
|
|
81
|
+
<div class="{{ defaultClass }}">
|
|
82
|
+
<slot />
|
|
83
|
+
</div>
|
|
84
|
+
@endif
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
interface PaginationProps {
|
|
3
|
+
currentPage?: number
|
|
4
|
+
totalPages?: number
|
|
5
|
+
totalItems?: number
|
|
6
|
+
itemsPerPage?: number
|
|
7
|
+
showItemsPerPage?: boolean
|
|
8
|
+
maxVisiblePages?: number
|
|
9
|
+
itemsPerPageOptions?: string
|
|
10
|
+
baseUrl?: string
|
|
11
|
+
showTotalItems?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { currentPage = 1, totalPages = 1, totalItems = 0, itemsPerPage = 10, showItemsPerPage = false, maxVisiblePages = 5, itemsPerPageOptions = [10, 25, 50, 100], baseUrl = '', showTotalItems = true } = defineProps<PaginationProps>()
|
|
15
|
+
|
|
16
|
+
// Calculate visible page numbers
|
|
17
|
+
function getVisiblePages() {
|
|
18
|
+
const pages = []
|
|
19
|
+
const total = totalPages
|
|
20
|
+
const current = currentPage
|
|
21
|
+
const max = maxVisiblePages
|
|
22
|
+
|
|
23
|
+
if (total <= max) {
|
|
24
|
+
for (let i = 1; i <= total; i++) {
|
|
25
|
+
pages.push(i)
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
pages.push(1)
|
|
29
|
+
let start = Math.max(2, current - Math.floor((max - 3) / 2))
|
|
30
|
+
let end = Math.min(total - 1, start + max - 4)
|
|
31
|
+
if (end === total - 1) {
|
|
32
|
+
start = Math.max(2, end - (max - 4))
|
|
33
|
+
}
|
|
34
|
+
if (start > 2) {
|
|
35
|
+
pages.push('...')
|
|
36
|
+
}
|
|
37
|
+
for (let i = start; i <= end; i++) {
|
|
38
|
+
pages.push(i)
|
|
39
|
+
}
|
|
40
|
+
if (end < total - 1) {
|
|
41
|
+
pages.push('...')
|
|
42
|
+
}
|
|
43
|
+
pages.push(total)
|
|
44
|
+
}
|
|
45
|
+
return pages
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const visiblePages = getVisiblePages()
|
|
49
|
+
|
|
50
|
+
// Calculate item range
|
|
51
|
+
const itemRangeStart = (currentPage - 1) * itemsPerPage + 1
|
|
52
|
+
const itemRangeEnd = Math.min(currentPage * itemsPerPage, totalItems)
|
|
53
|
+
|
|
54
|
+
function getPageUrl(page) {
|
|
55
|
+
return baseUrl ? `${baseUrl}?page=${page}` : `?page=${page}`
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
59
|
+
<!-- Left: Items info & per page selector -->
|
|
60
|
+
<div class="flex gap-4 items-center text-neutral-500 text-sm dark:text-neutral-400">
|
|
61
|
+
<!-- Items per page -->
|
|
62
|
+
@if(showItemsPerPage && itemsPerPage)
|
|
63
|
+
<div class="flex gap-2 items-center">
|
|
64
|
+
<span>Show</span>
|
|
65
|
+
<select
|
|
66
|
+
class="text-neutral-700 text-sm dark:text-neutral-200 bg-white dark:bg-neutral-800 border-neutral-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:ring-blue-500"
|
|
67
|
+
data-items-per-page
|
|
68
|
+
>
|
|
69
|
+
@foreach(itemsPerPageOptions as option)
|
|
70
|
+
<option value="{{ option }}" @if(option == itemsPerPage) selected @endif>
|
|
71
|
+
{{ option }}
|
|
72
|
+
</option>
|
|
73
|
+
@endforeach
|
|
74
|
+
</select>
|
|
75
|
+
<span>per page</span>
|
|
76
|
+
</div>
|
|
77
|
+
@endif
|
|
78
|
+
|
|
79
|
+
<!-- Total items info -->
|
|
80
|
+
@if(showTotalItems && totalItems > 0)
|
|
81
|
+
<div>
|
|
82
|
+
<span class="font-medium text-neutral-700 dark:text-neutral-200">
|
|
83
|
+
{{ itemRangeStart }}-{{ itemRangeEnd }}
|
|
84
|
+
</span>
|
|
85
|
+
of
|
|
86
|
+
<span class="font-medium text-neutral-700 dark:text-neutral-200">
|
|
87
|
+
{{ totalItems }}
|
|
88
|
+
</span>
|
|
89
|
+
items
|
|
90
|
+
</div>
|
|
91
|
+
@endif
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Right: Page navigation -->
|
|
95
|
+
<nav class="flex gap-1 items-center" aria-label="Pagination">
|
|
96
|
+
<!-- Previous button -->
|
|
97
|
+
<a
|
|
98
|
+
href="{{ currentPage > 1 ? getPageUrl(currentPage - 1) : '#' }}"
|
|
99
|
+
class="p-2 rounded-lg text-sm font-medium transition-colors {{ currentPage == 1 ? 'text-neutral-300 dark:text-neutral-600 cursor-not-allowed pointer-events-none' : 'text-neutral-500 hover:text-neutral-700 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:text-neutral-200 dark:hover:bg-neutral-800' }}"
|
|
100
|
+
>
|
|
101
|
+
<div class="h-5 w-5 i-hugeicons-arrow-left-01"></div>
|
|
102
|
+
</a>
|
|
103
|
+
|
|
104
|
+
<!-- Page numbers -->
|
|
105
|
+
@foreach(visiblePages as page)
|
|
106
|
+
@if(page == '...')
|
|
107
|
+
<span class="px-2 text-neutral-400 dark:text-neutral-500">...</span>
|
|
108
|
+
@else
|
|
109
|
+
<a
|
|
110
|
+
href="{{ getPageUrl(page) }}"
|
|
111
|
+
class="min-w-[2.25rem] h-9 px-3 rounded-lg text-sm font-medium transition-colors flex items-center justify-center {{ page == currentPage ? 'bg-blue-600 text-white' : 'text-neutral-600 hover:text-neutral-800 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:text-neutral-200 dark:hover:bg-neutral-800' }}"
|
|
112
|
+
>
|
|
113
|
+
{{ page }}
|
|
114
|
+
</a>
|
|
115
|
+
@endif
|
|
116
|
+
@endforeach
|
|
117
|
+
|
|
118
|
+
<!-- Next button -->
|
|
119
|
+
<a
|
|
120
|
+
href="{{ currentPage < totalPages ? getPageUrl(currentPage + 1) : '#' }}"
|
|
121
|
+
class="p-2 rounded-lg text-sm font-medium transition-colors {{ currentPage == totalPages ? 'text-neutral-300 dark:text-neutral-600 cursor-not-allowed pointer-events-none' : 'text-neutral-500 hover:text-neutral-700 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:text-neutral-200 dark:hover:bg-neutral-800' }}"
|
|
122
|
+
>
|
|
123
|
+
<div class="h-5 w-5 i-hugeicons-arrow-right-01"></div>
|
|
124
|
+
</a>
|
|
125
|
+
</nav>
|
|
126
|
+
</div>
|