mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 05:37:20 +01:00
Zen components conversion.
This commit is contained in:
parent
aac1a12e51
commit
5999bf6256
142 changed files with 1235 additions and 1454 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
15
src/components/layout/ActionForm.tsx
Normal file
15
src/components/layout/ActionForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue