mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 20:52:14 +01:00
Compare commits
2 Commits
feature/vo
...
feature/bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf78dbdc37 | ||
|
|
b22132b0f2 |
@@ -80,6 +80,10 @@ class PermissionStore
|
||||
'invoices:update',
|
||||
'invoices:download',
|
||||
'invoices:delete',
|
||||
'invoice-recipients:view',
|
||||
'invoice-recipients:create',
|
||||
'invoice-recipients:update',
|
||||
'invoice-recipients:delete',
|
||||
'invoice-settings:view',
|
||||
'invoice-settings:update',
|
||||
],
|
||||
@@ -147,6 +151,10 @@ class PermissionStore
|
||||
'invoices:update',
|
||||
'invoices:download',
|
||||
'invoices:delete',
|
||||
'invoice-recipients:view',
|
||||
'invoice-recipients:create',
|
||||
'invoice-recipients:update',
|
||||
'invoice-recipients:delete',
|
||||
'invoice-settings:view',
|
||||
'invoice-settings:update',
|
||||
],
|
||||
@@ -203,6 +211,10 @@ class PermissionStore
|
||||
'invoices:update',
|
||||
'invoices:download',
|
||||
'invoices:delete',
|
||||
'invoice-recipients:view',
|
||||
'invoice-recipients:create',
|
||||
'invoice-recipients:update',
|
||||
'invoice-recipients:delete',
|
||||
'invoice-settings:view',
|
||||
'invoice-settings:update',
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Billing": {
|
||||
"repository": "solidtime-io/extension-billing",
|
||||
"ref": "v0.0.3"
|
||||
"ref": "v0.0.4"
|
||||
},
|
||||
"Services": {
|
||||
"repository": "solidtime-io/extension-services",
|
||||
@@ -9,6 +9,6 @@
|
||||
},
|
||||
"Invoicing": {
|
||||
"repository": "solidtime-io/extension-invoicing",
|
||||
"ref": "v0.0.1"
|
||||
"ref": "feature/recipients"
|
||||
}
|
||||
}
|
||||
|
||||
2193
package-lock.json
generated
2193
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@
|
||||
"@floating-ui/core": "^1.7.5",
|
||||
"@floating-ui/vue": "^1.1.11",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@lucide/vue": "^1.14.0",
|
||||
"@lucide/vue": "^1.28.0",
|
||||
"@rushstack/eslint-patch": "^1.16.1",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/vue-form": "^1.32.0",
|
||||
@@ -67,7 +67,7 @@
|
||||
"@tanstack/vue-virtual": "^3.13.24",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.7.0",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@vueuse/core": "^14.4.0",
|
||||
"@vueuse/integrations": "^14.3.0",
|
||||
"@zodios/core": "^10.9.6",
|
||||
"chroma-js": "^3.2.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"parse-duration": "^2.1.6",
|
||||
"pinia": "^3.0.4",
|
||||
"radix-vue": "^1.9.17",
|
||||
"reka-ui": "^2.9.7",
|
||||
"reka-ui": "^2.10.1",
|
||||
"tailwind-merge": "^2.6.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue-draggable-plus": "^0.6.1",
|
||||
|
||||
@@ -117,6 +117,8 @@ export type DetailedInvoiceResponse = ZodiosResponseByAlias<SolidTimeApi, 'getIn
|
||||
export type DetailedInvoice = DetailedInvoiceResponse['data'];
|
||||
|
||||
export type InvoiceIndexEntry = ZodiosResponseByAlias<SolidTimeApi, 'getInvoices'>['data'][0];
|
||||
export type InvoiceRecipient = ZodiosResponseByAlias<SolidTimeApi, 'getInvoiceRecipients'>['data'][0];
|
||||
export type InvoiceRecipientBody = ZodiosBodyByAlias<SolidTimeApi, 'createInvoiceRecipient'>;
|
||||
|
||||
export type UpdateInvoiceSettings = ZodiosBodyByAlias<SolidTimeApi, 'updateInvoiceSettings'>;
|
||||
|
||||
|
||||
@@ -45,14 +45,54 @@ const InvitationResource = z
|
||||
const InvitationStoreRequest = z
|
||||
.object({ email: z.string().email(), role: z.enum(['admin', 'manager', 'employee']) })
|
||||
.passthrough();
|
||||
const InvoiceRecipientResource = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization_id: z.string(),
|
||||
name: z.string(),
|
||||
vatin: z.union([z.string(), z.null()]),
|
||||
address_line_1: z.union([z.string(), z.null()]),
|
||||
address_line_2: z.union([z.string(), z.null()]),
|
||||
address_line_3: z.union([z.string(), z.null()]),
|
||||
address_post_code: z.union([z.string(), z.null()]),
|
||||
address_city: z.union([z.string(), z.null()]),
|
||||
address_country: z.union([z.string(), z.null()]),
|
||||
phone: z.union([z.string(), z.null()]),
|
||||
email: z.union([z.string(), z.null()]),
|
||||
is_archived: z.boolean(),
|
||||
archived_at: z.union([z.string(), z.null()]),
|
||||
invoices_count: z.number().int(),
|
||||
has_non_draft_invoices: z.boolean(),
|
||||
created_at: z.union([z.string(), z.null()]),
|
||||
updated_at: z.union([z.string(), z.null()]),
|
||||
})
|
||||
.passthrough();
|
||||
const InvoiceRecipientCollection = z.array(InvoiceRecipientResource);
|
||||
const InvoiceRecipientRequest = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
vatin: z.union([z.string(), z.null()]).optional(),
|
||||
address_line_1: z.union([z.string(), z.null()]).optional(),
|
||||
address_line_2: z.union([z.string(), z.null()]).optional(),
|
||||
address_line_3: z.union([z.string(), z.null()]).optional(),
|
||||
address_post_code: z.union([z.string(), z.null()]).optional(),
|
||||
address_city: z.union([z.string(), z.null()]).optional(),
|
||||
address_country: z.union([z.string(), z.null()]).optional(),
|
||||
phone: z.union([z.string(), z.null()]).optional(),
|
||||
email: z.union([z.string(), z.null()]).optional(),
|
||||
is_archived: z.boolean().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
const InvoiceResource = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization_id: z.string(),
|
||||
invoice_recipient_id: z.string(),
|
||||
reference: z.string(),
|
||||
seller_name: z.string(),
|
||||
buyer_name: z.string(),
|
||||
recipient: z.string(),
|
||||
status: z.string(),
|
||||
status_label: z.string(),
|
||||
date: z.string(),
|
||||
due_at: z.string(),
|
||||
paid_date: z.string(),
|
||||
@@ -76,16 +116,7 @@ const InvoiceStoreRequest = z
|
||||
seller_address_country: z.union([z.string(), z.null()]).optional(),
|
||||
seller_phone: z.union([z.string(), z.null()]).optional(),
|
||||
seller_email: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_name: z.string(),
|
||||
buyer_vatin: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_line_1: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_line_2: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_line_3: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_post_code: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_city: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_address_country: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_phone: z.union([z.string(), z.null()]).optional(),
|
||||
buyer_email: z.union([z.string(), z.null()]).optional(),
|
||||
invoice_recipient_id: z.string(),
|
||||
date: z.string(),
|
||||
billing_period_start: z.union([z.string(), z.null()]).optional(),
|
||||
billing_period_end: z.union([z.string(), z.null()]).optional(),
|
||||
@@ -130,6 +161,7 @@ const DetailedInvoiceResource = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization_id: z.string(),
|
||||
invoice_recipient_id: z.string(),
|
||||
reference: z.string(),
|
||||
seller_name: z.string(),
|
||||
seller_vatin: z.string(),
|
||||
@@ -141,16 +173,7 @@ const DetailedInvoiceResource = z
|
||||
seller_address_country: z.string(),
|
||||
seller_phone: z.string(),
|
||||
seller_email: z.string(),
|
||||
buyer_name: z.string(),
|
||||
buyer_vatin: z.string(),
|
||||
buyer_address_line_1: z.string(),
|
||||
buyer_address_line_2: z.string(),
|
||||
buyer_address_line_3: z.string(),
|
||||
buyer_address_post_code: z.string(),
|
||||
buyer_address_city: z.string(),
|
||||
buyer_address_country: z.string(),
|
||||
buyer_phone: z.string(),
|
||||
buyer_email: z.string(),
|
||||
recipient: InvoiceRecipientResource,
|
||||
paid_date: z.string(),
|
||||
due_at: z.string(),
|
||||
discount_type: z.string(),
|
||||
@@ -171,7 +194,7 @@ const DetailedInvoiceResource = z
|
||||
entries: z.array(InvoiceEntryResource),
|
||||
})
|
||||
.passthrough();
|
||||
const InvoiceStatus = z.enum(['draft', 'sent', 'cancelled']);
|
||||
const InvoiceStatus = z.enum(['draft', 'sent', 'paid', 'cancelled']);
|
||||
const InvoiceUpdateRequest = z
|
||||
.object({
|
||||
status: InvoiceStatus,
|
||||
@@ -187,16 +210,7 @@ const InvoiceUpdateRequest = z
|
||||
seller_address_country: z.union([z.string(), z.null()]),
|
||||
seller_phone: z.union([z.string(), z.null()]),
|
||||
seller_email: z.union([z.string(), z.null()]),
|
||||
buyer_name: z.string(),
|
||||
buyer_vatin: z.union([z.string(), z.null()]),
|
||||
buyer_address_line_1: z.union([z.string(), z.null()]),
|
||||
buyer_address_line_2: z.union([z.string(), z.null()]),
|
||||
buyer_address_line_3: z.union([z.string(), z.null()]),
|
||||
buyer_address_post_code: z.union([z.string(), z.null()]),
|
||||
buyer_address_city: z.union([z.string(), z.null()]),
|
||||
buyer_address_country: z.union([z.string(), z.null()]),
|
||||
buyer_phone: z.union([z.string(), z.null()]),
|
||||
buyer_email: z.union([z.string(), z.null()]),
|
||||
invoice_recipient_id: z.string(),
|
||||
date: z.string(),
|
||||
billing_period_start: z.union([z.string(), z.null()]),
|
||||
billing_period_end: z.union([z.string(), z.null()]),
|
||||
@@ -1885,6 +1899,125 @@ const endpoints = makeApi([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/v1/organizations/:organization/invoice-recipients',
|
||||
alias: 'getInvoiceRecipients',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceRecipientCollection }).passthrough(),
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
path: '/v1/organizations/:organization/invoice-recipients',
|
||||
alias: 'createInvoiceRecipient',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'body',
|
||||
type: 'Body',
|
||||
schema: InvoiceRecipientRequest,
|
||||
},
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceRecipientResource }).passthrough(),
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/v1/organizations/:organization/invoice-recipients/:invoiceRecipient',
|
||||
alias: 'getInvoiceRecipient',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
{
|
||||
name: 'invoiceRecipient',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceRecipientResource }).passthrough(),
|
||||
},
|
||||
{
|
||||
method: 'put',
|
||||
path: '/v1/organizations/:organization/invoice-recipients/:invoiceRecipient',
|
||||
alias: 'updateInvoiceRecipient',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'body',
|
||||
type: 'Body',
|
||||
schema: InvoiceRecipientRequest,
|
||||
},
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
{
|
||||
name: 'invoiceRecipient',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceRecipientResource }).passthrough(),
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
path: '/v1/organizations/:organization/invoice-recipients/:invoiceRecipient/duplicate',
|
||||
alias: 'duplicateInvoiceRecipient',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'body',
|
||||
type: 'Body',
|
||||
schema: InvoiceRecipientRequest,
|
||||
},
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
{
|
||||
name: 'invoiceRecipient',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceRecipientResource }).passthrough(),
|
||||
},
|
||||
{
|
||||
method: 'delete',
|
||||
path: '/v1/organizations/:organization/invoice-recipients/:invoiceRecipient',
|
||||
alias: 'deleteInvoiceRecipient',
|
||||
requestFormat: 'json',
|
||||
parameters: [
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
{
|
||||
name: 'invoiceRecipient',
|
||||
type: 'Path',
|
||||
schema: z.string(),
|
||||
},
|
||||
],
|
||||
response: z.void(),
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/v1/organizations/:organization/invoices',
|
||||
@@ -1901,6 +2034,11 @@ const endpoints = makeApi([
|
||||
type: 'Query',
|
||||
schema: z.number().int().gte(1).lte(2147483647).optional(),
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'Query',
|
||||
schema: InvoiceStatus.optional(),
|
||||
},
|
||||
],
|
||||
response: z.object({ data: InvoiceCollection }).passthrough(),
|
||||
errors: [
|
||||
|
||||
25
resources/js/packages/ui/src/combobox/Combobox.vue
Normal file
25
resources/js/packages/ui/src/combobox/Combobox.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<ComboboxRootEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Keep the trigger inside this root. Reka treats anything within the root
|
||||
element as "not outside", so it suppresses its own dismissal for trigger
|
||||
clicks and ComboboxInput's blur-close ignores them. Putting the trigger
|
||||
in a separate Popover instead gives two competing open states, and the
|
||||
popover then closes on mousedown and reopens on the following click. -->
|
||||
<ComboboxRoot v-slot="slotProps" v-bind="forwarded" :class="cn('min-w-0', props.class)">
|
||||
<slot v-bind="slotProps" />
|
||||
</ComboboxRoot>
|
||||
</template>
|
||||
19
resources/js/packages/ui/src/combobox/ComboboxAnchor.vue
Normal file
19
resources/js/packages/ui/src/combobox/ComboboxAnchor.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxAnchorProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxAnchor, useForwardProps } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxAnchorProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ComboboxAnchor v-bind="forwarded" :class="cn('w-full', props.class)">
|
||||
<slot />
|
||||
</ComboboxAnchor>
|
||||
</template>
|
||||
35
resources/js/packages/ui/src/combobox/ComboboxInput.vue
Normal file
35
resources/js/packages/ui/src/combobox/ComboboxInput.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxInputEmits, ComboboxInputProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { Search } from '@lucide/vue';
|
||||
import { ComboboxInput, useForwardPropsEmits } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps<ComboboxInputProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<ComboboxInputEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative items-center border-b border-card-background-separator">
|
||||
<ComboboxInput
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'h-10 w-full border-0 rounded-none bg-transparent pl-9 pr-3 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-0',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-3">
|
||||
<Search class="size-4 text-text-tertiary" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
27
resources/js/packages/ui/src/combobox/ComboboxItem.vue
Normal file
27
resources/js/packages/ui/src/combobox/ComboboxItem.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxItem, useForwardPropsEmits } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<ComboboxItemEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ComboboxItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'flex w-full cursor-default items-center rounded-md px-2 py-1.5 text-sm text-text-primary data-[highlighted]:bg-card-background-active',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
</ComboboxItem>
|
||||
</template>
|
||||
41
resources/js/packages/ui/src/combobox/ComboboxList.vue
Normal file
41
resources/js/packages/ui/src/combobox/ComboboxList.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxContent, ComboboxPortal, useForwardPropsEmits } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
position: 'popper',
|
||||
align: 'start',
|
||||
sideOffset: 4,
|
||||
class: undefined,
|
||||
}
|
||||
);
|
||||
const emits = defineEmits<ComboboxContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ComboboxPortal>
|
||||
<ComboboxContent
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 w-[--reka-popper-anchor-width] min-w-60 overflow-hidden rounded-lg border border-popover-border bg-popover text-popover-foreground shadow-dropdown outline-none origin-[var(--reka-combobox-content-transform-origin)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</template>
|
||||
19
resources/js/packages/ui/src/combobox/ComboboxSeparator.vue
Normal file
19
resources/js/packages/ui/src/combobox/ComboboxSeparator.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxSeparatorProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxSeparator, useForwardProps } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ComboboxSeparator
|
||||
v-bind="forwarded"
|
||||
:class="cn('my-1 h-px bg-card-background-separator', props.class)" />
|
||||
</template>
|
||||
29
resources/js/packages/ui/src/combobox/ComboboxTrigger.vue
Normal file
29
resources/js/packages/ui/src/combobox/ComboboxTrigger.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxTriggerProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxTrigger, useForwardProps } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxTriggerProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Reka hardcodes tabindex="-1" here because it expects a ComboboxInput
|
||||
next to the trigger in the anchor to be the focusable control. Our input
|
||||
lives inside ComboboxList, so this trigger is the control and has to be
|
||||
tabbable. tabindex is a fallthrough attr, which Vue applies after Reka's
|
||||
own props and therefore wins.
|
||||
|
||||
Reka also hardcodes aria-label="Show popup", which would otherwise be
|
||||
the accessible name. Pass :aria-label on the child element (it wins
|
||||
again, because Slot merges child props over attrs) to name the trigger
|
||||
after its current value. -->
|
||||
<ComboboxTrigger v-bind="forwarded" :class="cn(props.class)" tabindex="0">
|
||||
<slot />
|
||||
</ComboboxTrigger>
|
||||
</template>
|
||||
38
resources/js/packages/ui/src/combobox/ComboboxViewport.vue
Normal file
38
resources/js/packages/ui/src/combobox/ComboboxViewport.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComboboxViewportProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { ComboboxViewport, useForwardProps } from 'reka-ui';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
const props = defineProps<ComboboxViewportProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ComboboxViewport
|
||||
v-bind="forwarded"
|
||||
:class="cn('ui-combobox-viewport max-h-60 overflow-y-auto p-2', props.class)">
|
||||
<slot />
|
||||
</ComboboxViewport>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Reka's ComboboxViewport injects a global style that hides scrollbars; the
|
||||
attribute+class selector below is more specific and restores them. */
|
||||
[data-reka-combobox-viewport].ui-combobox-viewport {
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: auto;
|
||||
}
|
||||
[data-reka-combobox-viewport].ui-combobox-viewport::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 8px;
|
||||
}
|
||||
[data-reka-combobox-viewport].ui-combobox-viewport::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(127 127 127 / 0.4);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
9
resources/js/packages/ui/src/combobox/index.ts
Normal file
9
resources/js/packages/ui/src/combobox/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as Combobox } from './Combobox.vue';
|
||||
export { default as ComboboxAnchor } from './ComboboxAnchor.vue';
|
||||
export { default as ComboboxInput } from './ComboboxInput.vue';
|
||||
export { default as ComboboxItem } from './ComboboxItem.vue';
|
||||
export { default as ComboboxList } from './ComboboxList.vue';
|
||||
export { default as ComboboxSeparator } from './ComboboxSeparator.vue';
|
||||
export { default as ComboboxTrigger } from './ComboboxTrigger.vue';
|
||||
export { default as ComboboxViewport } from './ComboboxViewport.vue';
|
||||
export { ComboboxVirtualizer } from 'reka-ui';
|
||||
@@ -57,6 +57,16 @@ import {
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton,
|
||||
} from './calendar/index';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxAnchor,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxSeparator,
|
||||
ComboboxTrigger,
|
||||
ComboboxViewport,
|
||||
} from './combobox/index';
|
||||
import { CommandPalette } from './CommandPalette/index';
|
||||
import {
|
||||
ContextMenu,
|
||||
@@ -176,6 +186,14 @@ export {
|
||||
CardTitle,
|
||||
Checkbox,
|
||||
color,
|
||||
Combobox,
|
||||
ComboboxAnchor,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxSeparator,
|
||||
ComboboxTrigger,
|
||||
ComboboxViewport,
|
||||
CommandPalette,
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
|
||||
Reference in New Issue
Block a user