116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
|
|
export interface AppHeaderProps {
|
|
brand: ReactNode;
|
|
brandHref?: string;
|
|
brandLabel?: string;
|
|
/** Opt in only for a light, single-color mark, never a full-color image. */
|
|
brandMonochrome?: boolean;
|
|
left?: ReactNode;
|
|
center?: ReactNode;
|
|
right?: ReactNode;
|
|
}
|
|
|
|
export function AppHeader({
|
|
brand,
|
|
brandHref,
|
|
brandLabel = "NODE.DC",
|
|
brandMonochrome = false,
|
|
left,
|
|
center,
|
|
right,
|
|
}: AppHeaderProps) {
|
|
const brandNode = brandHref ? (
|
|
<a className="nodedc-header__brand" data-monochrome={brandMonochrome || undefined} href={brandHref} aria-label={brandLabel}>{brand}</a>
|
|
) : (
|
|
<span className="nodedc-header__brand" data-monochrome={brandMonochrome || undefined} aria-label={brandLabel}>{brand}</span>
|
|
);
|
|
|
|
return (
|
|
<header className="nodedc-header-shell" data-fixed="true" data-preset="launcher">
|
|
<div className="nodedc-header">
|
|
<div className="nodedc-header__row">
|
|
<div className="nodedc-header__left">{brandNode}{left}</div>
|
|
<div className="nodedc-header__center">{center}</div>
|
|
<div className="nodedc-header__right">{right}</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|
|
|
|
export interface HeaderNavigationItem<T extends string> {
|
|
value: T;
|
|
label: ReactNode;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export interface HeaderNavigationProps<T extends string> {
|
|
label: string;
|
|
value?: T;
|
|
items: readonly HeaderNavigationItem<T>[];
|
|
onChange: (value: T) => void;
|
|
}
|
|
|
|
export function HeaderNavigation<T extends string>({ label, value, items, onChange }: HeaderNavigationProps<T>) {
|
|
return (
|
|
<nav className="nodedc-header-navigation" aria-label={label}>
|
|
{items.map((item) => (
|
|
<button
|
|
key={item.value}
|
|
type="button"
|
|
className="nodedc-header__nav-item"
|
|
data-active={item.value === value ? "true" : undefined}
|
|
aria-current={item.value === value ? "page" : undefined}
|
|
disabled={item.disabled}
|
|
onClick={() => onChange(item.value)}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export interface HeaderProfileProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function HeaderProfile({ children }: HeaderProfileProps) {
|
|
return <div className="nodedc-header__profile">{children}</div>;
|
|
}
|
|
|
|
export interface HeaderProfileButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className" | "style"> {}
|
|
|
|
export function HeaderProfileButton({ children, type = "button", ...props }: HeaderProfileButtonProps) {
|
|
return <button type={type} className="nodedc-header__profile-button" {...props}>{children}</button>;
|
|
}
|
|
|
|
export interface HeaderAvatarProps {
|
|
label: string;
|
|
imageUrl?: string;
|
|
}
|
|
|
|
export function HeaderAvatar({ label, imageUrl }: HeaderAvatarProps) {
|
|
return (
|
|
<span className="nodedc-header__avatar" title={label}>
|
|
{imageUrl ? <img src={imageUrl} alt="" /> : label.slice(0, 2).toUpperCase()}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export interface HeaderWorkspaceProps {
|
|
label: string;
|
|
imageUrl?: string;
|
|
kind?: "mark" | "avatar";
|
|
monochrome?: boolean;
|
|
}
|
|
|
|
export function HeaderWorkspace({ label, imageUrl, kind = "mark", monochrome = false }: HeaderWorkspaceProps) {
|
|
return (
|
|
<span className="nodedc-header__workspace" data-kind={kind} data-monochrome={monochrome || undefined} title={label}>
|
|
{imageUrl ? <img src={imageUrl} alt="" /> : label.slice(0, 2).toUpperCase()}
|
|
</span>
|
|
);
|
|
}
|