@stacksjs/defaults 0.72.97 → 0.72.99
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/ai/skills/stacks-auth/SKILL.md +35 -5
- package/ai/skills/stacks-config/SKILL.md +11 -1
- package/ai/skills/stacks-desktop/SKILL.md +46 -0
- package/ai/skills/stacks-env/SKILL.md +26 -0
- package/ai/skills/stacks-events/SKILL.md +76 -60
- package/ai/skills/stacks-jobs/SKILL.md +3 -0
- package/ai/skills/stacks-listeners/SKILL.md +39 -9
- package/ai/skills/stacks-middleware/SKILL.md +50 -12
- package/ai/skills/stacks-queue/SKILL.md +5 -0
- package/ai/skills/stacks-router/SKILL.md +17 -7
- package/ai/skills/stacks-scheduler/SKILL.md +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +1 -1
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +1 -1
- package/app/Actions/Dashboard/Content/content-input.ts +2 -2
- package/app/Actions/Dashboard/Content/post-input.ts +4 -4
- package/app/Actions/Dashboard/Email/inbox-request.ts +1 -1
- package/app/Actions/Dashboard/Email/mail-preference.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignStoreAction.ts +1 -1
- package/app/Actions/Dashboard/Marketing/CampaignUpdateAction.ts +1 -1
- package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +5 -5
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +2 -2
- package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +2 -2
- package/app/Events.ts +9 -4
- package/app/Gates.ts +83 -85
- package/app/Listener.ts +34 -81
- package/app/Middleware.ts +22 -7
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/storage/framework/server/tsconfig.docker.json +1 -2
- package/resources/components/Buttons/Counter.stx +30 -21
- package/resources/components/Dashboard/Analytics/WebAnalyticsDashboard.stx +1 -3
- package/resources/components/Dashboard/Auth/ForgotPasswordDashboard.stx +1 -1
- package/resources/components/Dashboard/Auth/LoginDashboard.stx +2 -1
- package/resources/components/Dashboard/Auth/RegisterDashboard.stx +2 -1
- package/resources/components/Dashboard/Auth/ResetPasswordDashboard.stx +1 -1
- package/resources/components/Dashboard/Ci/CiDashboard.stx +9 -9
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +7 -7
- package/resources/components/Dashboard/Commerce/CommerceOverviewCharts.stx +2 -1
- package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
- package/resources/components/Dashboard/Content/BlogDashboard.stx +37 -15
- package/resources/components/Dashboard/Content/ContentDashboard.stx +3 -26
- package/resources/components/Dashboard/Email/EmailActivityDashboard.stx +2 -15
- package/resources/components/Dashboard/Environment/EnvironmentDashboard.stx +9 -1
- package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +15 -13
- package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +10 -8
- package/resources/components/Dashboard/Kanban/KanbanCardDialog.stx +10 -10
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +3 -3
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +4 -1
- package/resources/components/Dashboard/Navbar.stx +1 -1
- package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +11 -6
- package/resources/components/Dashboard/Settings/SettingsDashboard.stx +2 -2
- package/resources/components/Dashboard/Transaction/index.stx +6 -6
- package/resources/components/Dashboard/UI/Avatar.stx +2 -2
- package/resources/components/Dashboard/UI/DropdownItem.stx +4 -4
- package/resources/components/Dashboard/UI/WindowControls.stx +17 -2
- package/resources/components/Docs/Demo/ComboboxDemo.stx +8 -3
- package/resources/components/Docs/Demo/DropdownDemo.stx +3 -3
- package/resources/components/Docs/Demo/StepperDemo.stx +1 -1
- package/resources/components/Forum/ForumLayout.stx +1 -1
- package/resources/components/Forum/ForumReplyForm.stx +1 -1
- package/resources/components/Marketing/ComingSoon.stx +5 -5
- package/resources/components/Storefront/CartDrawer.stx +44 -22
- package/resources/components/Storefront/ProductGallery.stx +3 -2
- package/resources/components/Storefront/QuantitySelector.stx +12 -7
- package/resources/functions/storefront/cart-cookie.ts +58 -0
- package/resources/layouts/storefront.stx +3 -2
- package/resources/views/cart.stx +19 -3
- package/resources/views/checkout/contact.stx +14 -3
- package/resources/views/checkout/payment.stx +14 -3
- package/resources/views/checkout/shipping.stx +14 -3
- package/resources/views/cms/blocks/form.stx +16 -11
- package/resources/views/cms/page.stx +28 -0
- package/resources/views/coming-soon.stx +0 -6
- package/resources/views/orders/[id].stx +19 -3
- package/views/dashboard/composables/index.ts +1 -1
- package/views/dashboard/composables/useChart.ts +26 -14
- package/views/dashboard/layouts/default.stx +42 -25
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
const columns = derived(() => kanban.currentColumns())
|
|
30
30
|
// Inline composer state. Each column has its own (independent) "add
|
|
31
31
|
// card" composer; the boolean tracks whether the input is open.
|
|
32
|
-
const composingCardForColumnId = state(null)
|
|
32
|
+
const composingCardForColumnId = state<number | null>(null)
|
|
33
33
|
const draftCardTitle = state('')
|
|
34
34
|
const composingNewColumn = state(false)
|
|
35
35
|
const draftColumnName = state('')
|
|
@@ -111,15 +111,16 @@
|
|
|
111
111
|
// Column color → header accent class. Free-form string in the
|
|
112
112
|
// model, mapped here so a new hue without an explicit mapping
|
|
113
113
|
// falls back to slate.
|
|
114
|
-
function columnAccentClass(color) {
|
|
115
|
-
|
|
114
|
+
function columnAccentClass(color: string) {
|
|
115
|
+
const swatches: Record<string, string> = {
|
|
116
116
|
slate: 'bg-slate-100 dark:bg-slate-800',
|
|
117
117
|
amber: 'bg-amber-100 dark:bg-amber-900/40',
|
|
118
118
|
blue: 'bg-blue-100 dark:bg-blue-900/40',
|
|
119
119
|
emerald: 'bg-emerald-100 dark:bg-emerald-900/40',
|
|
120
120
|
red: 'bg-red-100 dark:bg-red-900/40',
|
|
121
121
|
violet: 'bg-violet-100 dark:bg-violet-900/40',
|
|
122
|
-
}
|
|
122
|
+
}
|
|
123
|
+
return swatches[color] || 'bg-slate-100 dark:bg-slate-800'
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// Pull the id from the URL through the navigation composable.
|
|
@@ -130,7 +131,7 @@
|
|
|
130
131
|
|
|
131
132
|
// ─── Composer handlers ────────────────────────────────────────────
|
|
132
133
|
|
|
133
|
-
function openCardComposer(columnId) {
|
|
134
|
+
function openCardComposer(columnId: number) {
|
|
134
135
|
composingCardForColumnId.set(columnId)
|
|
135
136
|
draftCardTitle.set('')
|
|
136
137
|
}
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
composingCardForColumnId.set(null)
|
|
139
140
|
draftCardTitle.set('')
|
|
140
141
|
}
|
|
141
|
-
async function submitCard(columnId) {
|
|
142
|
+
async function submitCard(columnId: number) {
|
|
142
143
|
const title = draftCardTitle().trim()
|
|
143
144
|
if (!title) {
|
|
144
145
|
closeCardComposer()
|
|
@@ -147,7 +148,7 @@
|
|
|
147
148
|
await kanban.createCard({ columnId, title })
|
|
148
149
|
closeCardComposer()
|
|
149
150
|
}
|
|
150
|
-
function isComposingFor(columnId) {
|
|
151
|
+
function isComposingFor(columnId: number) {
|
|
151
152
|
return composingCardForColumnId() === columnId
|
|
152
153
|
}
|
|
153
154
|
|
|
@@ -165,21 +166,22 @@
|
|
|
165
166
|
closeColumnComposer()
|
|
166
167
|
return
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
const current = board()
|
|
170
|
+
if (!current) return
|
|
171
|
+
await kanban.createColumn({ boardId: current.id, name })
|
|
170
172
|
closeColumnComposer()
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
// ─── Card detail launcher ─────────────────────────────────────────
|
|
174
176
|
|
|
175
|
-
function openDetail(cardId) {
|
|
177
|
+
function openDetail(cardId: number) {
|
|
176
178
|
kanban.loadUsers() // background-fetch for assignee picker
|
|
177
179
|
kanban.openCardDetail(cardId)
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
// Label color → tailwind hue. Mirrors the column-accent map but
|
|
181
183
|
// returns a more saturated chip class.
|
|
182
|
-
function labelChipClass(color) {
|
|
184
|
+
function labelChipClass(color: string) {
|
|
183
185
|
return ({
|
|
184
186
|
slate: 'bg-slate-100 text-slate-700 dark:bg-slate-800/60 dark:text-slate-200',
|
|
185
187
|
amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200',
|
|
@@ -191,7 +193,7 @@
|
|
|
191
193
|
})[color] || 'bg-slate-100 text-slate-700 dark:bg-slate-800/60 dark:text-slate-200'
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
function initialsOf(name, email) {
|
|
196
|
+
function initialsOf(name: string | null | undefined, email: string | null | undefined) {
|
|
195
197
|
const source = (name || email || '?').trim()
|
|
196
198
|
const parts = source.split(/\s+/).filter(Boolean)
|
|
197
199
|
if (parts.length === 0) return '?'
|
|
@@ -199,7 +201,7 @@
|
|
|
199
201
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
|
|
200
202
|
}
|
|
201
203
|
|
|
202
|
-
function formatDueDate(iso) {
|
|
204
|
+
function formatDueDate(iso: string | null | undefined) {
|
|
203
205
|
if (!iso) return ''
|
|
204
206
|
const d = new Date(iso)
|
|
205
207
|
if (Number.isNaN(d.getTime())) return ''
|
|
@@ -99,18 +99,19 @@
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function colorSwatchClass(c) {
|
|
103
|
-
|
|
102
|
+
function colorSwatchClass(c: string) {
|
|
103
|
+
const swatches: Record<string, string> = {
|
|
104
104
|
violet: 'bg-violet-500',
|
|
105
105
|
blue: 'bg-blue-500',
|
|
106
106
|
emerald: 'bg-emerald-500',
|
|
107
107
|
amber: 'bg-amber-500',
|
|
108
108
|
rose: 'bg-rose-500',
|
|
109
109
|
slate: 'bg-slate-500',
|
|
110
|
-
}
|
|
110
|
+
}
|
|
111
|
+
return swatches[c] || 'bg-violet-500'
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
function colorButtonClass(c) {
|
|
114
|
+
function colorButtonClass(c: string) {
|
|
114
115
|
const base = `w-6 h-6 rounded-full ${colorSwatchClass(c)} cursor-pointer transition-all `
|
|
115
116
|
return draftColor() === c
|
|
116
117
|
? base + 'ring-2 ring-offset-2 ring-violet-500 dark:ring-offset-zinc-950'
|
|
@@ -120,18 +121,19 @@
|
|
|
120
121
|
// Color → tailwind class. Free-form string in the model, mapped here
|
|
121
122
|
// so the UI stays stable when a new hue is added without an explicit
|
|
122
123
|
// mapping (falls back to violet).
|
|
123
|
-
function accentClass(color) {
|
|
124
|
-
|
|
124
|
+
function accentClass(color: string) {
|
|
125
|
+
const swatches: Record<string, string> = {
|
|
125
126
|
violet: 'bg-violet-500',
|
|
126
127
|
blue: 'bg-blue-500',
|
|
127
128
|
emerald: 'bg-emerald-500',
|
|
128
129
|
amber: 'bg-amber-500',
|
|
129
130
|
rose: 'bg-rose-500',
|
|
130
131
|
slate: 'bg-slate-500',
|
|
131
|
-
}
|
|
132
|
+
}
|
|
133
|
+
return swatches[color] || 'bg-violet-500'
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
function cardCountText(n) {
|
|
136
|
+
function cardCountText(n: number) {
|
|
135
137
|
if (n === 0) return 'No cards'
|
|
136
138
|
if (n === 1) return '1 card'
|
|
137
139
|
return n + ' cards'
|
|
@@ -214,7 +214,7 @@ function initialsOf(name: string | null, email: string | null): string {
|
|
|
214
214
|
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase()
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
function formatTimestamp(iso: string | null): string {
|
|
217
|
+
function formatTimestamp(iso: string | null | undefined): string {
|
|
218
218
|
if (!iso)
|
|
219
219
|
return ''
|
|
220
220
|
const date = new Date(iso)
|
|
@@ -286,7 +286,7 @@ function formatTimestamp(iso: string | null): string {
|
|
|
286
286
|
</div>
|
|
287
287
|
</div>
|
|
288
288
|
|
|
289
|
-
<div :if="
|
|
289
|
+
<div :if="cardComments().length === 0 && !loadingCard()" class="italic text-zinc-400 text-xs">No comments yet.</div>
|
|
290
290
|
<ul class="space-y-3">
|
|
291
291
|
<li :for="comment in cardComments" class="flex gap-3">
|
|
292
292
|
<span class="inline-flex flex-shrink-0 items-center justify-center h-7 w-7 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(comment.authorName, comment.authorEmail)"></span>
|
|
@@ -334,14 +334,14 @@ function formatTimestamp(iso: string | null): string {
|
|
|
334
334
|
|
|
335
335
|
<div :if="!showLabelPicker()" class="flex flex-wrap gap-1">
|
|
336
336
|
<span :for="label in cardLabels" :class="`px-2 py-0.5 text-xs font-medium rounded ${labelChipClass(label.color)}`" :text="label.name"></span>
|
|
337
|
-
<span :if="
|
|
337
|
+
<span :if="cardLabels().length === 0" class="italic text-zinc-400 text-xs">None</span>
|
|
338
338
|
</div>
|
|
339
339
|
|
|
340
340
|
<div :if="showLabelPicker()" class="space-y-2">
|
|
341
341
|
<div :for="label in allLabels" :class="`flex items-center gap-1 px-1 py-0.5 w-full rounded ${labelChipClass(label.color)}`">
|
|
342
|
-
<button type="button" class="flex flex-1 items-center justify-between px-1 py-0.5 min-w-0 text-left text-xs rounded hover:opacity-90" :aria-pressed="String(
|
|
342
|
+
<button type="button" class="flex flex-1 items-center justify-between px-1 py-0.5 min-w-0 text-left text-xs rounded hover:opacity-90" :aria-pressed="String(cardLabels().some(cardLabel => cardLabel.id === label.id))" @click="toggleLabel(label.id)">
|
|
343
343
|
<span class="truncate" :text="label.name"></span>
|
|
344
|
-
<span :if="
|
|
344
|
+
<span :if="cardLabels().some(cardLabel => cardLabel.id === label.id)" class="h-3 w-3 i-hugeicons-tick-02"></span>
|
|
345
345
|
</button>
|
|
346
346
|
<Button variant="ghost" size="xs" iconOnly :ariaLabel="'Delete label ' + label.name" @click="requestDelete('label', label.id)">
|
|
347
347
|
<span class="block h-3 w-3 text-red-500 i-hugeicons-cancel-01"></span>
|
|
@@ -376,15 +376,15 @@ function formatTimestamp(iso: string | null): string {
|
|
|
376
376
|
<span class="inline-flex items-center justify-center h-6 w-6 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(assignee.name, assignee.email)"></span>
|
|
377
377
|
<span :text="assignee.name || assignee.email || 'Unknown'"></span>
|
|
378
378
|
</div>
|
|
379
|
-
<span :if="
|
|
379
|
+
<span :if="cardAssignees().length === 0" class="italic text-zinc-400 text-xs">Unassigned</span>
|
|
380
380
|
</div>
|
|
381
381
|
|
|
382
382
|
<div :if="showAssigneePicker()" class="overflow-y-auto space-y-1 max-h-48">
|
|
383
383
|
<p :if="kanban.users().length === 0" class="italic text-zinc-400 text-xs">No users to assign.</p>
|
|
384
|
-
<button :for="user in kanban.users" type="button" :aria-pressed="String(
|
|
384
|
+
<button :for="user in kanban.users" type="button" :aria-pressed="String(cardAssignees().some(assignee => assignee.userId === user.id))" @click="toggleAssignee(user.id)" class="flex gap-2 items-center px-2 py-1.5 w-full text-left text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded">
|
|
385
385
|
<span class="inline-flex flex-shrink-0 items-center justify-center h-6 w-6 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(user.name, user.email)"></span>
|
|
386
386
|
<span class="flex-1 text-zinc-700 truncate dark:text-zinc-200" :text="user.name || user.email"></span>
|
|
387
|
-
<span :if="
|
|
387
|
+
<span :if="cardAssignees().some(assignee => assignee.userId === user.id)" class="h-3.5 w-3.5 text-violet-500 i-hugeicons-tick-02"></span>
|
|
388
388
|
</button>
|
|
389
389
|
</div>
|
|
390
390
|
</section>
|
|
@@ -395,8 +395,8 @@ function formatTimestamp(iso: string | null): string {
|
|
|
395
395
|
</section>
|
|
396
396
|
|
|
397
397
|
<section class="pt-3 border-t border-zinc-200 dark:border-zinc-800">
|
|
398
|
-
<p class="text-[10px] text-zinc-400">Created <span :text="formatTimestamp(openCard()
|
|
399
|
-
<p :if="openCard()
|
|
398
|
+
<p class="text-[10px] text-zinc-400">Created <span :text="formatTimestamp(openCard()?.createdAt)"></span></p>
|
|
399
|
+
<p :if="openCard()?.updatedAt && openCard()?.updatedAt !== openCard()?.createdAt" class="text-[10px] text-zinc-400">Updated <span :text="formatTimestamp(openCard()?.updatedAt)"></span></p>
|
|
400
400
|
</section>
|
|
401
401
|
</aside>
|
|
402
402
|
</div>
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
|
|
64
64
|
const activeTab = state('roles')
|
|
65
65
|
|
|
66
|
-
function tabClass(id) {
|
|
66
|
+
function tabClass(id: string) {
|
|
67
67
|
const base = 'px-4 py-2 font-medium text-sm whitespace-nowrap bg-transparent border-b-2 transition-colors cursor-pointer '
|
|
68
68
|
return activeTab() === id
|
|
69
69
|
? base + 'border-violet-400 text-violet-600 dark:text-violet-400'
|
|
70
70
|
: base + 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function selectTab(id) {
|
|
73
|
+
function selectTab(id: string) {
|
|
74
74
|
activeTab.set(id)
|
|
75
75
|
selectedUser.set(null)
|
|
76
76
|
selectedRole.set(null)
|
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
|
|
316
316
|
// ─── Tiny formatters ──────────────────────────────────────────
|
|
317
317
|
|
|
318
|
-
function formatDate(iso) {
|
|
318
|
+
function formatDate(iso: string | null | undefined) {
|
|
319
319
|
if (!iso) return ''
|
|
320
320
|
const d = new Date(iso)
|
|
321
321
|
if (Number.isNaN(d.getTime())) return ''
|
|
@@ -9,7 +9,10 @@ const liveAbortText = useReactiveProp('abortText', 'Cancel')
|
|
|
9
9
|
const liveShow = useReactiveProp('show', true)
|
|
10
10
|
const emit = defineEmits()
|
|
11
11
|
|
|
12
|
-
const normalizedType = derived(()
|
|
12
|
+
const normalizedType = derived((): Exclude<AlertType, 'danger'> => {
|
|
13
|
+
const type = liveType()
|
|
14
|
+
return type === 'danger' ? 'error' : type
|
|
15
|
+
})
|
|
13
16
|
const iconClass = derived(() => ({
|
|
14
17
|
info: 'i-hugeicons-information-circle text-blue-600',
|
|
15
18
|
warning: 'i-hugeicons-alert-02 text-yellow-600',
|
|
@@ -9,7 +9,7 @@ interface NavbarProps {
|
|
|
9
9
|
|
|
10
10
|
const { isSidebarCollapsed = false, currentPath = '/' } = defineProps<NavbarProps>()
|
|
11
11
|
|
|
12
|
-
function isActiveRoute(path) {
|
|
12
|
+
function isActiveRoute(path: string) {
|
|
13
13
|
return currentPath === path || currentPath.startsWith(path + '/')
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -12,9 +12,14 @@ export const navSections = buildWebSidebarSections()
|
|
|
12
12
|
label: section.label,
|
|
13
13
|
count: section.items.length,
|
|
14
14
|
}))
|
|
15
|
+
|
|
16
|
+
// Declared so the client block below gets `navSections` typed rather than
|
|
17
|
+
// inferred off the implicit bridge.
|
|
18
|
+
defineClientPayload({ navSections })
|
|
15
19
|
</script>
|
|
16
20
|
|
|
17
21
|
<script client>
|
|
22
|
+
import type { ColorMode, SidebarStyle } from '~/storage/framework/defaults/views/dashboard/composables/useAppearance'
|
|
18
23
|
import {
|
|
19
24
|
COLOR_MODE_OPTIONS,
|
|
20
25
|
SIDEBAR_STYLE_OPTIONS,
|
|
@@ -37,20 +42,20 @@ const hiddenSections = appearance.hiddenSections
|
|
|
37
42
|
|
|
38
43
|
const hiddenCount = derived(() => hiddenSections().length)
|
|
39
44
|
|
|
40
|
-
function selectStyle(id) {
|
|
45
|
+
function selectStyle(id: SidebarStyle) {
|
|
41
46
|
appearance.setSidebarStyle(id)
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
function selectColorMode(id) {
|
|
49
|
+
function selectColorMode(id: ColorMode) {
|
|
45
50
|
appearance.setColorMode(id)
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
function isVisible(id) {
|
|
53
|
+
function isVisible(id: string) {
|
|
49
54
|
return !hiddenSections().includes(id)
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
function toggleSection(id, event) {
|
|
53
|
-
appearance.toggleSection(id, event.target.checked)
|
|
57
|
+
function toggleSection(id: string, event: Event) {
|
|
58
|
+
appearance.toggleSection(id, (event.target as HTMLInputElement).checked)
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
function resetAll() {
|
|
@@ -64,7 +69,7 @@ function resetAll() {
|
|
|
64
69
|
// upstream. It stays because it is the better shape regardless: one selector
|
|
65
70
|
// per state in the block below, rather than two class strings kept in sync by
|
|
66
71
|
// hand inside a ternary.
|
|
67
|
-
const selected = (a, b) => String(a === b)
|
|
72
|
+
const selected = (a: unknown, b: unknown) => String(a === b)
|
|
68
73
|
</script>
|
|
69
74
|
|
|
70
75
|
<style>
|
|
@@ -247,8 +247,8 @@ void loadFiles()
|
|
|
247
247
|
@elseif(selectedFile())
|
|
248
248
|
<div class="flex flex-wrap gap-4 items-center justify-between px-6 py-5 border-b border-neutral-200/60 dark:border-white/5">
|
|
249
249
|
<div>
|
|
250
|
-
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">{{ selectedFile()
|
|
251
|
-
<p class="mt-1 font-mono text-neutral-500 text-xs dark:text-neutral-400">config/{{ selectedFile()
|
|
250
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">{{ selectedFile()?.title }}</h2>
|
|
251
|
+
<p class="mt-1 font-mono text-neutral-500 text-xs dark:text-neutral-400">config/{{ selectedFile()?.name }}.ts</p>
|
|
252
252
|
</div>
|
|
253
253
|
<div class="flex gap-2 items-center">
|
|
254
254
|
<Badge :if="isDirty()" variant="warning" size="sm">Unsaved changes</Badge>
|
|
@@ -5,21 +5,21 @@ interface indexProps {
|
|
|
5
5
|
|
|
6
6
|
const { transactionHistory = [] } = defineProps<indexProps>()
|
|
7
7
|
|
|
8
|
-
function formatTimestampDate(timestamp) {
|
|
8
|
+
function formatTimestampDate(timestamp: number) {
|
|
9
9
|
if (!timestamp) return ''
|
|
10
10
|
const date = new Date(timestamp * 1000)
|
|
11
11
|
return date.toLocaleDateString()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function getTransactionType(lineData) {
|
|
15
|
-
return lineData[0]?.plan?.interval || ''
|
|
14
|
+
function getTransactionType(lineData: Array<{ plan?: { interval?: string } }> | undefined) {
|
|
15
|
+
return lineData?.[0]?.plan?.interval || ''
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function getUnitPrice(lineData) {
|
|
19
|
-
return (Number(lineData[0]?.price?.unit_amount || 0) / 100).toFixed(2)
|
|
18
|
+
function getUnitPrice(lineData: Array<{ price?: { unit_amount?: number | string } }> | undefined) {
|
|
19
|
+
return (Number(lineData?.[0]?.price?.unit_amount || 0) / 100).toFixed(2)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function getLastDigits(paymentIntent) {
|
|
22
|
+
function getLastDigits(paymentIntent: { payment_method?: { card?: { last4?: string } } } | undefined) {
|
|
23
23
|
return paymentIntent?.payment_method?.card?.last4 || ''
|
|
24
24
|
}
|
|
25
25
|
</script>
|
|
@@ -43,7 +43,7 @@ const statusSizes = {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Generate initials from name
|
|
46
|
-
function getInitials(fullName) {
|
|
46
|
+
function getInitials(fullName: string) {
|
|
47
47
|
if (!fullName) return ''
|
|
48
48
|
const parts = fullName.trim().split(/\s+/)
|
|
49
49
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()
|
|
@@ -66,7 +66,7 @@ const bgColors = [
|
|
|
66
66
|
'bg-pink-500/15 text-pink-600 dark:bg-pink-400/20 dark:text-pink-400'
|
|
67
67
|
]
|
|
68
68
|
|
|
69
|
-
function getBgColorIndex(fullName) {
|
|
69
|
+
function getBgColorIndex(fullName: string) {
|
|
70
70
|
if (!fullName) return 0
|
|
71
71
|
let hash = 0
|
|
72
72
|
for (let i = 0; i < fullName.length; i++) {
|
|
@@ -47,7 +47,7 @@ const classes = getClasses()
|
|
|
47
47
|
>
|
|
48
48
|
<!-- Leading icon slot -->
|
|
49
49
|
<span class="flex items-center justify-center h-4 w-4 text-current opacity-70">
|
|
50
|
-
|
|
50
|
+
<slot name="icon" />
|
|
51
51
|
</span>
|
|
52
52
|
|
|
53
53
|
<!-- Content -->
|
|
@@ -56,7 +56,7 @@ const classes = getClasses()
|
|
|
56
56
|
</span>
|
|
57
57
|
|
|
58
58
|
<!-- Trailing content slot (for shortcuts, badges, etc.) -->
|
|
59
|
-
|
|
59
|
+
<slot name="trailing" />
|
|
60
60
|
</a>
|
|
61
61
|
@else
|
|
62
62
|
<button
|
|
@@ -66,7 +66,7 @@ const classes = getClasses()
|
|
|
66
66
|
>
|
|
67
67
|
<!-- Leading icon slot -->
|
|
68
68
|
<span class="flex items-center justify-center h-4 w-4 text-current opacity-70">
|
|
69
|
-
|
|
69
|
+
<slot name="icon" />
|
|
70
70
|
</span>
|
|
71
71
|
|
|
72
72
|
<!-- Content -->
|
|
@@ -75,6 +75,6 @@ const classes = getClasses()
|
|
|
75
75
|
</span>
|
|
76
76
|
|
|
77
77
|
<!-- Trailing content slot (for shortcuts, badges, etc.) -->
|
|
78
|
-
|
|
78
|
+
<slot name="trailing" />
|
|
79
79
|
</button>
|
|
80
80
|
@endif
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Replica window controls, for chrome the page draws itself.
|
|
3
|
+
|
|
4
|
+
They yield to the real thing. Wherever the platform already drew its own
|
|
5
|
+
close/minimise/zoom buttons - which on macOS is every Craft window that is not
|
|
6
|
+
frameless - rendering these as well puts six circles in the corner, three of
|
|
7
|
+
them the wrong shade, without hover glyphs, and dead to the keyboard. Craft
|
|
8
|
+
says so by setting `--craft-window-controls-replicas: none` before the
|
|
9
|
+
document is parsed, and both variants below read it as a fallback, so a
|
|
10
|
+
browser (where nothing is published) still shows them and neither case needs
|
|
11
|
+
JavaScript or can flash.
|
|
12
|
+
|
|
13
|
+
Use them for a frameless window, where the page really does own its chrome,
|
|
14
|
+
and for a web dashboard that wants the look of one.
|
|
15
|
+
-->
|
|
1
16
|
<script client>
|
|
2
17
|
import { useWindowControls } from '../../../../functions/craft'
|
|
3
18
|
|
|
@@ -41,7 +56,7 @@ async function handleMaximize(): Promise<void> {
|
|
|
41
56
|
}
|
|
42
57
|
</script>
|
|
43
58
|
|
|
44
|
-
<div :if="liveVariant() === 'macos'" class="
|
|
59
|
+
<div :if="liveVariant() === 'macos'" class="items-center" style="display: var(--craft-window-controls-replicas, flex)" :class="config().gap" data-window-controls>
|
|
45
60
|
<span class="flex gap-1.5 items-center">
|
|
46
61
|
<button
|
|
47
62
|
type="button"
|
|
@@ -85,7 +100,7 @@ async function handleMaximize(): Promise<void> {
|
|
|
85
100
|
</span>
|
|
86
101
|
</div>
|
|
87
102
|
|
|
88
|
-
<div :else class="
|
|
103
|
+
<div :else class="items-center" style="display: var(--craft-window-controls-replicas, flex)" data-window-controls>
|
|
89
104
|
<button
|
|
90
105
|
type="button"
|
|
91
106
|
class="flex items-center justify-center h-8 w-12 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700 transition-colors"
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
interface ComboboxItem {
|
|
3
|
+
id: number
|
|
4
|
+
name: string
|
|
5
|
+
}
|
|
6
|
+
|
|
2
7
|
interface ComboboxDemoProps {
|
|
3
|
-
listItems?:
|
|
4
|
-
selected?:
|
|
8
|
+
listItems?: ComboboxItem[]
|
|
9
|
+
selected?: ComboboxItem | null
|
|
5
10
|
query?: string
|
|
6
11
|
isOpen?: boolean
|
|
7
12
|
}
|
|
@@ -13,7 +18,7 @@ const { listItems = [
|
|
|
13
18
|
{ id: 4, name: 'Michael Vincent' },
|
|
14
19
|
{ id: 5, name: 'Blake Ayer' },
|
|
15
20
|
], selected = null, query = '', isOpen = false } = defineProps<ComboboxDemoProps>()
|
|
16
|
-
function filterItems(items, query) {
|
|
21
|
+
function filterItems(items: ComboboxItem[], query: string): ComboboxItem[] {
|
|
17
22
|
if (query === '') return items
|
|
18
23
|
return items.filter(item => item.name.toLowerCase().includes(query.toLowerCase()))
|
|
19
24
|
}
|
|
@@ -21,9 +21,9 @@ const { menuItems = [
|
|
|
21
21
|
{ divider: true },
|
|
22
22
|
{ icon: 'i-hugeicons-trash-20-solid', label: 'Delete', variant: 'danger' },
|
|
23
23
|
], isOpen = false } = defineProps<DropdownDemoProps>()
|
|
24
|
-
function getMenuSections(items) {
|
|
25
|
-
const sections = []
|
|
26
|
-
let currentSection = []
|
|
24
|
+
function getMenuSections(items: DropdownMenuItem[]): DropdownMenuItem[][] {
|
|
25
|
+
const sections: DropdownMenuItem[][] = []
|
|
26
|
+
let currentSection: DropdownMenuItem[] = []
|
|
27
27
|
|
|
28
28
|
for (const item of items) {
|
|
29
29
|
if (item.divider) {
|
|
@@ -10,7 +10,7 @@ const { currentStep = 0, steps = [
|
|
|
10
10
|
{ title: 'Step 3', description: 'Third step', icon: 'i-heroicons-check' },
|
|
11
11
|
] } = defineProps<StepperDemoProps>()
|
|
12
12
|
|
|
13
|
-
function getStepContent(step) {
|
|
13
|
+
function getStepContent(step: number): { title: string, description: string } {
|
|
14
14
|
switch (step) {
|
|
15
15
|
case 0:
|
|
16
16
|
return { title: 'Personal Information', description: 'Please provide your basic details' }
|
|
@@ -5,7 +5,7 @@ interface ForumLayoutProps {
|
|
|
5
5
|
|
|
6
6
|
const { route = 'forum' } = defineProps<ForumLayoutProps>()
|
|
7
7
|
|
|
8
|
-
function isActiveRoute(currentRoute, targetRoute) {
|
|
8
|
+
function isActiveRoute(currentRoute: string, targetRoute: string): boolean {
|
|
9
9
|
return currentRoute === targetRoute
|
|
10
10
|
}
|
|
11
11
|
</script>
|
|
@@ -84,15 +84,15 @@ defineProps<ComingSoonProps>()
|
|
|
84
84
|
// Client-side wiring for the holding-page email form. The endpoint
|
|
85
85
|
// itself (/api/email/subscribe) is rate-limited server-side; this
|
|
86
86
|
// handler just gives the visitor immediate feedback.
|
|
87
|
-
const form = document.getElementById('coming-soon-form')
|
|
88
|
-
const emailInput = document.getElementById('coming-soon-email')
|
|
89
|
-
const submitBtn = document.getElementById('coming-soon-submit')
|
|
87
|
+
const form = document.getElementById('coming-soon-form') as HTMLFormElement | null
|
|
88
|
+
const emailInput = document.getElementById('coming-soon-email') as HTMLInputElement | null
|
|
89
|
+
const submitBtn = document.getElementById('coming-soon-submit') as HTMLButtonElement | null
|
|
90
90
|
const submitLabel = document.getElementById('coming-soon-submit-label')
|
|
91
91
|
const spinner = document.getElementById('coming-soon-spinner')
|
|
92
92
|
const errorEl = document.getElementById('coming-soon-error')
|
|
93
93
|
const successEl = document.getElementById('coming-soon-success')
|
|
94
94
|
|
|
95
|
-
function setLoading(loading) {
|
|
95
|
+
function setLoading(loading: boolean) {
|
|
96
96
|
if (!submitBtn || !submitLabel || !spinner) return
|
|
97
97
|
submitBtn.disabled = loading
|
|
98
98
|
submitBtn.classList.toggle('opacity-50', loading)
|
|
@@ -100,7 +100,7 @@ function setLoading(loading) {
|
|
|
100
100
|
spinner.classList.toggle('hidden', !loading)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function showError(message) {
|
|
103
|
+
function showError(message: string) {
|
|
104
104
|
if (!errorEl) return
|
|
105
105
|
errorEl.textContent = message
|
|
106
106
|
errorEl.classList.remove('hidden')
|