Zen components conversion.

This commit is contained in:
Mike Cao 2025-03-07 03:11:58 -08:00
parent aac1a12e51
commit 5999bf6256
142 changed files with 1235 additions and 1454 deletions

View file

@ -1,7 +1,7 @@
import { useLocale } from '@/components/hooks';
import { formatDate } from '@/lib/date';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
import { Flexbox, StatusLight } from 'react-basics';
import { Flexbox, StatusLight } from '@umami/react-zen';
const formats = {
millisecond: 'T',

View file

@ -1,6 +1,6 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
import { StatusLight } from 'react-basics';
import { StatusLight } from '@umami/react-zen';
import { formatLongNumber } from '@/lib/format';
export interface BubbleChartProps extends ChartProps {

View file

@ -1,5 +1,5 @@
import { useState, useRef, useEffect, useMemo, ReactNode } from 'react';
import { Loading } from 'react-basics';
import { Loading } from '@umami/react-zen';
import classNames from 'classnames';
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
import { HoverTooltip } from '@/components/common/HoverTooltip';

View file

@ -1,6 +1,6 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
import { StatusLight } from 'react-basics';
import { StatusLight } from '@umami/react-zen';
import { formatLongNumber } from '@/lib/format';
export interface PieChartProps extends ChartProps {

View file

@ -1,5 +1,5 @@
import Link from 'next/link';
import { Flexbox, Icon, Icons, Text } from 'react-basics';
import { Flexbox, Icon, Icons, Text } from '@umami/react-zen';
import styles from './Breadcrumb.module.css';
import { Fragment } from 'react';
@ -25,7 +25,7 @@ export function Breadcrumb({ data }: BreadcrumbProps) {
)}
{i !== data.length - 1 ? (
<Icon rotate={270}>
<Icons.ChevronDown />
<Icons.Chevron />
</Icon>
) : null}
</Fragment>

View file

@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { Icon, Text, Flexbox } from 'react-basics';
import { Icon, Text, Flexbox } from '@umami/react-zen';
import { Icons } from '@/components/icons';
export interface EmptyPlaceholderProps {

View file

@ -1,6 +1,6 @@
import { ErrorInfo, ReactNode } from 'react';
import { ErrorBoundary as Boundary } from 'react-error-boundary';
import { Button } from 'react-basics';
import { Button } from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
import styles from './ErrorBoundary.module.css';

View file

@ -1,4 +1,4 @@
import { Icon, Icons, Text } from 'react-basics';
import { Icon, Icons, Text } from '@umami/react-zen';
import styles from './ErrorMessage.module.css';
import { useMessages } from '@/components/hooks';

View file

@ -1,5 +1,5 @@
import { Key } from 'react';
import { ButtonGroup, Button, Flexbox } from 'react-basics';
import { Row, Button, Flexbox } from '@umami/react-zen';
export interface FilterButtonsProps {
items: any[];
@ -10,9 +10,11 @@ export interface FilterButtonsProps {
export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) {
return (
<Flexbox justifyContent="center">
<ButtonGroup items={items} selectedKey={selectedKey as any} onSelect={onSelect}>
{({ key, label }) => <Button key={key}>{label}</Button>}
</ButtonGroup>
<Row>
{items.map(({ key, label }) => (
<Button key={key}>{label}</Button>
))}
</Row>
</Flexbox>
);
}

View file

@ -1,8 +1,8 @@
import classNames from 'classnames';
import { useMessages, useNavigation } from '@/components/hooks';
import Link from 'next/link';
import { ReactNode } from 'react';
import { Icon, Icons } from 'react-basics';
import classNames from 'classnames';
import Link from 'next/link';
import { Icon, Icons } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks';
import styles from './FilterLink.module.css';
export interface FilterLinkProps {
@ -44,7 +44,7 @@ export function FilterLink({
{externalUrl && (
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
<Icon className={styles.icon}>
<Icons.External />
<Icons.ExternalLink />
</Icon>
</a>
)}

View file

@ -1,4 +1,4 @@
import { Button, Icon, Icons } from 'react-basics';
import { Button, Icon, Icons } from '@umami/react-zen';
import { useState } from 'react';
import { MobileMenu } from './MobileMenu';

View file

@ -1,5 +1,5 @@
import { ReactNode, useEffect, useState } from 'react';
import { Tooltip } from 'react-basics';
import { Tooltip } from '@umami/react-zen';
import styles from './HoverTooltip.module.css';
export function HoverTooltip({ children }: { children: ReactNode }) {

View file

@ -1,29 +1,29 @@
import { ReactNode } from 'react';
import classNames from 'classnames';
import Link from 'next/link';
import { Button } from '@umami/react-zen';
import { useLocale } from '@/components/hooks';
// eslint-disable-next-line css-modules/no-unused-class
import styles from './LinkButton.module.css';
export interface LinkButtonProps {
href: string;
className?: string;
variant?: string;
scroll?: boolean;
variant?: any;
children?: ReactNode;
}
export function LinkButton({ href, className, variant, scroll = true, children }: LinkButtonProps) {
export function LinkButton({
href,
variant = 'quiet',
scroll = true,
children,
...props
}: LinkButtonProps) {
const { dir } = useLocale();
return (
<Link
className={classNames(styles.button, className, { [styles[variant]]: true })}
href={href}
dir={dir}
scroll={scroll}
>
{children}
</Link>
<Button {...props} variant={variant} asChild>
<Link href={href} dir={dir} scroll={scroll}>
{children}
</Link>
</Button>
);
}

View file

@ -2,11 +2,10 @@ import {
Button,
Form,
FormButtons,
FormRow,
FormInput,
FormField,
TextField,
SubmitButton,
} from 'react-basics';
FormSubmitButton,
} from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
export function TypeConfirmationForm({
@ -20,7 +19,7 @@ export function TypeConfirmationForm({
}: {
confirmationValue: string;
buttonLabel?: string;
buttonVariant?: 'none' | 'primary' | 'secondary' | 'quiet' | 'danger';
buttonVariant?: 'primary' | 'secondary' | 'outline' | 'quiet' | 'danger' | 'none';
isLoading?: boolean;
error?: string | Error;
onConfirm?: () => void;
@ -35,18 +34,22 @@ export function TypeConfirmationForm({
return (
<Form onSubmit={onConfirm} error={error}>
<p>
{formatMessage(messages.actionConfirmation, { confirmation: <b>{confirmationValue}</b> })}
{formatMessage(messages.actionConfirmation, {
confirmation: <b key="value">{confirmationValue}</b>,
})}
</p>
<FormRow label={formatMessage(labels.confirm)}>
<FormInput name="confirm" rules={{ validate: value => value === confirmationValue }}>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormButtons flex>
<SubmitButton isLoading={isLoading} variant={buttonVariant}>
<FormField
label={formatMessage(labels.confirm)}
name="confirm"
rules={{ validate: value => value === confirmationValue }}
>
<TextField autoComplete="off" />
</FormField>
<FormButtons>
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
<FormSubmitButton isLoading={isLoading} variant={buttonVariant}>
{buttonLabel || formatMessage(labels.ok)}
</SubmitButton>
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
</FormSubmitButton>
</FormButtons>
</Form>
);

View file

@ -1,4 +1,4 @@
import { Icons as ReactBasicsIcons } from 'react-basics';
import { Icons as ReactBasicsIcons } from '@umami/react-zen';
import * as lucide from 'lucide-react';
import * as LocalIcons from '@/components/svg';

View file

@ -1,5 +1,15 @@
import { useState } from 'react';
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
import { useState, Key } from 'react';
import {
Icon,
Modal,
Select,
Text,
Block,
Row,
ListItem,
ListSeparator,
Dialog,
} from '@umami/react-zen';
import { endOfYear, isSameDay } from 'date-fns';
import { DatePickerForm } from '@/components/metrics/DatePickerForm';
import { useLocale, useMessages } from '@/components/hooks';
@ -39,19 +49,19 @@ export function DateFilter({
label: formatMessage(labels.lastHours, { x: 24 }),
value: '24hour',
},
{ divider: true },
{
label: formatMessage(labels.thisWeek),
value: '0week',
divider: true,
},
{
label: formatMessage(labels.lastDays, { x: 7 }),
value: '7day',
},
{ divider: true },
{
label: formatMessage(labels.thisMonth),
value: '0month',
divider: true,
},
{
label: formatMessage(labels.lastDays, { x: 30 }),
@ -61,7 +71,8 @@ export function DateFilter({
label: formatMessage(labels.lastDays, { x: 90 }),
value: '90day',
},
{ label: formatMessage(labels.thisYear), value: '0year', divider: true },
{ divider: true },
{ label: formatMessage(labels.thisYear), value: '0year' },
{
label: formatMessage(labels.lastMonths, { x: 6 }),
value: '6month',
@ -70,29 +81,31 @@ export function DateFilter({
label: formatMessage(labels.lastMonths, { x: 12 }),
value: '12month',
},
{ divider: true },
showAllTime && {
label: formatMessage(labels.allTime),
value: 'all',
divider: true,
},
{ divider: true },
{
label: formatMessage(labels.customRange),
value: 'custom',
divider: true,
},
].filter(n => n);
]
.filter(n => n)
.map((a, id) => ({ ...a, id }));
const handleChange = (value: string) => {
const handleChange = (value: Key) => {
if (value === 'custom') {
setShowPicker(true);
return;
}
onChange(value);
onChange(value.toString());
};
const handlePickerChange = (value: string) => {
setShowPicker(false);
onChange(value);
onChange(value.toString());
};
const handleClose = () => setShowPicker(false);
@ -122,33 +135,37 @@ export function DateFilter({
return options.find(e => e.value === value)?.label;
};
console.log({ showPicker });
return (
<>
<Dropdown
<Select
className={classNames(className, styles.dropdown)}
items={options}
renderValue={renderValue}
value={value}
alignment={alignment}
placeholder={formatMessage(labels.selectDate)}
onChange={key => handleChange(key as any)}
onSelectionChange={handleChange}
>
{({ label, value, divider }) => (
<Item key={value} divider={divider}>
{label}
</Item>
)}
</Dropdown>
{({ label, value, divider }: any) => {
if (divider) {
return <ListSeparator />;
}
return <ListItem id={value}>{label}</ListItem>;
}}
</Select>
{showPicker && (
<Modal onClose={handleClose}>
<DatePickerForm
startDate={startDate}
endDate={endDate}
minDate={new Date(2000, 0, 1)}
maxDate={endOfYear(new Date())}
onChange={handlePickerChange}
onClose={() => setShowPicker(false)}
/>
<Modal isOpen={true}>
<Dialog>
<DatePickerForm
startDate={startDate}
endDate={endDate}
minDate={new Date(2000, 0, 1)}
maxDate={endOfYear(new Date())}
onChange={handlePickerChange}
onClose={() => setShowPicker(false)}
/>
</Dialog>
</Modal>
)}
</>
@ -167,8 +184,8 @@ const CustomRange = ({ startDate, endDate, unit, onClick }) => {
}
return (
<Flexbox gap={10} alignItems="center" wrap="nowrap">
<Icon className="mr-2" onClick={handleClick}>
<Row gap="3" alignItems="center" wrap="nowrap">
<Icon onClick={handleClick}>
<Icons.Calendar />
</Icon>
<Text>
@ -181,6 +198,6 @@ const CustomRange = ({ startDate, endDate, unit, onClick }) => {
</>
)}
</Text>
</Flexbox>
</Row>
);
};

View file

@ -1,46 +0,0 @@
.menu {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 10px;
background: var(--base50);
z-index: var(--z-index-popup);
border-radius: 5px;
border: 1px solid var(--border-color);
margin-inline-start: 10px;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 200px;
border-radius: 5px;
padding: 5px 10px;
}
.item:hover {
background: var(--base75);
cursor: pointer;
}
.selected {
color: var(--font-color);
font-weight: 700;
background: var(--blue100);
}
.icon {
color: var(--primary400);
}
@media screen and (max-width: 992px) {
.menu {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (max-width: 768px) {
.menu {
transform: translateX(40px);
}
}

View file

@ -1,4 +1,4 @@
import { Button, Icon, Icons, TooltipPopup } from 'react-basics';
import { Button, Icon, Icons, TooltipPopup } from '@umami/react-zen';
import Link from 'next/link';
import { useMessages } from '@/components/hooks';

View file

@ -1,14 +1,6 @@
import { useRef } from 'react';
import {
Text,
Icon,
CalendarMonthSelect,
CalendarYearSelect,
Button,
PopupTrigger,
Popup,
} from 'react-basics';
import { startOfMonth, endOfMonth } from 'date-fns';
import { Row, Text, Icon, Button, MenuTrigger, Popover, Menu, MenuItem } from '@umami/react-zen';
import { startOfMonth, endOfMonth, startOfYear, addMonths, subYears } from 'date-fns';
import { Icons } from '@/components/icons';
import { useLocale } from '@/components/hooks';
import { formatDate } from '@/lib/date';
@ -25,40 +17,48 @@ export function MonthSelect({ date = new Date(), onChange }) {
close();
};
const start = startOfYear(date);
const months: Date[] = [];
for (let i = 0; i < 12; i++) {
months.push(addMonths(start, i));
}
const years: number[] = [];
for (let i = 0; i < 10; i++) {
years.push(subYears(start, 10 - i).getFullYear());
}
return (
<>
<div ref={ref} className={styles.container}>
<PopupTrigger>
<Button className={styles.input} variant="quiet">
<Text>{month}</Text>
<Icon size="sm">
<Icons.ChevronDown />
</Icon>
</Button>
<Popup className={styles.popup} alignment="start">
{close => (
<CalendarMonthSelect
date={date}
locale={dateLocale}
onSelect={handleChange.bind(null, close)}
/>
)}
</Popup>
</PopupTrigger>
<PopupTrigger>
<Button className={styles.input} variant="quiet">
<Text>{year}</Text>
<Icon size="sm">
<Icons.ChevronDown />
</Icon>
</Button>
<Popup className={styles.popup} alignment="start">
{(close: any) => (
<CalendarYearSelect date={date} onSelect={handleChange.bind(null, close)} />
)}
</Popup>
</PopupTrigger>
</div>
</>
<Row>
<MenuTrigger>
<Button className={styles.input} variant="quiet">
<Text>{month}</Text>
<Icon size="sm">
<Icons.Chevron />
</Icon>
</Button>
<Popover>
<Menu items={months}>
{month => {
return <MenuItem id={month}>{month.getDay()}</MenuItem>;
}}
</Menu>
</Popover>
</MenuTrigger>
<MenuTrigger>
<Button className={styles.input} variant="quiet">
<Text>{year}</Text>
<Icon size="sm">
<Icons.Chevron />
</Icon>
</Button>
<Popover>
<Menu items={years}>
{year => {
return <MenuItem id={year}>{year}</MenuItem>;
}}
</Menu>
</Popover>
</MenuTrigger>
</Row>
);
}

View file

@ -1,4 +1,4 @@
import { LoadingButton, Icon, TooltipPopup } from 'react-basics';
import { LoadingButton, Icon, TooltipPopup } from '@umami/react-zen';
import { setWebsiteDateRange } from '@/store/websites';
import { useDateRange } from '@/components/hooks';
import { Icons } from '@/components/icons';

View file

@ -1,4 +1,4 @@
import { Button, Icon, PopupTrigger, Popup, Form, FormRow } from 'react-basics';
import { Button, Icon, PopupTrigger, Popup, Form, FormRow } from '@umami/react-zen';
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
import { Icons } from '@/components/icons';

View file

@ -1,7 +1,7 @@
import { useDateRange, useLocale } from '@/components/hooks';
import { isAfter } from 'date-fns';
import { getOffsetDateRange } from '@/lib/date';
import { Button, Icon, Icons } from 'react-basics';
import { Button, Icon, Icons } from '@umami/react-zen';
import { DateFilter } from './DateFilter';
import styles from './WebsiteDateFilter.module.css';
import { DateRange } from '@/lib/types';
@ -40,14 +40,14 @@ export function WebsiteDateFilter({
/>
{value !== 'all' && !value.startsWith('range') && (
<div className={styles.buttons}>
<Button onClick={() => handleIncrement(-1)}>
<Icon rotate={dir === 'rtl' ? 270 : 90}>
<Icons.ChevronDown />
<Button onPress={() => handleIncrement(-1)}>
<Icon size="sm" rotate={180}>
<Icons.Chevron />
</Icon>
</Button>
<Button onClick={() => handleIncrement(1)} disabled={disableForward}>
<Icon rotate={dir === 'rtl' ? 90 : 270}>
<Icons.ChevronDown />
<Button onPress={() => handleIncrement(1)} isDisabled={disableForward}>
<Icon size="sm">
<Icons.Chevron />
</Icon>
</Button>
</div>

View file

@ -1,5 +1,5 @@
import { useState, Key } from 'react';
import { Dropdown, Item } from 'react-basics';
import { Dropdown, Item } from '@umami/react-zen';
import { useWebsite, useWebsites, useMessages } from '@/components/hooks';
import { Empty } from '@/components/common/Empty';
import styles from './WebsiteSelect.module.css';

View file

@ -0,0 +1,15 @@
import { Row, Column, Text } from '@umami/react-zen';
export function ActionForm({ label, description, children }) {
return (
<Row padding="6" borderSize="1" borderRadius="3" justifyContent="space-between" shadow="2">
<Column>
<Text weight="bold">{label}</Text>
<Text>{description}</Text>
</Column>
<Row gap="3" alignItems="center">
{children}
</Row>
</Row>
);
}

View file

@ -1,6 +1,6 @@
import { CSSProperties } from 'react';
import classNames from 'classnames';
import { mapChildren } from 'react-basics';
import { mapChildren } from '@/lib/react';
// eslint-disable-next-line css-modules/no-unused-class
import styles from './Grid.module.css';

View file

@ -1,29 +1,37 @@
import { List, ListItem, Text } from '@umami/react-zen';
import { usePathname } from 'next/navigation';
import { Column, Button, Text, List, ListItem } from '@umami/react-zen';
import Link from 'next/link';
export interface SideNavProps {
export interface MenuNavProps {
items: any[];
shallow?: boolean;
scroll?: boolean;
selectedKey?: string;
}
export function MenuNav({ items, shallow = true, scroll = false }: SideNavProps) {
const pathname = usePathname();
export function MenuNav({ items, selectedKey }: MenuNavProps) {
return (
<List>
{items.map(({ key, label, url }) => {
const isSelected = pathname.startsWith(url);
return (
<ListItem key={key}>
<Link href={url} shallow={shallow} scroll={scroll}>
<Text weight={isSelected ? 'bold' : 'regular'}>{label}</Text>
</Link>
<ListItem key={key} href={url}>
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
</ListItem>
);
})}
</List>
);
}
export function MenuNav2({ items, selectedKey }: MenuNavProps) {
return (
<Column gap="3" alignItems="flex-start" justifyContent="stretch">
{items.map(({ key, label, url }) => {
return (
<Button key={key} style={{ width: '100%' }} asChild>
<Link href={url}>
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
</Link>
</Button>
);
})}
</Column>
);
}

View file

@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { StatusLight } from 'react-basics';
import { StatusLight } from '@umami/react-zen';
import { useApi } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import styles from './ActiveUsers.module.css';

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import { Icon, Icons } from 'react-basics';
import { Icon, Icons } from '@umami/react-zen';
import { ReactNode } from 'react';
import styles from './ChangeLabel.module.css';
@ -35,7 +35,7 @@ export function ChangeLabel({
>
{!neutral && (
<Icon rotate={positive ? -90 : 90} size={size}>
<Icons.ArrowRight />
<Icons.Arrow />
</Icon>
)}
{children || value}

View file

@ -1,9 +1,9 @@
import { useState } from 'react';
import { Button, ButtonGroup, Calendar } from 'react-basics';
import { Button, Row, Calendar } from '@umami/react-zen';
import { isAfter, isBefore, isSameDay, startOfDay, endOfDay } from 'date-fns';
import { useLocale } from '@/components/hooks';
import { FILTER_DAY, FILTER_RANGE } from '@/lib/constants';
import { useMessages } from '@/components/hooks';
import { parseDate } from '@internationalized/date';
import styles from './DatePickerForm.module.css';
export function DatePickerForm({
@ -17,10 +17,9 @@ export function DatePickerForm({
const [selected, setSelected] = useState(
isSameDay(defaultStartDate, defaultEndDate) ? FILTER_DAY : FILTER_RANGE,
);
const [singleDate, setSingleDate] = useState(defaultStartDate);
const [startDate, setStartDate] = useState(defaultStartDate);
const [endDate, setEndDate] = useState(defaultEndDate);
const { dateLocale } = useLocale();
const [singleDate, setSingleDate] = useState(defaultStartDate || new Date());
const [startDate, setStartDate] = useState(defaultStartDate || new Date());
const [endDate, setEndDate] = useState(defaultEndDate || new Date());
const { formatMessage, labels } = useMessages();
const disabled =
@ -36,48 +35,41 @@ export function DatePickerForm({
}
};
console.log({ minDate, maxDate, singleDate, startDate, endDate, disabled });
return (
<div className={styles.container}>
<div className={styles.filter}>
<ButtonGroup selectedKey={selected} onSelect={key => setSelected(key as any)}>
<Button key={FILTER_DAY}>{formatMessage(labels.singleDay)}</Button>
<Button key={FILTER_RANGE}>{formatMessage(labels.dateRange)}</Button>
</ButtonGroup>
<Row>
<Button key={FILTER_DAY} onPress={key => setSelected(key as any)}>
{formatMessage(labels.singleDay)}
</Button>
<Button key={FILTER_RANGE} onPress={key => setSelected(key as any)}>
{formatMessage(labels.dateRange)}
</Button>
</Row>
</div>
<div className={styles.calendars}>
{selected === FILTER_DAY && (
<Calendar
date={singleDate}
minDate={minDate}
maxDate={maxDate}
locale={dateLocale}
onChange={setSingleDate}
value={parseDate(singleDate.toISOString().split('T')[0])}
onChange={d => setSingleDate(d.toDate('America/Los_Angeles'))}
/>
)}
{selected === FILTER_RANGE && (
<>
<Calendar
date={startDate}
minDate={minDate}
maxDate={endDate}
locale={dateLocale}
onChange={setStartDate}
/>
<Calendar
date={endDate}
minDate={startDate}
maxDate={maxDate}
locale={dateLocale}
onChange={setEndDate}
value={parseDate(startDate.toISOString().split('T')[0])}
onChange={d => setStartDate(d.toDate('America/Los_Angeles'))}
/>
</>
)}
</div>
<div className={styles.buttons}>
<Button variant="primary" onClick={handleSave} disabled={disabled}>
<Button variant="primary" onPress={handleSave} isDisabled={disabled}>
{formatMessage(labels.save)}
</Button>
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
</div>
</div>
);

View file

@ -1,5 +1,5 @@
import { MouseEvent } from 'react';
import { Button, Icon, Icons, Popup, PopupTrigger, Text } from 'react-basics';
import { Button, Icon, Icons, Popover, MenuTrigger, Text, Row } from '@umami/react-zen';
import {
useDateRange,
useFields,
@ -8,7 +8,6 @@ import {
useFormat,
useFilters,
} from '@/components/hooks';
import { PopupForm } from '@/app/(main)/reports/[reportId]/PopupForm';
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
import { OPERATOR_PREFIXES } from '@/lib/constants';
import { isSearchOperator, parseParameterValue } from '@/lib/params';
@ -59,8 +58,17 @@ export function FilterTags({
};
return (
<div className={styles.filters}>
<div className={styles.label}>{formatMessage(labels.filters)}</div>
<Row
gap="3"
backgroundColor="1"
alignItems="center"
paddingX="3"
paddingY="2"
borderRadius="2"
borderSize="1"
marginBottom="6"
>
<Text weight="bold">{formatMessage(labels.filters)}</Text>
{Object.keys(params).map(key => {
if (!params[key]) {
return null;
@ -70,44 +78,44 @@ export function FilterTags({
const paramValue = isSearchOperator(operator) ? value : formatValue(value, key);
return (
<PopupTrigger key={key}>
<div key={key} className={styles.tag}>
<Text className={styles.name}>{label}</Text>
<Text className={styles.operator}>{operatorLabels[operator]}</Text>
<Text className={styles.value}>{paramValue}</Text>
<Icon className={styles.icon} onClick={e => handleCloseFilter(key, e)}>
<Icons.Close />
</Icon>
</div>
<Popup alignment="start">
{(close: () => void) => {
<MenuTrigger key={key}>
<Button variant="outline">
<Row alignItems="center" gap="3">
<Text weight="bold">{label}</Text>
<Text>{operatorLabels[operator]}</Text>
<Text weight="bold">{paramValue}</Text>
<Icon onClick={e => handleCloseFilter(key, e)}>
<Icons.Close />
</Icon>
</Row>
</Button>
<Popover placement="start">
{({ close }: any) => {
return (
<PopupForm>
<FieldFilterEditForm
label={label}
type="string"
websiteId={websiteId}
name={key}
operator={operator}
defaultValue={value}
startDate={startDate}
endDate={endDate}
onChange={values => handleChangeFilter(values, close)}
/>
</PopupForm>
<FieldFilterEditForm
label={label}
type="string"
websiteId={websiteId}
name={key}
operator={operator}
defaultValue={value}
startDate={startDate}
endDate={endDate}
onChange={values => handleChangeFilter(values, close)}
/>
);
}}
</Popup>
</PopupTrigger>
</Popover>
</MenuTrigger>
);
})}
<WebsiteFilterButton websiteId={websiteId} alignment="center" showText={false} />
<Button className={styles.close} variant="quiet" onClick={handleResetFilter}>
<Button className={styles.close} variant="quiet" onPress={handleResetFilter}>
<Icon>
<Icons.Close />
</Icon>
<Text>{formatMessage(labels.clearAll)}</Text>
</Button>
</div>
</Row>
);
}

View file

@ -1,7 +1,7 @@
import { MetricsTable, MetricsTableProps } from './MetricsTable';
import { FilterLink } from '@/components/common/FilterLink';
import { useMessages } from '@/components/hooks';
import { Flexbox } from 'react-basics';
import { Flexbox } from '@umami/react-zen';
export function HostsTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();

View file

@ -1,4 +1,4 @@
import { StatusLight } from 'react-basics';
import { StatusLight } from '@umami/react-zen';
import { colord } from 'colord';
import classNames from 'classnames';
import { LegendItem } from 'chart.js/auto';

View file

@ -1,8 +1,8 @@
import { ReactNode } from 'react';
import { Loading, cloneChildren } from 'react-basics';
import { Loading, Row } from '@umami/react-zen';
import { cloneChildren } from '@/lib/react';
import { ErrorMessage } from '@/components/common/ErrorMessage';
import { formatLongNumber } from '@/lib/format';
import styles from './MetricsBar.module.css';
export interface MetricsBarProps {
isLoading?: boolean;
@ -15,15 +15,15 @@ export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBar
const formatFunc = n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`);
return (
<div className={styles.bar}>
<Row>
{isLoading && !isFetched && <Loading icon="dots" />}
{error && <ErrorMessage />}
{!isLoading &&
!error &&
isFetched &&
cloneChildren(children, child => {
return { format: child.props.format || formatFunc };
return { format: child.props['format'] || formatFunc };
})}
</div>
</Row>
);
}

View file

@ -1,17 +1,11 @@
import { ReactNode, useMemo, useState } from 'react';
import { Loading, Icon, Text, SearchField } from 'react-basics';
import { Loading, Icon, Text, SearchField } from '@umami/react-zen';
import classNames from 'classnames';
import { ErrorMessage } from '@/components/common/ErrorMessage';
import { LinkButton } from '@/components/common/LinkButton';
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
import { percentFilter } from '@/lib/filters';
import {
useNavigation,
useWebsiteMetrics,
useMessages,
useLocale,
useFormat,
} from '@/components/hooks';
import { useNavigation, useWebsiteMetrics, useMessages, useFormat } from '@/components/hooks';
import { Icons } from '@/components/icons';
import { ListTable, ListTableProps } from './ListTable';
import styles from './MetricsTable.module.css';
@ -51,7 +45,6 @@ export function MetricsTable({
const { formatValue } = useFormat();
const { renderUrl } = useNavigation();
const { formatMessage, labels } = useMessages();
const { dir } = useLocale();
const { data, isLoading, isFetched, error } = useWebsiteMetrics(
websiteId,
@ -114,8 +107,8 @@ export function MetricsTable({
{showMore && data && !error && limit && (
<LinkButton href={renderUrl({ view: type })} variant="quiet">
<Text>{formatMessage(labels.more)}</Text>
<Icon size="sm" rotate={dir === 'rtl' ? 180 : 0}>
<Icons.ArrowRight />
<Icon size="sm">
<Icons.Arrow />
</Icon>
</LinkButton>
)}

View file

@ -5,7 +5,7 @@ import { MetricsTable, MetricsTableProps } from './MetricsTable';
import { FilterButtons } from '@/components/common/FilterButtons';
import thenby from 'thenby';
import { GROUPED_DOMAINS } from '@/lib/constants';
import { Flexbox } from 'react-basics';
import { Flexbox } from '@umami/react-zen';
export interface ReferrersTableProps extends MetricsTableProps {
allowFilter?: boolean;

View file

@ -1,7 +1,7 @@
import { MetricsTable, MetricsTableProps } from './MetricsTable';
import { FilterLink } from '@/components/common/FilterLink';
import { useMessages } from '@/components/hooks';
import { Flexbox } from 'react-basics';
import { Flexbox } from '@umami/react-zen';
export function TagsTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();

View file

@ -1,14 +1,7 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgPath = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="none"
viewBox="0 0 64 64"
{...props}
>
<svg xmlns="http://www.w3.org/2000/svg" width={512} height={512} viewBox="0 0 64 64" {...props}>
<path d="m56.4 47.6-6-6c-.8-.8-2-.8-2.8 0s-.8 2 0 2.8l2.6 2.6H18.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5h27C51.3 34 56 29.3 56 23.5S51.3 13 45.5 13H22.7c-.9-3.4-4-6-7.7-6-4.4 0-8 3.6-8 8s3.6 8 8 8c3.7 0 6.8-2.6 7.7-6h22.8c3.6 0 6.5 2.9 6.5 6.5S49.1 30 45.5 30h-27C12.7 30 8 34.7 8 40.5S12.7 51 18.5 51h31.7l-2.6 2.6c-.8.8-.8 2 0 2.8.4.4.9.6 1.4.6s1-.2 1.4-.6l6-6c.8-.8.8-2 0-2.8M15 19c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4" />
</svg>
);