feat(dashboard): reuse reports filter on overview and add more operators (#31)
This commit is contained in:
@@ -46,10 +46,10 @@ export function OverviewFiltersButtons({
|
||||
size="sm"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => setFilter(filter.name, filter.value[0], 'is')}
|
||||
onClick={() => setFilter(filter.name, [], 'is')}
|
||||
>
|
||||
<span className="mr-1">{getPropertyLabel(filter.name)} is</span>
|
||||
<strong className="font-semibold">{filter.value[0]}</strong>
|
||||
<strong className="font-semibold">{filter.value.join(', ')}</strong>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
|
||||
@@ -8,9 +9,9 @@ import {
|
||||
useEventQueryFilters,
|
||||
useEventQueryNamesFilter,
|
||||
} from '@/hooks/useEventQueryFilters';
|
||||
import { useEventValues } from '@/hooks/useEventValues';
|
||||
import { useProfileProperties } from '@/hooks/useProfileProperties';
|
||||
import { useProfileValues } from '@/hooks/useProfileValues';
|
||||
import { usePropertyValues } from '@/hooks/usePropertyValues';
|
||||
import { XIcon } from 'lucide-react';
|
||||
import type { Options as NuqsOptions } from 'nuqs';
|
||||
|
||||
@@ -35,7 +36,7 @@ export function OverviewFiltersDrawerContent({
|
||||
enableEventsFilter,
|
||||
mode,
|
||||
}: OverviewFiltersDrawerContentProps) {
|
||||
const { interval, range } = useOverviewOptions();
|
||||
const { interval, range, startDate, endDate } = useOverviewOptions();
|
||||
const [filters, setFilter] = useEventQueryFilters(nuqsOptions);
|
||||
const [event, setEvent] = useEventQueryNamesFilter(nuqsOptions);
|
||||
const eventNames = useEventNames({ projectId, interval, range });
|
||||
@@ -49,54 +50,65 @@ export function OverviewFiltersDrawerContent({
|
||||
<SheetTitle>Overview filters</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{enableEventsFilter && (
|
||||
<ComboboxAdvanced
|
||||
<div className="mt-8 flex flex-col rounded-md border bg-def-100">
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
{enableEventsFilter && (
|
||||
<ComboboxAdvanced
|
||||
className="w-full"
|
||||
value={event}
|
||||
onChange={setEvent}
|
||||
// First items is * which is only used for report editing
|
||||
items={eventNames.slice(1).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}))}
|
||||
placeholder="Select event"
|
||||
/>
|
||||
)}
|
||||
<Combobox
|
||||
className="w-full"
|
||||
value={event}
|
||||
onChange={setEvent}
|
||||
// First items is * which is only used for report editing
|
||||
items={eventNames.slice(1).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}))}
|
||||
placeholder="Select event"
|
||||
onChange={(value) => {
|
||||
setFilter(value, [], 'is');
|
||||
}}
|
||||
value=""
|
||||
placeholder="Filter by property"
|
||||
label="What do you want to filter by?"
|
||||
items={properties
|
||||
.filter((item) => item !== 'name')
|
||||
.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}))}
|
||||
searchable
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
<Combobox
|
||||
className="w-full"
|
||||
onChange={(value) => {
|
||||
setFilter(value, '');
|
||||
}}
|
||||
value=""
|
||||
placeholder="Filter by property"
|
||||
label="What do you want to filter by?"
|
||||
items={properties.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}))}
|
||||
searchable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
</div>
|
||||
{filters
|
||||
.filter((filter) => filter.value[0] !== null)
|
||||
.map((filter) => {
|
||||
return mode === 'events' ? (
|
||||
<FilterOptionEvent
|
||||
<PureFilterItem
|
||||
className="border-t p-4 first:border-0"
|
||||
eventName="screen_view"
|
||||
key={filter.name}
|
||||
projectId={projectId}
|
||||
setFilter={setFilter}
|
||||
{...filter}
|
||||
filter={filter}
|
||||
range={range}
|
||||
interval={interval}
|
||||
onRemove={() => {
|
||||
setFilter(filter.name, [], filter.operator);
|
||||
}}
|
||||
onChangeValue={(value) => {
|
||||
setFilter(filter.name, value, filter.operator);
|
||||
}}
|
||||
onChangeOperator={(operator) => {
|
||||
setFilter(filter.name, filter.value, operator);
|
||||
}}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
) : (
|
||||
<FilterOptionProfile
|
||||
key={filter.name}
|
||||
projectId={projectId}
|
||||
setFilter={setFilter}
|
||||
{...filter}
|
||||
/>
|
||||
/* TODO: Implement profile filters */
|
||||
null
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -117,7 +129,7 @@ export function FilterOptionEvent({
|
||||
) => void;
|
||||
}) {
|
||||
const { interval, range } = useOverviewOptions();
|
||||
const values = useEventValues({
|
||||
const values = usePropertyValues({
|
||||
projectId,
|
||||
event: filter.name === 'path' ? 'screen_view' : 'session_start',
|
||||
property: filter.name,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '@/trpc/client';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import type { IChartProps } from '@openpanel/validation';
|
||||
|
||||
@@ -16,7 +18,7 @@ import { ReportPieChart } from './ReportPieChart';
|
||||
|
||||
export type ReportChartProps = IChartProps;
|
||||
|
||||
export function Chart() {
|
||||
function useChartData() {
|
||||
const {
|
||||
interval,
|
||||
events,
|
||||
@@ -32,26 +34,78 @@ export function Chart() {
|
||||
limit,
|
||||
offset,
|
||||
} = useChartContext();
|
||||
const [data] = api.chart.chart.useSuspenseQuery(
|
||||
{
|
||||
|
||||
const [debouncedParams, setDebouncedParams] = useState({
|
||||
interval,
|
||||
events,
|
||||
breakdowns,
|
||||
chartType,
|
||||
range,
|
||||
previous,
|
||||
formula,
|
||||
metric,
|
||||
projectId,
|
||||
startDate,
|
||||
endDate,
|
||||
limit,
|
||||
offset,
|
||||
});
|
||||
|
||||
const debouncedSetParams = useMemo(
|
||||
() => debounce(setDebouncedParams, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedSetParams({
|
||||
interval,
|
||||
chartType,
|
||||
events,
|
||||
events: events.map((event) => ({
|
||||
...event,
|
||||
filters: event.filters?.filter((filter) => filter.value.length > 0),
|
||||
})),
|
||||
breakdowns,
|
||||
chartType,
|
||||
range,
|
||||
startDate,
|
||||
endDate,
|
||||
projectId,
|
||||
previous,
|
||||
formula,
|
||||
metric,
|
||||
projectId,
|
||||
startDate,
|
||||
endDate,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
return () => {
|
||||
debouncedSetParams.cancel();
|
||||
};
|
||||
}, [
|
||||
interval,
|
||||
events,
|
||||
breakdowns,
|
||||
chartType,
|
||||
range,
|
||||
previous,
|
||||
formula,
|
||||
metric,
|
||||
projectId,
|
||||
startDate,
|
||||
endDate,
|
||||
limit,
|
||||
offset,
|
||||
debouncedSetParams,
|
||||
]);
|
||||
|
||||
const [data] = api.chart.chart.useSuspenseQuery(debouncedParams, {
|
||||
keepPreviousData: true,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function Chart() {
|
||||
const { chartType } = useChartContext();
|
||||
const data = useChartData();
|
||||
|
||||
if (data.series.length === 0) {
|
||||
return <ChartEmpty />;
|
||||
|
||||
@@ -58,7 +58,7 @@ export function ReportLineChart({ data }: ReportLineChartProps) {
|
||||
range,
|
||||
},
|
||||
{
|
||||
staleTime: 1000 * 60 * 5,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
}
|
||||
);
|
||||
const formatDate = useFormatDateInterval(interval);
|
||||
@@ -134,7 +134,7 @@ export function ReportLineChart({ data }: ReportLineChartProps) {
|
||||
strokeDasharray="3 3"
|
||||
horizontal={true}
|
||||
vertical={false}
|
||||
className="stroke-def-200"
|
||||
className="stroke-border"
|
||||
/>
|
||||
{references.data?.map((ref) => (
|
||||
<ReferenceLine
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { ColorSquare } from '@/components/color-square';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useEventProperties } from '@/hooks/useEventProperties';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { api } from '@/trpc/client';
|
||||
import { SplitIcon } from 'lucide-react';
|
||||
|
||||
import type { IChartBreakdown } from '@openpanel/validation';
|
||||
@@ -20,12 +20,11 @@ export function ReportBreakdowns() {
|
||||
const range = useSelector((state) => state.report.range);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const propertiesQuery = api.chart.properties.useQuery({
|
||||
const properties = useEventProperties({
|
||||
projectId,
|
||||
range,
|
||||
interval,
|
||||
});
|
||||
const propertiesCombobox = (propertiesQuery.data ?? []).map((item) => ({
|
||||
}).map((item) => ({
|
||||
value: item,
|
||||
label: item, // <RenderDots truncate>{item}</RenderDots>,
|
||||
}));
|
||||
@@ -64,7 +63,7 @@ export function ReportBreakdowns() {
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={propertiesCombobox}
|
||||
items={properties}
|
||||
placeholder="Select..."
|
||||
/>
|
||||
<ReportBreakdownMore onClick={handleMore(item)} />
|
||||
@@ -84,7 +83,7 @@ export function ReportBreakdowns() {
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={propertiesCombobox}
|
||||
items={properties}
|
||||
placeholder="Select breakdown"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ColorSquare } from '@/components/color-square';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
|
||||
import { DropdownMenuComposed } from '@/components/ui/dropdown-menu';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RenderDots } from '@/components/ui/RenderDots';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { usePropertyValues } from '@/hooks/usePropertyValues';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { api } from '@/trpc/client';
|
||||
import { SlidersHorizontal, Trash } from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { RefreshCcwIcon, SlidersHorizontal, Trash } from 'lucide-react';
|
||||
|
||||
import { operators } from '@openpanel/constants';
|
||||
import type {
|
||||
IChartEvent,
|
||||
IChartEventFilter,
|
||||
IChartEventFilterOperator,
|
||||
IChartEventFilterValue,
|
||||
IChartRange,
|
||||
IInterval,
|
||||
} from '@openpanel/validation';
|
||||
import { mapKeys } from '@openpanel/validation';
|
||||
|
||||
@@ -21,52 +28,53 @@ import { changeEvent } from '../../reportSlice';
|
||||
|
||||
interface FilterProps {
|
||||
event: IChartEvent;
|
||||
filter: IChartEvent['filters'][number];
|
||||
filter: IChartEventFilter;
|
||||
}
|
||||
|
||||
interface PureFilterProps {
|
||||
eventName: string;
|
||||
filter: IChartEventFilter;
|
||||
range: IChartRange;
|
||||
startDate: string | null;
|
||||
endDate: string | null;
|
||||
interval: IInterval;
|
||||
onRemove: (filter: IChartEventFilter) => void;
|
||||
onChangeValue: (
|
||||
value: IChartEventFilterValue[],
|
||||
filter: IChartEventFilter
|
||||
) => void;
|
||||
onChangeOperator: (
|
||||
operator: IChartEventFilterOperator,
|
||||
filter: IChartEventFilter
|
||||
) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function FilterItem({ filter, event }: FilterProps) {
|
||||
const { projectId } = useAppParams();
|
||||
const { range, startDate, endDate, interval } = useSelector(
|
||||
(state) => state.report
|
||||
);
|
||||
const getLabel = useMappings();
|
||||
const dispatch = useDispatch();
|
||||
const potentialValues = api.chart.values.useQuery({
|
||||
event: event.name,
|
||||
property: filter.name,
|
||||
projectId,
|
||||
range,
|
||||
interval,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
const valuesCombobox =
|
||||
potentialValues.data?.values?.map((item) => ({
|
||||
value: item,
|
||||
label: getLabel(item),
|
||||
})) ?? [];
|
||||
|
||||
const removeFilter = () => {
|
||||
const onRemove = ({ id }: IChartEventFilter) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
...event,
|
||||
filters: event.filters.filter((item) => item.id !== filter.id),
|
||||
filters: event.filters.filter((item) => item.id !== id),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const changeFilterValue = (
|
||||
value: IChartEventFilterValue | IChartEventFilterValue[]
|
||||
const onChangeValue = (
|
||||
value: IChartEventFilterValue[],
|
||||
{ id }: IChartEventFilter
|
||||
) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
...event,
|
||||
filters: event.filters.map((item) => {
|
||||
if (item.id === filter.id) {
|
||||
if (item.id === id) {
|
||||
return {
|
||||
...item,
|
||||
value: Array.isArray(value) ? value : [value],
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,14 +84,18 @@ export function FilterItem({ filter, event }: FilterProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const changeFilterOperator = (operator: IChartEventFilterOperator) => {
|
||||
const onChangeOperator = (
|
||||
operator: IChartEventFilterOperator,
|
||||
{ id }: IChartEventFilter
|
||||
) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
...event,
|
||||
filters: event.filters.map((item) => {
|
||||
if (item.id === filter.id) {
|
||||
if (item.id === id) {
|
||||
return {
|
||||
...item,
|
||||
value: item.value ? item.value.filter(Boolean).slice(0, 1) : [],
|
||||
operator,
|
||||
};
|
||||
}
|
||||
@@ -94,11 +106,68 @@ export function FilterItem({ filter, event }: FilterProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<div
|
||||
key={filter.name}
|
||||
<PureFilterItem
|
||||
filter={filter}
|
||||
eventName={event.name}
|
||||
range={range}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
interval={interval}
|
||||
onRemove={onRemove}
|
||||
onChangeValue={onChangeValue}
|
||||
onChangeOperator={onChangeOperator}
|
||||
className="px-4 py-2 shadow-[inset_6px_0_0] shadow-def-200 first:border-t"
|
||||
>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function PureFilterItem({
|
||||
filter,
|
||||
eventName,
|
||||
range,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
onRemove,
|
||||
onChangeValue,
|
||||
onChangeOperator,
|
||||
className,
|
||||
}: PureFilterProps) {
|
||||
const { projectId } = useAppParams();
|
||||
const getLabel = useMappings();
|
||||
|
||||
const potentialValues = usePropertyValues({
|
||||
event: eventName,
|
||||
property: filter.name,
|
||||
projectId,
|
||||
range,
|
||||
interval,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
const valuesCombobox =
|
||||
potentialValues.map((item) => ({
|
||||
value: item,
|
||||
label: getLabel(item),
|
||||
})) ?? [];
|
||||
|
||||
const removeFilter = () => {
|
||||
onRemove(filter);
|
||||
};
|
||||
|
||||
const changeFilterValue = (value: IChartEventFilterValue[]) => {
|
||||
onChangeValue(value, filter);
|
||||
};
|
||||
|
||||
const changeFilterOperator = (operator: IChartEventFilterOperator) => {
|
||||
onChangeOperator(operator, filter);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<ColorSquare className="bg-emerald-500">
|
||||
<SlidersHorizontal size={10} />
|
||||
@@ -119,17 +188,78 @@ export function FilterItem({ filter, event }: FilterProps) {
|
||||
}))}
|
||||
label="Operator"
|
||||
>
|
||||
<Button variant={'ghost'} className="whitespace-nowrap">
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className="whitespace-nowrap"
|
||||
size="default"
|
||||
>
|
||||
{operators[filter.operator]}
|
||||
</Button>
|
||||
</DropdownMenuComposed>
|
||||
<ComboboxAdvanced
|
||||
items={valuesCombobox}
|
||||
value={filter.value}
|
||||
className="flex-1"
|
||||
onChange={changeFilterValue}
|
||||
placeholder="Select..."
|
||||
/>
|
||||
{filter.operator === 'is' || filter.operator === 'isNot' ? (
|
||||
<ComboboxAdvanced
|
||||
items={valuesCombobox}
|
||||
value={filter.value}
|
||||
className="flex-1"
|
||||
onChange={changeFilterValue}
|
||||
placeholder="Select..."
|
||||
/>
|
||||
) : (
|
||||
<FilterRawInput
|
||||
value={filter.value[0] ? String(filter.value[0]) : ''}
|
||||
onChangeValue={(value) => changeFilterValue([value])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FilterRawInput({
|
||||
value,
|
||||
onChangeValue,
|
||||
}: {
|
||||
value: string;
|
||||
onChangeValue: (value: string) => void;
|
||||
}) {
|
||||
const [internalValue, setInternalValue] = useState(value || '');
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== internalValue) {
|
||||
setInternalValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
value={internalValue}
|
||||
onChange={(e) => setInternalValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onChangeValue(internalValue);
|
||||
}
|
||||
}}
|
||||
placeholder="Value"
|
||||
size="default"
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||
<AnimatePresence>
|
||||
{internalValue !== value && (
|
||||
<motion.button
|
||||
key="refresh"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
onClick={() => onChangeValue(internalValue)}
|
||||
>
|
||||
<Badge variant="muted">
|
||||
Press enter
|
||||
<RefreshCcwIcon className="ml-1 h-3 w-3" />
|
||||
</Badge>
|
||||
</motion.button>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useEventProperties } from '@/hooks/useEventProperties';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { api } from '@/trpc/client';
|
||||
import { FilterIcon } from 'lucide-react';
|
||||
|
||||
import { shortId } from '@openpanel/common';
|
||||
@@ -21,7 +21,7 @@ export function FiltersCombobox({ event }: FiltersComboboxProps) {
|
||||
const endDate = useSelector((state) => state.report.endDate);
|
||||
const { projectId } = useAppParams();
|
||||
|
||||
const query = api.chart.properties.useQuery(
|
||||
const properties = useEventProperties(
|
||||
{
|
||||
event: event.name,
|
||||
projectId,
|
||||
@@ -35,17 +35,15 @@ export function FiltersCombobox({ event }: FiltersComboboxProps) {
|
||||
}
|
||||
);
|
||||
|
||||
const properties = (query.data ?? []).map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
searchable
|
||||
placeholder="Select a filter"
|
||||
value=""
|
||||
items={properties}
|
||||
items={properties.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex h-[20px] items-center rounded-full border px-1.5 text-sm font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'inline-flex h-[20px] items-center rounded-full border px-2 text-sm font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -19,6 +19,7 @@ const badgeVariants = cva(
|
||||
success:
|
||||
'border-transparent bg-emerald-500 text-emerald-100 hover:bg-emerald-500/80',
|
||||
outline: 'text-foreground',
|
||||
muted: 'bg-def-100 text-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -10,12 +10,13 @@ const inputVariant = cva(
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
default: 'h-8 px-3 py-2 ',
|
||||
sm: 'h-8 px-3 py-2 ',
|
||||
default: 'h-10 px-3 py-2 ',
|
||||
large: 'h-12 px-4 py-3 text-lg',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'default',
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user