commit
00dbbe0fe4
@ -0,0 +1,35 @@
|
|||||||
|
import { Spinner } from '@tih/ui';
|
||||||
|
|
||||||
|
import ResumseListItem from './ResumeListItem';
|
||||||
|
|
||||||
|
import type { Resume } from '~/types/resume';
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
isLoading: boolean;
|
||||||
|
resumes: Array<Resume>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ResumeListItems({ isLoading, resumes }: Props) {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pt-4">
|
||||||
|
<Spinner display="block" size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pr-8">
|
||||||
|
<ul role="list">
|
||||||
|
{resumes.map((resumeObj: Resume) => (
|
||||||
|
<li key={resumeObj.id}>
|
||||||
|
<ResumseListItem
|
||||||
|
href={`resumes/${resumeObj.id}`}
|
||||||
|
resumeInfo={resumeObj}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { Spinner } from '@tih/ui';
|
||||||
|
|
||||||
|
import Comment from './comment/Comment';
|
||||||
|
|
||||||
|
import type { ResumeComment } from '~/types/resume-comments';
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
comments: Array<ResumeComment>;
|
||||||
|
isLoading: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function CommentListItems({ comments, isLoading }: Props) {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pt-4">
|
||||||
|
<Spinner display="block" size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="m-2 flow-root h-[calc(100vh-20rem)] w-full flex-col space-y-3 overflow-y-scroll">
|
||||||
|
{comments.map((comment) => (
|
||||||
|
<Comment
|
||||||
|
key={comment.id}
|
||||||
|
comment={comment}
|
||||||
|
userId={session?.user?.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { TypeaheadOption } from '@tih/ui';
|
||||||
|
import { Typeahead } from '@tih/ui';
|
||||||
|
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
disabled?: boolean;
|
||||||
|
onSelect: (option: TypeaheadOption) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function CompaniesTypeahead({ disabled, onSelect }: Props) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const companies = trpc.useQuery([
|
||||||
|
'companies.list',
|
||||||
|
{
|
||||||
|
name: query,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { data } = companies;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
disabled={disabled}
|
||||||
|
label="Company"
|
||||||
|
noResultsMessage="No companies found"
|
||||||
|
nullable={true}
|
||||||
|
options={
|
||||||
|
data?.map(({ id, name }) => ({
|
||||||
|
id,
|
||||||
|
label: name,
|
||||||
|
value: id,
|
||||||
|
})) ?? []
|
||||||
|
}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import { Select } from '@tih/ui';
|
||||||
|
|
||||||
|
type Month = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||||
|
|
||||||
|
export type MonthYear = Readonly<{
|
||||||
|
month: Month;
|
||||||
|
year: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
onChange: (value: MonthYear) => void;
|
||||||
|
value: MonthYear;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const MONTH_OPTIONS = [
|
||||||
|
{
|
||||||
|
label: 'January',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'February',
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'March',
|
||||||
|
value: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'April',
|
||||||
|
value: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'May',
|
||||||
|
value: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'June',
|
||||||
|
value: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'July',
|
||||||
|
value: 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'August',
|
||||||
|
value: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'September',
|
||||||
|
value: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'October',
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'November',
|
||||||
|
value: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'December',
|
||||||
|
value: 12,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const NUM_YEARS = 5;
|
||||||
|
const YEAR_OPTIONS = Array.from({ length: NUM_YEARS }, (_, i) => {
|
||||||
|
const year = new Date().getFullYear() - NUM_YEARS + i + 1;
|
||||||
|
return {
|
||||||
|
label: String(year),
|
||||||
|
value: year,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function MonthYearPicker({ value, onChange }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<Select
|
||||||
|
label="Month"
|
||||||
|
options={MONTH_OPTIONS}
|
||||||
|
value={value.month}
|
||||||
|
onChange={(newMonth) =>
|
||||||
|
onChange({ month: Number(newMonth) as Month, year: value.year })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Year"
|
||||||
|
options={YEAR_OPTIONS}
|
||||||
|
value={value.year}
|
||||||
|
onChange={(newYear) =>
|
||||||
|
onChange({ month: value.month, year: Number(newYear) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { createRouter } from './context';
|
||||||
|
|
||||||
|
export const companiesRouter = createRouter().query('list', {
|
||||||
|
input: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
}),
|
||||||
|
async resolve({ ctx, input }) {
|
||||||
|
return await ctx.prisma.company.findMany({
|
||||||
|
orderBy: {
|
||||||
|
name: 'desc',
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
where: {
|
||||||
|
name: {
|
||||||
|
contains: input.name,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -1,27 +0,0 @@
|
|||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { createProtectedRouter } from './context';
|
|
||||||
|
|
||||||
export const resumesResumeUserRouter = createProtectedRouter().mutation(
|
|
||||||
'create',
|
|
||||||
{
|
|
||||||
// TODO: Use enums for experience, location, role
|
|
||||||
input: z.object({
|
|
||||||
additionalInfo: z.string().optional(),
|
|
||||||
experience: z.string(),
|
|
||||||
location: z.string(),
|
|
||||||
role: z.string(),
|
|
||||||
title: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
}),
|
|
||||||
async resolve({ ctx, input }) {
|
|
||||||
const userId = ctx.session?.user.id;
|
|
||||||
return await ctx.prisma.resumesResume.create({
|
|
||||||
data: {
|
|
||||||
...input,
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,42 +0,0 @@
|
|||||||
import { createRouter } from './context';
|
|
||||||
|
|
||||||
import type { Resume } from '~/types/resume';
|
|
||||||
|
|
||||||
export const resumesRouter = createRouter().query('all', {
|
|
||||||
async resolve({ ctx }) {
|
|
||||||
const resumesData = await ctx.prisma.resumesResume.findMany({
|
|
||||||
include: {
|
|
||||||
_count: {
|
|
||||||
select: {
|
|
||||||
comments: true,
|
|
||||||
stars: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return resumesData.map((r) => {
|
|
||||||
const resume: Resume = {
|
|
||||||
additionalInfo: r.additionalInfo,
|
|
||||||
createdAt: r.createdAt,
|
|
||||||
experience: r.experience,
|
|
||||||
id: r.id,
|
|
||||||
location: r.location,
|
|
||||||
numComments: r._count.comments,
|
|
||||||
numStars: r._count.stars,
|
|
||||||
role: r.role,
|
|
||||||
title: r.title,
|
|
||||||
url: r.url,
|
|
||||||
user: r.user.name!,
|
|
||||||
};
|
|
||||||
return resume;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@ -0,0 +1,79 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { createRouter } from '../context';
|
||||||
|
|
||||||
|
import type { Resume } from '~/types/resume';
|
||||||
|
|
||||||
|
export const resumesRouter = createRouter()
|
||||||
|
.query('findAll', {
|
||||||
|
async resolve({ ctx }) {
|
||||||
|
const resumesData = await ctx.prisma.resumesResume.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
comments: true,
|
||||||
|
stars: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return resumesData.map((r) => {
|
||||||
|
const resume: Resume = {
|
||||||
|
additionalInfo: r.additionalInfo,
|
||||||
|
createdAt: r.createdAt,
|
||||||
|
experience: r.experience,
|
||||||
|
id: r.id,
|
||||||
|
location: r.location,
|
||||||
|
numComments: r._count.comments,
|
||||||
|
numStars: r._count.stars,
|
||||||
|
role: r.role,
|
||||||
|
title: r.title,
|
||||||
|
url: r.url,
|
||||||
|
user: r.user.name!,
|
||||||
|
};
|
||||||
|
return resume;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.query('findOne', {
|
||||||
|
input: z.object({
|
||||||
|
resumeId: z.string(),
|
||||||
|
}),
|
||||||
|
async resolve({ ctx, input }) {
|
||||||
|
const { resumeId } = input;
|
||||||
|
const userId = ctx.session?.user?.id;
|
||||||
|
|
||||||
|
// Use the resumeId to query all related information of a single resume
|
||||||
|
// from Resumesresume:
|
||||||
|
return await ctx.prisma.resumesResume.findUnique({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
stars: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stars: {
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: resumeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { createRouter } from './context';
|
import { createRouter } from '../context';
|
||||||
|
|
||||||
import type { ResumeComment } from '~/types/resume-comments';
|
import type { ResumeComment } from '~/types/resume-comments';
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ResumesSection } from '@prisma/client';
|
import { ResumesSection } from '@prisma/client';
|
||||||
|
|
||||||
import { createProtectedRouter } from './context';
|
import { createProtectedRouter } from '../context';
|
||||||
|
|
||||||
type IResumeCommentInput = Readonly<{
|
type IResumeCommentInput = Readonly<{
|
||||||
description: string;
|
description: string;
|
@ -0,0 +1,79 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import type { ComponentMeta } from '@storybook/react';
|
||||||
|
import type { TypeaheadOption } from '@tih/ui';
|
||||||
|
import { Typeahead } from '@tih/ui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
argTypes: {
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
},
|
||||||
|
isLabelHidden: {
|
||||||
|
control: 'boolean',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
noResultsMessage: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
component: Typeahead,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
iframeHeight: 400,
|
||||||
|
inlineStories: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: 'Typeahead',
|
||||||
|
} as ComponentMeta<typeof Typeahead>;
|
||||||
|
|
||||||
|
export function Basic({
|
||||||
|
disabled,
|
||||||
|
isLabelHidden,
|
||||||
|
label,
|
||||||
|
}: Pick<
|
||||||
|
React.ComponentProps<typeof Typeahead>,
|
||||||
|
'disabled' | 'isLabelHidden' | 'label'
|
||||||
|
>) {
|
||||||
|
const people = [
|
||||||
|
{ id: '1', label: 'Wade Cooper', value: '1' },
|
||||||
|
{ id: '2', label: 'Arlene Mccoy', value: '2' },
|
||||||
|
{ id: '3', label: 'Devon Webb', value: '3' },
|
||||||
|
{ id: '4', label: 'Tom Cook', value: '4' },
|
||||||
|
{ id: '5', label: 'Tanya Fox', value: '5' },
|
||||||
|
{ id: '6', label: 'Hellen Schmidt', value: '6' },
|
||||||
|
];
|
||||||
|
const [selectedEntry, setSelectedEntry] = useState<TypeaheadOption>(
|
||||||
|
people[0],
|
||||||
|
);
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
|
const filteredPeople =
|
||||||
|
query === ''
|
||||||
|
? people
|
||||||
|
: people.filter((person) =>
|
||||||
|
person.label
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.includes(query.toLowerCase().replace(/\s+/g, '')),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
disabled={disabled}
|
||||||
|
isLabelHidden={isLabelHidden}
|
||||||
|
label={label}
|
||||||
|
options={filteredPeople}
|
||||||
|
value={selectedEntry}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
onSelect={setSelectedEntry}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Basic.args = {
|
||||||
|
disabled: false,
|
||||||
|
isLabelHidden: false,
|
||||||
|
label: 'Author',
|
||||||
|
};
|
@ -0,0 +1,131 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import { Fragment, useState } from 'react';
|
||||||
|
import { Combobox, Transition } from '@headlessui/react';
|
||||||
|
import { ChevronUpDownIcon } from '@heroicons/react/20/solid';
|
||||||
|
|
||||||
|
export type TypeaheadOption = Readonly<{
|
||||||
|
// String value to uniquely identify the option.
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
disabled?: boolean;
|
||||||
|
isLabelHidden?: boolean;
|
||||||
|
label: string;
|
||||||
|
noResultsMessage?: string;
|
||||||
|
nullable?: boolean;
|
||||||
|
onQueryChange: (
|
||||||
|
value: string,
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => void;
|
||||||
|
onSelect: (option: TypeaheadOption) => void;
|
||||||
|
options: ReadonlyArray<TypeaheadOption>;
|
||||||
|
value?: TypeaheadOption;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function Typeahead({
|
||||||
|
disabled = false,
|
||||||
|
isLabelHidden,
|
||||||
|
label,
|
||||||
|
noResultsMessage = 'No results',
|
||||||
|
nullable = false,
|
||||||
|
options,
|
||||||
|
onQueryChange,
|
||||||
|
value,
|
||||||
|
onSelect,
|
||||||
|
}: Props) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
return (
|
||||||
|
<Combobox
|
||||||
|
by="id"
|
||||||
|
disabled={disabled}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
multiple={false}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
nullable={nullable}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
value={value}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
if (newValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
onSelect(newValue as TypeaheadOption);
|
||||||
|
}}>
|
||||||
|
<Combobox.Label
|
||||||
|
className={clsx(
|
||||||
|
isLabelHidden
|
||||||
|
? 'sr-only'
|
||||||
|
: 'mb-1 block text-sm font-medium text-slate-700',
|
||||||
|
)}>
|
||||||
|
{label}
|
||||||
|
</Combobox.Label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm">
|
||||||
|
<Combobox.Input
|
||||||
|
className={clsx(
|
||||||
|
'w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-slate-900 focus:ring-0',
|
||||||
|
disabled && 'pointer-events-none select-none bg-slate-100',
|
||||||
|
)}
|
||||||
|
displayValue={(option) =>
|
||||||
|
(option as unknown as TypeaheadOption)?.label
|
||||||
|
}
|
||||||
|
onChange={(event) => {
|
||||||
|
setQuery(event.target.value);
|
||||||
|
onQueryChange(event.target.value, event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-5 w-5 text-slate-400"
|
||||||
|
/>
|
||||||
|
</Combobox.Button>
|
||||||
|
</div>
|
||||||
|
<Transition
|
||||||
|
afterLeave={() => setQuery('')}
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0">
|
||||||
|
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
|
{options.length === 0 && query !== '' ? (
|
||||||
|
<div className="relative cursor-default select-none py-2 px-4 text-slate-700">
|
||||||
|
{noResultsMessage}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
options.map((option) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={option.id}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx(
|
||||||
|
'relative cursor-default select-none py-2 px-4 text-slate-500',
|
||||||
|
active && 'bg-slate-100',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={option}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'block truncate',
|
||||||
|
selected ? 'font-medium' : 'font-normal',
|
||||||
|
)}>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Combobox>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue