Files
Dashboard/ui/components/DatePicker/DatePicker.tsx
T
dejanandClaude Opus 5 b8bf987d0e feat(dashboard): Analytics-Dashboard als Gestaltungsvorlage zur Übergabe
UI/UX-Prototyp der Analytics-Dashboards in HiveGrid, fachlich auf einen
SHK-/Handwerksbetrieb zugeschnitten. Ziel ist die Übergabe an die Entwicklung,
nicht der Produktivbetrieb.

Inhalt: sechs Seiten (Übersicht, Finanzen, Aufträge, Kunden, Team, Komponenten),
26 KPIs, neun Chart-Bausteine, Filtersystem mit seitenabhängiger Relevanz und ein
deterministischer Demo-Datensatz (420 Kunden, 1.900 Aufträge, Stichtag 30.07.2026).

Drei Quellen sind zusammengeführt: die fachliche Planung mit dem Branchenpartner
(Januar 2026), die Subframe-Komponentenbibliothek aus dem HiveGrid-Pitch und
IBM Carbon Data Visualization für die Diagrammregeln. Herkunft und bewusste
Abweichungen stehen in docs/HERKUNFT.md.

Dokumentation: README, HANDOVER (was echte Logik braucht), docs/DESIGN-SYSTEM,
KPI-KATALOG, DATENMODELL, NAVIGATION, HERKUNFT, ZIELSYSTEM (Einbauort in
HiveGrid Cloud) und LEITPLANKEN (welche Cloud-Screens als Vorbild taugen).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
2026-08-29 17:25:20 +02:00

680 lines
25 KiB
TypeScript

