Files
fx-test/fluxer/packages/admin/src/components/ui/Typography.tsx
Vish 3b9d759b4b feat: add fluxer upstream source and self-hosting documentation
- Clone of github.com/fluxerapp/fluxer (official upstream)
- SELF_HOSTING.md: full VM rebuild procedure, architecture overview,
  service reference, step-by-step setup, troubleshooting, seattle reference
- dev/.env.example: all env vars with secrets redacted and generation instructions
- dev/livekit.yaml: LiveKit config template with placeholder keys
- fluxer-seattle/: existing seattle deployment setup scripts
2026-03-13 00:55:14 -07:00

159 lines
4.6 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
/** @jsxRuntime automatic */
/** @jsxImportSource hono/jsx */
import {cn} from '@fluxer/admin/src/utils/ClassNames';
import type {Child, PropsWithChildren} from 'hono/jsx';
export interface HeadingProps {
level: 1 | 2 | 3 | 4 | 5 | 6;
size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
class?: string;
}
const headingSizes: Record<1 | 2 | 3 | 4 | 5 | 6, string> = {
1: 'text-3xl font-bold',
2: 'text-2xl font-semibold',
3: 'text-xl font-semibold',
4: 'text-lg font-semibold',
5: 'text-base font-semibold',
6: 'text-sm font-semibold',
};
const customSizes: Record<NonNullable<HeadingProps['size']>, string> = {
xs: 'text-xs',
sm: 'text-sm',
base: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
'2xl': 'text-2xl',
'3xl': 'text-3xl',
'4xl': 'text-4xl',
};
export function Heading(props: PropsWithChildren<HeadingProps>) {
const {level, size, children, class: className} = props;
const classes = cn('text-gray-900 tracking-tight', size ? customSizes[size] : headingSizes[level], className);
if (level === 1) return <h1 class={classes}>{children}</h1>;
if (level === 2) return <h2 class={classes}>{children}</h2>;
if (level === 3) return <h3 class={classes}>{children}</h3>;
if (level === 4) return <h4 class={classes}>{children}</h4>;
if (level === 5) return <h5 class={classes}>{children}</h5>;
return <h6 class={classes}>{children}</h6>;
}
export interface TextProps {
size?: 'xs' | 'sm' | 'base' | 'lg';
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
color?: 'default' | 'muted' | 'primary' | 'danger' | 'success';
class?: string;
}
const textSizes: Record<NonNullable<TextProps['size']>, string> = {
xs: 'text-xs',
sm: 'text-sm',
base: 'text-base',
lg: 'text-lg',
};
const textWeights: Record<NonNullable<TextProps['weight']>, string> = {
normal: 'font-normal',
medium: 'font-medium',
semibold: 'font-semibold',
bold: 'font-bold',
};
const textColors: Record<NonNullable<TextProps['color']>, string> = {
default: 'text-gray-900',
muted: 'text-neutral-500',
primary: 'text-brand-primary',
danger: 'text-red-600',
success: 'text-green-600',
};
export function Text(props: PropsWithChildren<TextProps>) {
const {size = 'base', weight = 'normal', color = 'default', children, class: className} = props;
const classes = cn(textSizes[size], textWeights[weight], textColors[color], className);
return <p class={classes}>{children}</p>;
}
export interface LabelProps {
htmlFor?: string;
required?: boolean;
class?: string;
}
export function Label(props: PropsWithChildren<LabelProps>) {
const {htmlFor, required = false, children, class: className} = props;
const classes = cn('block text-xs font-semibold uppercase tracking-wide text-neutral-500', className);
return (
<label for={htmlFor} class={classes}>
{children}
{required && <span class="ml-1 text-red-600">*</span>}
</label>
);
}
export interface CaptionProps {
variant?: 'default' | 'error' | 'success';
class?: string;
}
const captionVariants: Record<NonNullable<CaptionProps['variant']>, string> = {
default: 'text-gray-500',
error: 'text-red-600',
success: 'text-green-600',
};
export function Caption(props: PropsWithChildren<CaptionProps>) {
const {variant = 'default', children, class: className} = props;
const classes = cn('text-xs', captionVariants[variant], className);
return <p class={classes}>{children}</p>;
}
export interface SectionHeadingProps {
actions?: Child;
class?: string;
}
export function SectionHeading(props: PropsWithChildren<SectionHeadingProps>) {
const {actions, children, class: className} = props;
if (actions) {
return (
<div class={cn('mb-4 flex items-center justify-between', className)}>
<h2 class="font-semibold text-gray-900 text-xl">{children}</h2>
<div class="flex items-center gap-2">{actions}</div>
</div>
);
}
return <h2 class={cn('mb-4 font-semibold text-gray-900 text-xl', className)}>{children}</h2>;
}