mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 11:42:15 +01:00
Add Field component system and migrate UI
This commit is contained in:
@@ -7,7 +7,7 @@ import type { CreateClientBody } from '@/packages/api/src';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import InputLabel from '../../../packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
|
||||
const { createClient } = useClientsStore();
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -37,19 +37,19 @@ useFocus(clientNameInput, { initialValue: true });
|
||||
|
||||
<template #content>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1">
|
||||
<InputLabel for="clientName" value="Client Name" />
|
||||
<Field class="col-span-6 sm:col-span-4 flex-1">
|
||||
<FieldLabel for="clientName">Client Name</FieldLabel>
|
||||
<TextInput
|
||||
id="clientName"
|
||||
ref="clientNameInput"
|
||||
v-model="client.name"
|
||||
type="text"
|
||||
placeholder="Client Name"
|
||||
class="mt-1 block w-full"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="clientName"
|
||||
@keydown.enter="submit" />
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
ComboboxViewport,
|
||||
} from 'radix-vue';
|
||||
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
|
||||
const { members } = useMembersQuery();
|
||||
|
||||
@@ -77,7 +77,6 @@ function selectMember(member: Member) {
|
||||
:disabled="disabled"
|
||||
type="button"
|
||||
variant="input"
|
||||
size="input"
|
||||
class="w-full justify-between text-start font-normal">
|
||||
<div class="flex items-center gap-3 truncate">
|
||||
<UserIcon class="w-4 text-text-secondary shrink-0" />
|
||||
@@ -92,7 +91,7 @@ function selectMember(member: Member) {
|
||||
<template #content>
|
||||
<ComboboxRoot
|
||||
v-model:search-term="searchValue"
|
||||
:open="open"
|
||||
v-model:open="open"
|
||||
class="relative"
|
||||
:filter-function="(val: string[]) => val">
|
||||
<ComboboxAnchor>
|
||||
|
||||
@@ -9,8 +9,7 @@ import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
|
||||
import Checkbox from '@/packages/ui/src/Input/Checkbox.vue';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import InputError from '@/packages/ui/src/Input/InputError.vue';
|
||||
import { Field, FieldLabel, FieldError } from '@/packages/ui/src/field';
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
@@ -113,25 +112,21 @@ const close = () => {
|
||||
},
|
||||
}">
|
||||
<template #default="{ field }">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center space-x-3 text-sm">
|
||||
<Checkbox
|
||||
:id="field.name"
|
||||
:name="field.name"
|
||||
:checked="field.state.value"
|
||||
@update:checked="field.handleChange"
|
||||
@blur="field.handleBlur" />
|
||||
<InputLabel
|
||||
:for="field.name"
|
||||
class="font-medium text-text-primary">
|
||||
I understand that this will permanently delete all data
|
||||
related to this member
|
||||
</InputLabel>
|
||||
</div>
|
||||
<InputError
|
||||
class="pl-7 pt-2"
|
||||
:message="field.state.meta.errors[0]" />
|
||||
</div>
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox
|
||||
:id="field.name"
|
||||
:name="field.name"
|
||||
:checked="field.state.value"
|
||||
@update:checked="field.handleChange"
|
||||
@blur="field.handleBlur" />
|
||||
<FieldLabel :for="field.name" class="font-medium text-text-primary">
|
||||
I understand that this will permanently delete all data related
|
||||
to this member
|
||||
</FieldLabel>
|
||||
<FieldError v-if="field.state.meta.errors[0]" class="pl-7 pt-2">
|
||||
{{ field.state.meta.errors[0] }}
|
||||
</FieldError>
|
||||
</Field>
|
||||
</template>
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Member, UpdateMemberBody } from '@/packages/api/src';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import { type MemberBillableKey, useMembersStore } from '@/utils/useMembers';
|
||||
import BillableRateInput from '@/packages/ui/src/Input/BillableRateInput.vue';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import MemberBillableRateModal from '@/Components/Common/Member/MemberBillableRateModal.vue';
|
||||
import MemberBillableSelect from '@/Components/Common/Member/MemberBillableSelect.vue';
|
||||
import { onMounted, watch } from 'vue';
|
||||
@@ -121,28 +121,24 @@ const roleDescription = computed(() => {
|
||||
<template #content>
|
||||
<div class="pb-5 pt-2 divide-y divide-border-secondary">
|
||||
<div class="pb-5 flex space-x-6">
|
||||
<div>
|
||||
<InputLabel for="role" value="Role" />
|
||||
<MemberRoleSelect
|
||||
v-model="memberBody.role"
|
||||
class="mt-2"
|
||||
name="role"></MemberRoleSelect>
|
||||
</div>
|
||||
<Field>
|
||||
<FieldLabel for="role">Role</FieldLabel>
|
||||
<MemberRoleSelect v-model="memberBody.role" name="role"></MemberRoleSelect>
|
||||
</Field>
|
||||
<div class="flex-1 text-xs flex items-center pt-6">
|
||||
<p>{{ roleDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4 pt-5">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1 flex space-x-5">
|
||||
<div>
|
||||
<InputLabel for="billableType" value="Billable" />
|
||||
<Field>
|
||||
<FieldLabel for="billableType">Billable</FieldLabel>
|
||||
<MemberBillableSelect
|
||||
v-model="billableRateSelect"
|
||||
class="mt-2"
|
||||
name="billableType"></MemberBillableSelect>
|
||||
</div>
|
||||
<div v-if="billableRateSelect === 'custom-rate'" class="flex-1">
|
||||
<InputLabel for="memberBillableRate" value="Billable Rate" />
|
||||
</Field>
|
||||
<Field v-if="billableRateSelect === 'custom-rate'" class="flex-1">
|
||||
<FieldLabel for="memberBillableRate">Billable Rate</FieldLabel>
|
||||
<BillableRateInput
|
||||
v-model="memberBody.billable_rate"
|
||||
focus
|
||||
@@ -150,7 +146,7 @@ const roleDescription = computed(() => {
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
name="memberBillableRate"
|
||||
@keydown.enter="saveWithChecks()"></BillableRateInput>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,7 @@ import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import { ref } from 'vue';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import InputError from '@/packages/ui/src/Input/InputError.vue';
|
||||
import { Field, FieldLabel, FieldError } from '@/packages/ui/src/field';
|
||||
import type { Role } from '@/types/jetstream';
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
@@ -111,8 +110,8 @@ useFocus(clientNameInput, { initialValue: true });
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="space-y-4">
|
||||
<div class="col-span-6 sm:col-span-4 flex-1">
|
||||
<InputLabel for="email" value="Email" />
|
||||
<Field class="col-span-6 sm:col-span-4 flex-1">
|
||||
<FieldLabel for="email">Email</FieldLabel>
|
||||
<TextInput
|
||||
id="email"
|
||||
ref="memberEmailInput"
|
||||
@@ -120,16 +119,16 @@ useFocus(clientNameInput, { initialValue: true });
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder="Member Email"
|
||||
class="mt-1 block w-full"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="memberName"
|
||||
@keydown.enter="submit" />
|
||||
<InputError :message="errors.email" class="mt-2" />
|
||||
</div>
|
||||
<FieldError v-if="errors.email">{{ errors.email }}</FieldError>
|
||||
</Field>
|
||||
|
||||
<div v-if="availableRoles.length > 0">
|
||||
<InputLabel for="roles" value="Role" />
|
||||
<InputError :message="errors.role" class="mt-2" />
|
||||
<Field v-if="availableRoles.length > 0">
|
||||
<FieldLabel for="roles">Role</FieldLabel>
|
||||
<FieldError v-if="errors.role">{{ errors.role }}</FieldError>
|
||||
|
||||
<div
|
||||
class="relative z-0 mt-1 border border-card-border rounded-lg bg-card-background cursor-pointer">
|
||||
@@ -182,7 +181,7 @@ useFocus(clientNameInput, { initialValue: true });
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -123,7 +123,7 @@ function updateValue(project: Project) {
|
||||
<UseFocusTrap v-if="open" :options="{ immediate: true, allowOutsideClick: true }">
|
||||
<ComboboxRoot
|
||||
v-model:search-term="searchValue"
|
||||
:open="open"
|
||||
v-model:open="open"
|
||||
:model-value="currentProject"
|
||||
class="relative"
|
||||
@update:model-value="updateValue">
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useClientsQuery } from '@/utils/useClientsQuery';
|
||||
import ProjectColorSelector from '@/packages/ui/src/Project/ProjectColorSelector.vue';
|
||||
import { UserCircleIcon } from '@heroicons/vue/20/solid';
|
||||
import EstimatedTimeSection from '@/packages/ui/src/EstimatedTimeSection.vue';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import ProjectBillableRateModal from '@/packages/ui/src/Project/ProjectBillableRateModal.vue';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import ProjectEditBillableSection from '@/packages/ui/src/Project/ProjectEditBillableSection.vue';
|
||||
@@ -82,34 +82,28 @@ async function submitBillableRate() {
|
||||
|
||||
<template #content>
|
||||
<div class="sm:flex items-center space-y-2 sm:space-y-0 sm:space-x-5">
|
||||
<div class="flex-1 flex items-center">
|
||||
<Field class="flex-1 flex items-center">
|
||||
<div class="text-center">
|
||||
<InputLabel for="color" value="Color" />
|
||||
<ProjectColorSelector
|
||||
v-model="project.color"
|
||||
class="mt-1"></ProjectColorSelector>
|
||||
<FieldLabel for="color">Color</FieldLabel>
|
||||
<ProjectColorSelector v-model="project.color"></ProjectColorSelector>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<InputLabel for="projectName" value="Project name" />
|
||||
</Field>
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="projectName">Project name</FieldLabel>
|
||||
<TextInput
|
||||
id="projectName"
|
||||
ref="projectNameInput"
|
||||
v-model="project.name"
|
||||
type="text"
|
||||
placeholder="Project Name"
|
||||
class="mt-1 block w-full"
|
||||
class="block w-full"
|
||||
required
|
||||
autocomplete="projectName"
|
||||
@keydown.enter="submit()" />
|
||||
</div>
|
||||
<div class="">
|
||||
<InputLabel for="client" value="Client" />
|
||||
<ClientDropdown
|
||||
v-model="project.client_id"
|
||||
:create-client
|
||||
:clients="clients"
|
||||
class="mt-1">
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="client">Client</FieldLabel>
|
||||
<ClientDropdown v-model="project.client_id" :create-client :clients="clients">
|
||||
<template #trigger>
|
||||
<Badge
|
||||
class="bg-input-background cursor-pointer hover:bg-tertiary"
|
||||
@@ -123,7 +117,7 @@ async function submitBillableRate() {
|
||||
</Badge>
|
||||
</template>
|
||||
</ClientDropdown>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useProjectMembersStore } from '@/utils/useProjectMembers';
|
||||
import BillableRateInput from '@/packages/ui/src/Input/BillableRateInput.vue';
|
||||
import { UserIcon } from '@heroicons/vue/24/solid';
|
||||
import ProjectMemberBillableRateModal from '@/Components/Common/ProjectMember/ProjectMemberBillableRateModal.vue';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
const { updateProjectMember } = useProjectMembersStore();
|
||||
|
||||
@@ -82,14 +82,14 @@ useFocus(projectNameInput, { initialValue: true });
|
||||
<UserIcon class="w-4 text-text-secondary"></UserIcon>
|
||||
<span>{{ props.name }}</span>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-1 flex-1">
|
||||
<InputLabel for="billable_rate" class="mb-2" value="Billable Rate"></InputLabel>
|
||||
<Field class="col-span-3 sm:col-span-1 flex-1">
|
||||
<FieldLabel for="billable_rate">Billable Rate</FieldLabel>
|
||||
<BillableRateInput
|
||||
v-model="projectMemberBody.billable_rate"
|
||||
:currency="getOrganizationCurrencyString()"
|
||||
name="billable_rate"
|
||||
@keydown.enter="submit"></BillableRateInput>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -4,7 +4,7 @@ import SecondaryButton from '../../../packages/ui/src/Buttons/SecondaryButton.vu
|
||||
import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import { ref } from 'vue';
|
||||
import PrimaryButton from '../../../packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import InputLabel from '../../../packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import type { CreateReportBody, CreateReportBodyProperties } from '@/packages/api/src';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
@@ -82,31 +82,33 @@ async function submit() {
|
||||
|
||||
<template #content>
|
||||
<div class="items-center space-y-4 w-full">
|
||||
<div class="w-full">
|
||||
<InputLabel for="name" value="Name" />
|
||||
<TextInput id="name" v-model="report.name" class="mt-1.5 w-full"></TextInput>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="description" value="Description" />
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="name">Name</FieldLabel>
|
||||
<TextInput id="name" v-model="report.name" class="w-full"></TextInput>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="description">Description</FieldLabel>
|
||||
<TextInput
|
||||
id="description"
|
||||
v-model="report.description"
|
||||
class="mt-1.5 w-full"></TextInput>
|
||||
</div>
|
||||
<InputLabel value="Visibility" />
|
||||
<div class="flex items-center space-x-12">
|
||||
<div class="flex items-center space-x-3 px-2 py-3">
|
||||
<Checkbox id="is_public" v-model:checked="report.is_public"></Checkbox>
|
||||
<InputLabel for="is_public" value="Public" />
|
||||
class="w-full"></TextInput>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>Visibility</FieldLabel>
|
||||
<div class="flex items-center space-x-12">
|
||||
<Field orientation="horizontal" class="px-2 py-3">
|
||||
<Checkbox id="is_public" v-model:checked="report.is_public"></Checkbox>
|
||||
<FieldLabel for="is_public">Public</FieldLabel>
|
||||
</Field>
|
||||
<Field v-if="report.is_public" class="flex-row items-center space-x-4">
|
||||
<div>
|
||||
<FieldLabel for="public_until">Expires at</FieldLabel>
|
||||
<div class="text-text-tertiary font-medium">(optional)</div>
|
||||
</div>
|
||||
<DatePicker v-model="report.public_until"></DatePicker>
|
||||
</Field>
|
||||
</div>
|
||||
<div v-if="report.is_public" class="flex items-center space-x-4">
|
||||
<div>
|
||||
<InputLabel for="public_until" value="Expires at" />
|
||||
<div class="text-text-tertiary font-medium">(optional)</div>
|
||||
</div>
|
||||
<DatePicker v-model="report.public_until"></DatePicker>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -4,7 +4,7 @@ import SecondaryButton from '../../../packages/ui/src/Buttons/SecondaryButton.vu
|
||||
import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import PrimaryButton from '../../../packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import InputLabel from '../../../packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import type { UpdateReportBody } from '@/packages/api/src';
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
@@ -105,28 +105,30 @@ async function submit() {
|
||||
|
||||
<template #content>
|
||||
<div class="items-center space-y-4 w-full">
|
||||
<div class="w-full">
|
||||
<InputLabel for="name" value="Name" />
|
||||
<TextInput id="name" v-model="report.name" class="mt-1.5 w-full"></TextInput>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="description" value="Description" />
|
||||
<Field class="w-full">
|
||||
<FieldLabel for="name">Name</FieldLabel>
|
||||
<TextInput id="name" v-model="report.name" class="w-full"></TextInput>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="description">Description</FieldLabel>
|
||||
<TextInput
|
||||
id="description"
|
||||
v-model="report.description"
|
||||
class="mt-1.5 w-full"></TextInput>
|
||||
</div>
|
||||
<InputLabel value="Visibility" />
|
||||
<div class="flex items-center space-x-12">
|
||||
<div class="flex items-center space-x-2 px-2 py-3">
|
||||
<Checkbox id="is_public" v-model:checked="report.is_public"></Checkbox>
|
||||
<InputLabel for="is_public" value="Public" />
|
||||
class="w-full"></TextInput>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>Visibility</FieldLabel>
|
||||
<div class="flex items-center space-x-12">
|
||||
<Field orientation="horizontal" class="px-2 py-3">
|
||||
<Checkbox id="is_public" v-model:checked="report.is_public"></Checkbox>
|
||||
<FieldLabel for="is_public">Public</FieldLabel>
|
||||
</Field>
|
||||
<Field v-if="report.is_public" orientation="horizontal">
|
||||
<FieldLabel for="public_until">Expires at</FieldLabel>
|
||||
<DatePicker v-model="localPublicUntil"></DatePicker>
|
||||
</Field>
|
||||
</div>
|
||||
<div v-if="report.is_public" class="flex items-center space-x-4">
|
||||
<InputLabel for="public_until" value="Expires at" />
|
||||
<DatePicker v-model="localPublicUntil"></DatePicker>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/Components/ui/select';
|
||||
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
||||
import { Field, FieldLabel } from '@/packages/ui/src/field';
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldInput,
|
||||
@@ -162,7 +162,7 @@ const iconClass = computed(() => {
|
||||
>Rounding is a premium feature. Upgrade to unlock this feature.</span
|
||||
>
|
||||
<Link href="/billing">
|
||||
<Button size="sm" variant="input" class="items-center space-x-1">
|
||||
<Button size="sm" variant="outline" class="items-center space-x-1">
|
||||
<CreditCardIcon class="w-3.5 h-3.5 text-text-tertiary mr-1" />
|
||||
Go to Billing
|
||||
</Button>
|
||||
@@ -170,14 +170,14 @@ const iconClass = computed(() => {
|
||||
</div>
|
||||
<div v-else class="space-y-4">
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<InputLabel for="enable-rounding" value="Enable Rounding" />
|
||||
<Field orientation="horizontal" class="justify-between">
|
||||
<FieldLabel for="enable-rounding">Enable Rounding</FieldLabel>
|
||||
<Switch
|
||||
id="enable-rounding"
|
||||
:model-value="enabled"
|
||||
class="data-[state=checked]:bg-accent-500"
|
||||
@update:model-value="updateEnabled" />
|
||||
</div>
|
||||
</Field>
|
||||
<div
|
||||
class="mb-3 pb-2 pt-1 text-xs text-muted-foreground border-b border-border-secondary text-text-tertiary">
|
||||
Rounding is applied to each individual time entry, not to the accumulated
|
||||
@@ -185,15 +185,15 @@ const iconClass = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="rounding-type" value="Rounding Type" class="mb-2" />
|
||||
<Field>
|
||||
<FieldLabel for="rounding-type">Rounding Type</FieldLabel>
|
||||
<Select
|
||||
:model-value="type"
|
||||
:disabled="!enabled"
|
||||
@update:model-value="(value) => updateType(value as TimeEntryRoundingType)">
|
||||
<SelectTrigger
|
||||
id="rounding-type"
|
||||
size="small"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
:disabled="!enabled">
|
||||
<SelectValue placeholder="Select rounding type" />
|
||||
@@ -204,16 +204,16 @@ const iconClass = computed(() => {
|
||||
<SelectItem value="nearest">Round Nearest</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel for="minutes-interval" value="Minutes Interval" class="mb-2" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="minutes-interval">Minutes Interval</FieldLabel>
|
||||
<Select
|
||||
:model-value="selectedInterval"
|
||||
:disabled="!enabled"
|
||||
@update:model-value="(value) => handleIntervalChange(value as string)">
|
||||
<SelectTrigger
|
||||
id="minutes-interval"
|
||||
size="small"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
:disabled="!enabled">
|
||||
<SelectValue placeholder="Select interval" />
|
||||
@@ -232,7 +232,6 @@ const iconClass = computed(() => {
|
||||
<NumberField
|
||||
id="custom-minutes"
|
||||
:model-value="customMinutes"
|
||||
size="small"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
:disabled="!enabled"
|
||||
@@ -247,7 +246,7 @@ const iconClass = computed(() => {
|
||||
</NumberFieldContent>
|
||||
</NumberField>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue';
|
||||
import DialogModal from '@/packages/ui/src/DialogModal.vue';
|
||||
import InputError from '@/packages/ui/src/Input/InputError.vue';
|
||||
import { Field, FieldError } from '@/packages/ui/src/field';
|
||||
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
|
||||
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
|
||||
@@ -87,18 +87,18 @@ const closeModal = () => {
|
||||
<template #content>
|
||||
{{ content }}
|
||||
|
||||
<div class="mt-4">
|
||||
<Field class="mt-4">
|
||||
<TextInput
|
||||
ref="passwordInput"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
class="mt-1 block w-3/4"
|
||||
class="block w-3/4"
|
||||
placeholder="Password"
|
||||
autocomplete="current-password"
|
||||
@keyup.enter="confirmPassword" />
|
||||
|
||||
<InputError :message="form.error" class="mt-2" />
|
||||
</div>
|
||||
<FieldError v-if="form.error">{{ form.error }}</FieldError>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Primitive, type PrimitiveProps } from 'reka-ui';
|
||||
import { type ButtonVariants, buttonVariants } from '.';
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant'];
|
||||
size?: ButtonVariants['size'];
|
||||
class?: HTMLAttributes['class'];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -1,36 +0,0 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export { default as Button } from './Button.vue';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline:
|
||||
'border shadow-xs hover:text-text-primary bg-card-background dark:bg-transparent border-input dark:border-input hover:bg-white/5',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
input: 'border-input-border border bg-input-background text-text-primary focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-transparent shadow-sm',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
xs: 'h-7 rounded px-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
input: 'h-[42px] px-3 py-2 text-base',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
@@ -42,7 +42,6 @@ const organization = inject<ComputedRef<Organization>>('organization');
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="input"
|
||||
size="input"
|
||||
:class="[
|
||||
'w-full justify-start text-left font-normal',
|
||||
!model && 'text-muted-foreground',
|
||||
|
||||
24
resources/js/Components/ui/label/Label.vue
Normal file
24
resources/js/Components/ui/label/Label.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { LabelProps } from 'reka-ui';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { reactiveOmit } from '@vueuse/core';
|
||||
import { Label } from 'reka-ui';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
1
resources/js/Components/ui/label/index.ts
Normal file
1
resources/js/Components/ui/label/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue';
|
||||
@@ -13,7 +13,7 @@ const props = defineProps<{
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-[42px] w-full rounded-md border border-input-border bg-input-background px-3 py-2.5 text-sm text-center shadow-sm transition-colors placeholder:text-muted-foreground focus:border-input-border focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-9 w-full rounded-md border border-input-border bg-input-background px-3 py-1 text-sm text-center shadow-sm transition-colors placeholder:text-muted-foreground focus:border-input-border focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class
|
||||
)
|
||||
" />
|
||||
|
||||
@@ -7,7 +7,7 @@ import { computed, type HTMLAttributes } from 'vue';
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
SelectTriggerProps & {
|
||||
size?: 'small';
|
||||
size?: 'default' | 'sm' | 'lg';
|
||||
class?: HTMLAttributes['class'];
|
||||
showChevron?: boolean;
|
||||
variant?: 'default' | 'outline';
|
||||
@@ -18,11 +18,19 @@ const props = withDefaults(
|
||||
showChevron: true,
|
||||
variant: 'default',
|
||||
active: false,
|
||||
size: 'default',
|
||||
}
|
||||
);
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, showChevron: __, variant: ___, active: ____, ...delegated } = props;
|
||||
const {
|
||||
class: _,
|
||||
showChevron: __,
|
||||
variant: ___,
|
||||
active: ____,
|
||||
size: _____,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
@@ -30,7 +38,14 @@ const delegatedProps = computed(() => {
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
|
||||
const sizeClasses = computed(() => {
|
||||
return props.size === 'small' ? 'h-8 text-xs' : 'h-[42px] w-full text-sm';
|
||||
switch (props.size) {
|
||||
case 'sm':
|
||||
return 'h-8 px-3 text-xs';
|
||||
case 'lg':
|
||||
return 'h-10 px-4 text-sm';
|
||||
default:
|
||||
return 'h-9 px-3 text-sm';
|
||||
}
|
||||
});
|
||||
|
||||
const variantClasses = computed(() => {
|
||||
@@ -49,7 +64,7 @@ const variantClasses = computed(() => {
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center justify-between gap-3 whitespace-nowrap rounded-md px-3 py-2.5 data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start font-medium transition-colors',
|
||||
'flex items-center justify-between gap-3 whitespace-nowrap rounded-md data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start font-medium transition-colors',
|
||||
sizeClasses,
|
||||
variantClasses,
|
||||
props.class
|
||||
|
||||
Reference in New Issue
Block a user