dashboard: add dark mode

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-28 06:47:04 +01:00
parent 64701bf29f
commit a1fa48da75
37 changed files with 181 additions and 155 deletions

View File

@@ -0,0 +1,39 @@
'use client';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
export default function DarkModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -18,7 +18,7 @@ export function FullPageEmptyState({
return (
<div className={cn('flex items-center justify-center p-4', className)}>
<div className="flex w-full max-w-xl flex-col items-center justify-center p-8">
<div className="mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-white shadow-sm">
<div className="mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-background shadow-sm">
<Icon size={60} strokeWidth={1} />
</div>

View File

@@ -85,7 +85,7 @@ export function OverviewLiveHistogram({
{staticArray.map((percent, i) => (
<div
key={i}
className="flex-1 animate-pulse rounded-md bg-slate-200"
className="flex-1 animate-pulse rounded-md bg-slate-200 dark:bg-slate-800"
style={{ height: `${percent}%` }}
/>
))}
@@ -147,7 +147,7 @@ function Wrapper({ open, children, count }: WrapperProps) {
<div className="absolute -top-3 right-0 text-xs text-muted-foreground">
NOW
</div>
{/* <div className="md:absolute top-0 left-0 md:card md:p-4 mr-2 md:bg-white/90 z-50"> */}
{/* <div className="md:absolute top-0 left-0 md:card md:p-4 mr-2 md:bg-background/90 z-50"> */}
{children}
</div>
</div>

View File

@@ -192,13 +192,13 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
return (
<>
<div className="col-span-6 grid grid-cols-6 gap-1">
<div className="col-span-6 flex flex-wrap gap-4">
{reports.map((report, index) => (
<button
key={index}
className={cn(
'group relative col-span-3 scale-95 transition-all md:col-span-2 lg:col-span-1',
index === metric && 'z-10 scale-105 rounded-xl shadow-md'
'group relative col-span-3 min-w-[170px] transition-all max-md:flex-1 md:col-span-2 lg:col-span-1 [&_[role="heading"]]:text-sm',
index === metric && 'z-10 scale-[101%] rounded-xl shadow-md'
)}
onClick={() => {
setMetric(index);

View File

@@ -20,7 +20,7 @@ export function WidgetHead({ className, ...props }: WidgetHeadProps) {
return (
<WidgetHeadBase
className={cn(
'flex flex-col p-0 [&_.title]:flex [&_.title]:items-center [&_.title]:justify-between [&_.title]:p-4',
'flex flex-col rounded-t-xl p-0 [&_.title]:flex [&_.title]:items-center [&_.title]:justify-between [&_.title]:p-4',
className
)}
{...props}
@@ -85,7 +85,7 @@ export function WidgetButtons({
<div
ref={container}
className={cn(
'flex flex-wrap justify-start self-stretch px-4 transition-opacity [&_button.active]:border-b [&_button.active]:border-black [&_button.active]:opacity-100 [&_button]:whitespace-nowrap [&_button]:py-1 [&_button]:text-xs [&_button]:opacity-50',
'-mt-2 flex flex-wrap justify-start self-stretch px-4 transition-opacity [&_button.active]:border-b-2 [&_button.active]:border-black [&_button.active]:opacity-100 [&_button]:whitespace-nowrap [&_button]:py-1 [&_button]:text-xs [&_button]:opacity-50',
className
)}
style={{ gap }}

View File

@@ -25,7 +25,7 @@ export const ChartAnimationContainer = (
<div
{...props}
className={cn(
'rounded-md border border-border bg-white p-8',
'rounded-md border border-border bg-background p-8',
props.className
)}
/>

View File

@@ -84,7 +84,10 @@ export function MetricCard({
<div className="flex items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2 text-left font-medium">
<ColorSquare>{serie.event.id}</ColorSquare>
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
<span
role="heading"
className="overflow-hidden text-ellipsis whitespace-nowrap"
>
{serie.name || serie.event.displayName || serie.event.name}
</span>
</div>

View File

@@ -39,9 +39,10 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
<div
key={serie.name}
className={cn(
'relative flex w-full flex-1 items-center gap-4 overflow-hidden rounded px-2 py-3 even:bg-slate-50',
'[&_[role=progressbar]]:shadow-sm [&_[role=progressbar]]:even:bg-white',
isClickable && 'cursor-pointer hover:!bg-slate-100'
'relative flex w-full flex-1 items-center gap-4 overflow-hidden rounded px-2 py-3 even:bg-slate-50 dark:even:bg-slate-100',
'[&_[role=progressbar]]:shadow-sm [&_[role=progressbar]]:even:bg-background',
isClickable &&
'cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-50'
)}
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
>

View File

@@ -39,7 +39,7 @@ export function ReportChartTooltip({
const hidden = sorted.slice(limit);
return (
<div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-white p-3 text-sm shadow-xl">
<div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-background p-3 text-sm shadow-xl">
{visible.map((item, index) => {
// If we have a <Cell /> component, payload can be nested
const payload = item.payload.payload ?? item.payload;

View File

@@ -50,6 +50,11 @@ export function ReportLineChart({
<ResponsiveContainer>
{({ width, height }) => (
<LineChart width={width} height={height} data={rechartData}>
<CartesianGrid
strokeDasharray="3 3"
horizontal={true}
vertical={false}
/>
{references.map((ref) => (
<ReferenceLine
key={ref.id}
@@ -65,11 +70,6 @@ export function ReportLineChart({
fontSize={10}
/>
))}
<CartesianGrid
strokeDasharray="3 3"
horizontal={true}
vertical={false}
/>
<YAxis
width={getYAxisWidth(data.metrics.max)}
fontSize={12}

View File

@@ -1,5 +1,8 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { TriangleIcon } from 'lucide-react';
import type { IChartInput } from '@openpanel/validation';
import { Funnel } from '../funnel';
@@ -12,7 +15,18 @@ export const ChartSwitch = withChartProivder(function ChartSwitch(
props: ReportChartProps
) {
if (props.chartType === 'funnel') {
return <Funnel {...props} />;
return (
<>
<Alert>
<TriangleIcon className="h-4 w-4" />
<AlertTitle>Keep in mind</AlertTitle>
<AlertDescription>
Funnel chart is still experimental and might not work as expected.
</AlertDescription>
</Alert>
<Funnel {...props} />
</>
);
}
return <Chart {...props} />;

View File

@@ -99,7 +99,7 @@ export function FunnelSteps({
)}
key={step.event.id}
>
<div className="card divide-y divide-border bg-white">
<div className="card divide-y divide-border bg-background">
<div className="p-4">
<p className="text-muted-foreground">Step {index + 1}</p>
<h3 className="font-bold">
@@ -108,7 +108,7 @@ export function FunnelSteps({
</div>
<div className="relative aspect-square">
<FunnelChart from={step.prevPercent} to={step.percent} />
<div className="absolute left-0 right-0 top-0 flex flex-col bg-white/40 p-4">
<div className="absolute left-0 right-0 top-0 flex flex-col bg-background/40 p-4">
<div className="font-medium uppercase text-muted-foreground">
Sessions
</div>

View File

@@ -135,7 +135,7 @@ export function ReportEvents() {
]}
label="Segment"
>
<button className="flex items-center gap-1 rounded-md border border-border bg-white p-1 px-2 text-xs font-medium leading-none">
<button className="flex items-center gap-1 rounded-md border border-border bg-background p-1 px-2 text-xs font-medium leading-none">
{event.segment === 'user' ? (
<>
<Users size={12} /> Unique users

View File

@@ -54,7 +54,7 @@ export function FiltersCombobox({ event }: FiltersComboboxProps) {
);
}}
>
<button className="flex items-center gap-1 rounded-md border border-border bg-white p-1 px-2 text-xs font-medium leading-none">
<button className="flex items-center gap-1 rounded-md border border-border bg-background p-1 px-2 text-xs font-medium leading-none">
<FilterIcon size={12} /> Add filter
</button>
</Combobox>

View File

@@ -23,7 +23,7 @@ export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
<div className="bg-black/5 p-1 px-2">{name}</div>
<div
className={cn(
'overflow-hidden text-ellipsis whitespace-nowrap bg-white p-1 px-2 font-mono text-blue-700 shadow-[inset_0_0_0_1px_#fff]',
'overflow-hidden text-ellipsis whitespace-nowrap bg-background p-1 px-2 font-mono text-blue-700',
clickable && 'group-hover:underline'
)}
>

View File

@@ -101,7 +101,7 @@ const SheetFooter = ({
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
'sticky bottom-0 left-0 right-0 mt-auto bg-white',
'sticky bottom-0 left-0 right-0 mt-auto bg-background',
className
)}
{...props}

View File

@@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
'h-10 px-4 text-left align-middle font-medium text-muted-foreground shadow-[0_0_0_0.5px] shadow-border [&:has([role=checkbox])]:pr-0',
'h-10 border-b border-border bg-slate-50 px-4 text-left align-middle text-sm font-medium text-muted-foreground text-slate-500 shadow-[0_0_0_0.5px] shadow-border [&:has([role=checkbox])]:pr-0',
className
)}
{...props}

View File

@@ -21,7 +21,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md border bg-black px-3 py-1.5 text-sm text-popover shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 overflow-hidden rounded-md border bg-popover-foreground px-3 py-1.5 text-sm text-popover shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}