// @subframe/sync-disable
// @ts-nocheck
"use client";
/*
* Documentation:
* Date Picker — https://app.subframe.com/f023064a5ca0/library?component=Date+Picker_e0461776-caeb-4bb3-a9a1-7df3bcfe9afe
* Dropdown Menu select — https://app.subframe.com/f023064a5ca0/library?component=Dropdown+Menu+select_99951515-459b-4286-919e-a89e7549b43b
* Icon Button — https://app.subframe.com/f023064a5ca0/library?component=Icon+Button_af9405b1-8c54-4e01-9786-5aad308224f6
*/
import React from "react";
import { FeatherCalendar } from "@subframe/core";
import { FeatherChevronDown } from "@subframe/core";
import { FeatherChevronLeft } from "@subframe/core";
import { FeatherChevronRight } from "@subframe/core";
import * as SubframeCore from "@subframe/core";
import * as SubframeUtils from "../../utils";
import { DropdownMenuSelect } from "../DropdownMenuSelect";
import { IconButton } from "../IconButton";
export interface WeekdayProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
className?: string;
}
const Weekday = React.forwardRef<HTMLDivElement, WeekdayProps>(function Weekday(
{ children, className, ...otherProps }: WeekdayProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"flex h-8 w-9 items-center justify-center",
className
)}
ref={ref}
{...otherProps}
>
{children ? (
<span className="text-body-extra-small-medium-0 font-body-extra-small-medium-0 text-text-neutral-tertiary">
{children}
</span>
) : null}
</div>
);
});
export interface SelectProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
grow?: boolean;
className?: string;
}
const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select(
{ children, grow = false, className, ...otherProps }: SelectProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"group/f2c7e363 flex cursor-pointer items-center justify-between rounded-sm border border-solid border-border-default bg-surface-neutral-secondary px-2 py-1.5 hover:bg-surface-neutral-tertiary",
{ "w-full": grow },
className
)}
ref={ref}
{...otherProps}
>
<div className="flex items-center gap-2 pr-2">
{children ? (
<span className="text-body-small-medium-0 font-body-small-medium-0 text-text-neutral-primary">
{children}
</span>
) : null}
</div>
<FeatherChevronDown className="text-body-small-medium-0 font-body-small-medium-0 text-icon-neutral-tertiary" />
</div>
);
});
export interface CalendarPanelProps
extends React.HTMLAttributes<HTMLDivElement> {
month?: React.ReactNode;
year?: React.ReactNode;
className?: string;
}
const CalendarPanel = React.forwardRef<HTMLDivElement, CalendarPanelProps>(
function CalendarPanel(
{ month, year, className, ...otherProps }: CalendarPanelProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"flex flex-col items-start gap-2",
className
)}
ref={ref}
{...otherProps}
>
<div className="flex w-80 flex-col items-start overflow-hidden rounded-md border border-solid border-border-default shadow-md">
<div className="flex w-full flex-col items-start bg-surface-neutral-tertiary">
<div className="flex w-full items-center justify-center px-3 py-3">
<div className="flex grow shrink-0 basis-0 items-center justify-center gap-1">
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronLeft />}
/>
<DatePicker.MonthSelect />
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronRight />}
/>
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronLeft />}
/>
<DatePicker.YearSelect />
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronRight />}
/>
</div>
</div>
<div className="flex w-full items-center justify-between px-3">
<DatePicker.Weekday>Mo</DatePicker.Weekday>
<DatePicker.Weekday>Di</DatePicker.Weekday>
<DatePicker.Weekday>Mi</DatePicker.Weekday>
<DatePicker.Weekday>Do</DatePicker.Weekday>
<DatePicker.Weekday>Fr</DatePicker.Weekday>
<DatePicker.Weekday>Sa</DatePicker.Weekday>
<DatePicker.Weekday>So</DatePicker.Weekday>
</div>
</div>
<div className="flex h-px w-full flex-none flex-col items-center gap-2 bg-[#e5e5e5]" />
<div className="flex w-full flex-col items-center gap-1 bg-surface-neutral-secondary px-3 py-3">
<div className="flex w-full items-center justify-between">
<DatePicker.Day outside={true}>27</DatePicker.Day>
<DatePicker.Day outside={true}>28</DatePicker.Day>
<DatePicker.Day outside={true}>29</DatePicker.Day>
<DatePicker.Day outside={true}>30</DatePicker.Day>
<DatePicker.Day outside={true}>31</DatePicker.Day>
<DatePicker.Day>1</DatePicker.Day>
<DatePicker.Day>2</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>3</DatePicker.Day>
<DatePicker.Day>4</DatePicker.Day>
<DatePicker.Day>5</DatePicker.Day>
<DatePicker.Day>6</DatePicker.Day>
<DatePicker.Day>7</DatePicker.Day>
<DatePicker.Day>8</DatePicker.Day>
<DatePicker.Day>9</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>10</DatePicker.Day>
<DatePicker.Day>11</DatePicker.Day>
<DatePicker.Day>12</DatePicker.Day>
<DatePicker.Day>13</DatePicker.Day>
<DatePicker.Day selected={true}>14</DatePicker.Day>
<DatePicker.Day>15</DatePicker.Day>
<DatePicker.Day>16</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>17</DatePicker.Day>
<DatePicker.Day>18</DatePicker.Day>
<DatePicker.Day today={true}>19</DatePicker.Day>
<DatePicker.Day>20</DatePicker.Day>
<DatePicker.Day>21</DatePicker.Day>
<DatePicker.Day>22</DatePicker.Day>
<DatePicker.Day>23</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>24</DatePicker.Day>
<DatePicker.Day>25</DatePicker.Day>
<DatePicker.Day>26</DatePicker.Day>
<DatePicker.Day>27</DatePicker.Day>
<DatePicker.Day>28</DatePicker.Day>
<DatePicker.Day outside={true}>1</DatePicker.Day>
<DatePicker.Day outside={true}>2</DatePicker.Day>
</div>
</div>
</div>
<div className="hidden w-80 flex-col items-start overflow-hidden rounded-md border border-solid border-border-default shadow-md">
<div className="flex w-full flex-col items-start bg-surface-neutral-tertiary">
<div className="flex w-full items-center justify-center px-3 py-3">
<div className="flex grow shrink-0 basis-0 items-center justify-center gap-1">
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronLeft />}
/>
<DatePicker.MonthSelect />
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronRight />}
/>
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronLeft />}
/>
<DatePicker.YearSelect />
<IconButton
variant="ghost"
size="extra-small"
square={true}
icon={<FeatherChevronRight />}
/>
</div>
</div>
<div className="flex w-full items-center justify-between px-3">
<DatePicker.Weekday>Mo</DatePicker.Weekday>
<DatePicker.Weekday>Di</DatePicker.Weekday>
<DatePicker.Weekday>Mi</DatePicker.Weekday>
<DatePicker.Weekday>Do</DatePicker.Weekday>
<DatePicker.Weekday>Fr</DatePicker.Weekday>
<DatePicker.Weekday>Sa</DatePicker.Weekday>
<DatePicker.Weekday>So</DatePicker.Weekday>
</div>
</div>
<div className="flex h-px w-full flex-none flex-col items-center gap-2 bg-[#e5e5e5]" />
<div className="flex w-full flex-col items-center gap-1 bg-surface-neutral-secondary px-3 py-3">
<div className="flex w-full items-center justify-between">
<DatePicker.Day outside={true}>27</DatePicker.Day>
<DatePicker.Day outside={true}>28</DatePicker.Day>
<DatePicker.Day outside={true}>29</DatePicker.Day>
<DatePicker.Day outside={true}>30</DatePicker.Day>
<DatePicker.Day outside={true}>31</DatePicker.Day>
<DatePicker.Day>1</DatePicker.Day>
<DatePicker.Day>2</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>3</DatePicker.Day>
<DatePicker.Day>4</DatePicker.Day>
<DatePicker.Day>5</DatePicker.Day>
<DatePicker.Day>6</DatePicker.Day>
<DatePicker.Day>7</DatePicker.Day>
<DatePicker.Day>8</DatePicker.Day>
<DatePicker.Day>9</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>10</DatePicker.Day>
<DatePicker.Day>11</DatePicker.Day>
<DatePicker.Day>12</DatePicker.Day>
<DatePicker.Day>13</DatePicker.Day>
<DatePicker.Day selected={true}>14</DatePicker.Day>
<DatePicker.Day>15</DatePicker.Day>
<DatePicker.Day>16</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>17</DatePicker.Day>
<DatePicker.Day>18</DatePicker.Day>
<DatePicker.Day today={true}>19</DatePicker.Day>
<DatePicker.Day>20</DatePicker.Day>
<DatePicker.Day>21</DatePicker.Day>
<DatePicker.Day>22</DatePicker.Day>
<DatePicker.Day>23</DatePicker.Day>
</div>
<div className="flex w-full items-center justify-between">
<DatePicker.Day>24</DatePicker.Day>
<DatePicker.Day>25</DatePicker.Day>
<DatePicker.Day>26</DatePicker.Day>
<DatePicker.Day>27</DatePicker.Day>
<DatePicker.Day>28</DatePicker.Day>
<DatePicker.Day outside={true}>1</DatePicker.Day>
<DatePicker.Day outside={true}>2</DatePicker.Day>
</div>
</div>
</div>
</div>
);
}
);
export interface MonthSelectProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
}
const MonthSelect = React.forwardRef<HTMLDivElement, MonthSelectProps>(
function MonthSelect({ className, ...otherProps }: MonthSelectProps, ref) {
return (
<SubframeCore.DropdownMenu.Root>
<SubframeCore.DropdownMenu.Trigger asChild={true}>
<div
className={SubframeUtils.twClassNames(
"flex flex-col items-start gap-2",
className
)}
ref={ref}
{...otherProps}
>
<DatePicker.Select>September</DatePicker.Select>
</div>
</SubframeCore.DropdownMenu.Trigger>
<SubframeCore.DropdownMenu.Portal>
<SubframeCore.DropdownMenu.Content
side="bottom"
align="start"
sideOffset={4}
asChild={true}
>
<DropdownMenuSelect className="min-w-0">
<div className="flex w-full flex-col items-start gap-0.5">
<DatePicker.YearCell>Januar</DatePicker.YearCell>
<DatePicker.YearCell selected={true}>
Februar
</DatePicker.YearCell>
<DatePicker.YearCell>März</DatePicker.YearCell>
<DatePicker.YearCell>April</DatePicker.YearCell>
<DatePicker.YearCell>Mai</DatePicker.YearCell>
<DatePicker.YearCell>Juni</DatePicker.YearCell>
<DatePicker.YearCell>Juli</DatePicker.YearCell>
<DatePicker.YearCell>August</DatePicker.YearCell>
<DatePicker.YearCell>September</DatePicker.YearCell>
<DatePicker.YearCell>Oktober</DatePicker.YearCell>
<DatePicker.YearCell>November</DatePicker.YearCell>
<DatePicker.YearCell>Dezember</DatePicker.YearCell>
</div>
</DropdownMenuSelect>
</SubframeCore.DropdownMenu.Content>
</SubframeCore.DropdownMenu.Portal>
</SubframeCore.DropdownMenu.Root>
);
}
);
export interface YearSelectProps
extends React.ComponentProps<typeof DatePicker.Select> {
className?: string;
}
const YearSelect = React.forwardRef<
React.ElementRef<typeof DatePicker.Select>,
YearSelectProps
>(function YearSelect({ className, ...otherProps }: YearSelectProps, ref) {
return (
<SubframeCore.DropdownMenu.Root>
<SubframeCore.DropdownMenu.Trigger asChild={true}>
<DatePicker.Select className={className} ref={ref} {...otherProps}>
2026
</DatePicker.Select>
</SubframeCore.DropdownMenu.Trigger>
<SubframeCore.DropdownMenu.Portal>
<SubframeCore.DropdownMenu.Content
side="bottom"
align="start"
sideOffset={4}
asChild={true}
>
<DropdownMenuSelect className="min-w-0">
<div className="flex w-full flex-col items-start gap-0.5">
<DatePicker.YearCell>2021</DatePicker.YearCell>
<DatePicker.YearCell>2022</DatePicker.YearCell>
<DatePicker.YearCell>2023</DatePicker.YearCell>
<DatePicker.YearCell>2024</DatePicker.YearCell>
<DatePicker.YearCell>2025</DatePicker.YearCell>
<DatePicker.YearCell selected={true}>2026</DatePicker.YearCell>
<DatePicker.YearCell>2027</DatePicker.YearCell>
<DatePicker.YearCell>2028</DatePicker.YearCell>
<DatePicker.YearCell>2029</DatePicker.YearCell>
<DatePicker.YearCell>2030</DatePicker.YearCell>
<DatePicker.YearCell>2031</DatePicker.YearCell>
</div>
</DropdownMenuSelect>
</SubframeCore.DropdownMenu.Content>
</SubframeCore.DropdownMenu.Portal>
</SubframeCore.DropdownMenu.Root>
);
});
export interface DayProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
selected?: boolean;
today?: boolean;
outside?: boolean;
className?: string;
}
const Day = React.forwardRef<HTMLDivElement, DayProps>(function Day(
{
children,
selected = false,
today = false,
outside = false,
className,
...otherProps
}: DayProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"group/3262f2d3 flex h-full max-h-[36px] min-h-[36px] w-full min-w-[36px] max-w-[36px] cursor-pointer items-center justify-center rounded-round hover:bg-surface-neutral-tertiary",
{
"border border-solid border-border-strong": today,
"bg-brand-brand-surface-solid-default": selected,
},
className
)}
ref={ref}
{...otherProps}
>
{children ? (
<span
className={SubframeUtils.twClassNames(
"text-body-small-regular-0 font-body-small-regular-0 text-text-neutral-primary",
{
"text-text-neutral-subtle": outside,
"text-text-neutral-on-solid": selected,
}
)}
>
{children}
</span>
) : null}
</div>
);
});
export interface YearCellProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
selected?: boolean;
className?: string;
}
const YearCell = React.forwardRef<HTMLDivElement, YearCellProps>(
function YearCell(
{ children, selected = false, className, ...otherProps }: YearCellProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"group/5db54e28 flex h-7 w-full cursor-pointer items-center rounded-sm px-2 group/yearcell hover:bg-surface-feedback-subtle-information-hover",
{ "bg-brand-brand-surface-solid-default": selected },
className
)}
ref={ref}
{...otherProps}
>
{children ? (
<span
className={SubframeUtils.twClassNames(
"text-body-small-medium-0 font-body-small-medium-0 text-text-neutral-primary",
{
"text-text-neutral-on-solid group-hover/5db54e28:text-text-neutral-primary":
selected,
}
)}
>
{children}
</span>
) : null}
</div>
);
}
);
export interface DatePickerRootProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "placeholder"> {
disabled?: boolean;
error?: boolean;
hasValue?: boolean;
size?: "small" | "medium" | "large";
fillContainer?: boolean;
borderless?: boolean;
noPadding?: boolean;
showLabel?: boolean;
label?: React.ReactNode;
helpText?: React.ReactNode;
message?: React.ReactNode;
placeholder?: React.ReactNode;
value?: React.ReactNode;
month?: React.ReactNode;
year?: React.ReactNode;
boolean?: boolean;
className?: string;
}
const DatePickerRoot = React.forwardRef<HTMLDivElement, DatePickerRootProps>(
function DatePickerRoot(
{
disabled = false,
error = false,
hasValue = false,
size = "medium",
fillContainer = false,
borderless = false,
noPadding = false,
showLabel = false,
label,
helpText,
message,
placeholder,
value,
month,
year,
boolean = false,
className,
...otherProps
}: DatePickerRootProps,
ref
) {
return (
<SubframeCore.DropdownMenu.Root>
<SubframeCore.DropdownMenu.Trigger asChild={true}>
<div
className={SubframeUtils.twClassNames(
"group/e0461776 flex min-w-[144px] cursor-pointer flex-col items-start gap-3",
{
"gap-0": showLabel,
"w-full min-w-[240px]": fillContainer,
"cursor-not-allowed": disabled,
},
className
)}
ref={ref}
{...otherProps}
>
<div
className={SubframeUtils.twClassNames(
"hidden flex-col items-start gap-2",
{ flex: showLabel }
)}
>
{label ? (
<span
className={SubframeUtils.twClassNames(
"text-body-default-medium-0 font-body-default-medium-0 text-text-neutral-primary",
{
"text-text-feedback-negative": error,
"text-text-neutral-disabled": disabled,
}
)}
>
{label}
</span>
) : null}
{helpText ? (
<span
className={SubframeUtils.twClassNames(
"text-body-small-regular-0 font-body-small-regular-0 text-text-neutral-tertiary",
{
"text-text-feedback-negative": error,
"text-text-neutral-disabled": disabled,
}
)}
>
{helpText}
</span>
) : null}
</div>
<div
className={SubframeUtils.twClassNames(
"flex w-full flex-col items-start gap-3",
{ "gap-1": showLabel }
)}
>
<div
className={SubframeUtils.twClassNames(
"flex w-full flex-col items-start gap-1 relative",
{ "px-0 pt-1.5 pb-0": showLabel }
)}
>
<div
className={SubframeUtils.twClassNames(
"flex h-9 w-full flex-none items-center gap-2 rounded-sm border border-solid border-border-strong bg-surface-neutral-primary px-3 group-hover/e0461776:bg-surface-neutral-secondary group-focus-within/e0461776:bg-surface-neutral-secondary group-focus-within/e0461776:shadow-focus",
{
"border border-solid border-border-default": showLabel,
"h-auto px-0 py-0": noPadding,
"border-none": borderless,
"h-12": size === "large",
"h-8": size === "small",
"border border-solid border-border-negative-solid group-focus-within/e0461776:border-2 group-focus-within/e0461776:border-solid group-focus-within/e0461776:border-border-negative-solid group-focus-within/e0461776:shadow-none":
error,
"border border-solid border-border-disabled bg-surface-neutral-disabled cursor-not-allowed group-hover/e0461776:bg-surface-neutral-disabled group-focus-within/e0461776:bg-surface-neutral-disabled group-focus-within/e0461776:shadow-none":
disabled,
}
)}
>
{value ? (
<span
className={SubframeUtils.twClassNames(
"hidden grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-secondary",
{
"text-body-small-regular-0 font-body-small-regular-0":
size === "small",
inline: hasValue,
"text-text-neutral-disabled": disabled,
}
)}
>
{value}
</span>
) : null}
{placeholder ? (
<span
className={SubframeUtils.twClassNames(
"grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-subtle",
{
"text-body-small-regular-0 font-body-small-regular-0":
size === "small",
hidden: hasValue,
"text-text-neutral-disabled": disabled,
}
)}
>
{placeholder}
</span>
) : null}
<FeatherCalendar
className={SubframeUtils.twClassNames(
"text-body-default-regular-0 font-body-default-regular-0 text-icon-neutral-tertiary",
{
"text-body-small-regular-0 font-body-small-regular-0":
size === "small",
"text-icon-feedback-negative": error,
"text-icon-neutral-disabled": disabled,
}
)}
/>
</div>
</div>
{message ? (
<span
className={SubframeUtils.twClassNames(
"hidden text-body-extra-small-regular-0 font-body-extra-small-regular-0 text-text-neutral-tertiary",
{
inline: showLabel,
"text-text-feedback-negative": error,
"text-text-neutral-disabled": disabled,
}
)}
>
{message}
</span>
) : null}
</div>
</div>
</SubframeCore.DropdownMenu.Trigger>
<SubframeCore.DropdownMenu.Portal>
<SubframeCore.DropdownMenu.Content
side="bottom"
align="start"
sideOffset={4}
asChild={true}
>
<DropdownMenuSelect className="h-auto w-auto flex-none">
<CalendarPanel month="Februar" year="2025" />
</DropdownMenuSelect>
</SubframeCore.DropdownMenu.Content>
</SubframeCore.DropdownMenu.Portal>
</SubframeCore.DropdownMenu.Root>
);
}
);
export const DatePicker = Object.assign(DatePickerRoot, {
Weekday,
Select,
CalendarPanel,
MonthSelect,
YearSelect,
Day,
YearCell,
});