From c1e28224aa2e1450031669d9a7e359b63e04687d Mon Sep 17 00:00:00 2001 From: Volpeon Date: Wed, 22 Jul 2026 19:40:59 +0200 Subject: Init --- src/ui/objects/ActionButton.tsx | 61 ++++++++++++++++++ src/ui/objects/Alert.tsx | 21 +++++++ src/ui/objects/Avatar.tsx | 69 ++++++++++++++++++++ src/ui/objects/Badge.tsx | 19 ++++++ src/ui/objects/Body.tsx | 21 +++++++ src/ui/objects/Button.tsx | 26 ++++++++ src/ui/objects/Card.tsx | 47 ++++++++++++++ src/ui/objects/CardBody.tsx | 32 ++++++++++ src/ui/objects/CardFooter.tsx | 11 ++++ src/ui/objects/CardImage.tsx | 40 ++++++++++++ src/ui/objects/CollapseCard.tsx | 27 ++++++++ src/ui/objects/ContentImage.tsx | 15 +++++ src/ui/objects/Deflist.tsx | 7 +++ src/ui/objects/Dialog.tsx | 41 ++++++++++++ src/ui/objects/Divider.tsx | 51 +++++++++++++++ src/ui/objects/Emoji.tsx | 21 +++++++ src/ui/objects/Figure.tsx | 13 ++++ src/ui/objects/FigureCaption.tsx | 11 ++++ src/ui/objects/Heading.tsx | 49 +++++++++++++++ src/ui/objects/Icon.tsx | 20 ++++++ src/ui/objects/Lightbox.tsx | 122 ++++++++++++++++++++++++++++++++++++ src/ui/objects/LoadingIndicator.tsx | 10 +++ src/ui/objects/Navbar.tsx | 37 +++++++++++ src/ui/objects/NavbarItem.tsx | 33 ++++++++++ src/ui/objects/PlaceholderBox.tsx | 11 ++++ src/ui/objects/SideNav.tsx | 14 +++++ src/ui/objects/SideNavItem.tsx | 33 ++++++++++ src/ui/objects/StatusIndicator.tsx | 18 ++++++ src/ui/objects/Tab.tsx | 14 +++++ src/ui/objects/Tabbar.tsx | 43 +++++++++++++ src/ui/objects/TextField.tsx | 62 ++++++++++++++++++ src/ui/objects/Thumbnail.tsx | 46 ++++++++++++++ 32 files changed, 1045 insertions(+) create mode 100644 src/ui/objects/ActionButton.tsx create mode 100644 src/ui/objects/Alert.tsx create mode 100644 src/ui/objects/Avatar.tsx create mode 100644 src/ui/objects/Badge.tsx create mode 100644 src/ui/objects/Body.tsx create mode 100644 src/ui/objects/Button.tsx create mode 100644 src/ui/objects/Card.tsx create mode 100644 src/ui/objects/CardBody.tsx create mode 100644 src/ui/objects/CardFooter.tsx create mode 100644 src/ui/objects/CardImage.tsx create mode 100644 src/ui/objects/CollapseCard.tsx create mode 100644 src/ui/objects/ContentImage.tsx create mode 100644 src/ui/objects/Deflist.tsx create mode 100644 src/ui/objects/Dialog.tsx create mode 100644 src/ui/objects/Divider.tsx create mode 100644 src/ui/objects/Emoji.tsx create mode 100644 src/ui/objects/Figure.tsx create mode 100644 src/ui/objects/FigureCaption.tsx create mode 100644 src/ui/objects/Heading.tsx create mode 100644 src/ui/objects/Icon.tsx create mode 100644 src/ui/objects/Lightbox.tsx create mode 100644 src/ui/objects/LoadingIndicator.tsx create mode 100644 src/ui/objects/Navbar.tsx create mode 100644 src/ui/objects/NavbarItem.tsx create mode 100644 src/ui/objects/PlaceholderBox.tsx create mode 100644 src/ui/objects/SideNav.tsx create mode 100644 src/ui/objects/SideNavItem.tsx create mode 100644 src/ui/objects/StatusIndicator.tsx create mode 100644 src/ui/objects/Tab.tsx create mode 100644 src/ui/objects/Tabbar.tsx create mode 100644 src/ui/objects/TextField.tsx create mode 100644 src/ui/objects/Thumbnail.tsx (limited to 'src/ui/objects') diff --git a/src/ui/objects/ActionButton.tsx b/src/ui/objects/ActionButton.tsx new file mode 100644 index 0000000..76f859f --- /dev/null +++ b/src/ui/objects/ActionButton.tsx @@ -0,0 +1,61 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + "as"?: Tag; + "label-as"?: string; + "pill"?: boolean; + "icon"?: JSX.Element; + "pre"?: JSX.Element; + "post"?: JSX.Element; + "highlighted"?: boolean; + "selected"?: boolean; + "wide"?: boolean; + "justify"?: boolean; + "align-block"?: boolean; + "level"?: "button" | "badge" | "quiet"; + "variant"?: "accent" | "positive" | "negative" | "warning" | "static-black" | "static-white"; + "size"?: "xs" | "sm" | "lg" | "xl"; + "children"?: JSX.Element; +}>; + +export const ActionButton = ({ + children, + as: Tag = "a" as T, + "label-as": Label = "span", + pill, + pre, + post, + wide, + justify, + icon, + "align-block": alignBlock, + highlighted, + selected, + level, + variant, + size, + class: cls, + ...props +}: Props) => ( + + {pre} {icon ?? } {post} + +); diff --git a/src/ui/objects/Alert.tsx b/src/ui/objects/Alert.tsx new file mode 100644 index 0000000..f590e18 --- /dev/null +++ b/src/ui/objects/Alert.tsx @@ -0,0 +1,21 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + as?: Tag; + icon?: JSX.Element; + variant?: "accent" | "positive" | "negative" | "warning"; +}>; + +export const Alert = ({ + children, + as: Tag = "div" as T, + variant, + icon, + class: cls, + ...props +}: Props) => ( + + {icon && {icon}} + {children} + +); diff --git a/src/ui/objects/Avatar.tsx b/src/ui/objects/Avatar.tsx new file mode 100644 index 0000000..459af12 --- /dev/null +++ b/src/ui/objects/Avatar.tsx @@ -0,0 +1,69 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +import { StatusIndicator } from "$/ui/objects/StatusIndicator.tsx"; + +export type Props = Polymorphic<{ + "as"?: Tag; + "src"?: string; + "icon"?: JSX.Element; + "children"?: JSX.Element; + "class"?: string; + "circle"?: boolean; + "placeholder"?: boolean; + "color"?: `hsl(${number}, ${number}%, ${number}%)`; + "color2"?: `hsl(${number}, ${number}%, ${number}%)`; + "color-angle"?: number | `${number}`; + "size"?: "xs" | "sm" | "lg" | "xl" | "xxl" | "xxxl"; + "status"?: "accent" | "positive" | "negative" | "warning"; +}>; + +const hslRegex = /^hsl\(([\d.]+), ([\d.]+%), ([\d.]+%)\)$/; + +export const Avatar = ({ + children, + as: Tag = "div" as T, + src, + icon, + circle, + placeholder, + color, + color2, + "color-angle": colorAngle, + size, + status, + class: cls, + ...props +}: Props) => ( + + {status && } + {icon &&
{icon}
} + + {src ? ( + + ) : ( + {children} + )} +
+); diff --git a/src/ui/objects/Badge.tsx b/src/ui/objects/Badge.tsx new file mode 100644 index 0000000..e98ef0d --- /dev/null +++ b/src/ui/objects/Badge.tsx @@ -0,0 +1,19 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +import type { Props as ActionButtonProps } from "./ActionButton.tsx"; + +import { ActionButton } from "./ActionButton.tsx"; + +export type Props = Polymorphic<{ + as?: Tag; + variant?: ActionButtonProps["variant"]; + pill?: boolean; + selected?: boolean; + size?: ActionButtonProps["size"]; + children?: JSX.Element; +}>; + +export const Badge = ({ as = "span" as T, ...props }: Props) => ( + // deno-lint-ignore no-explicit-any + +); diff --git a/src/ui/objects/Body.tsx b/src/ui/objects/Body.tsx new file mode 100644 index 0000000..149662c --- /dev/null +++ b/src/ui/objects/Body.tsx @@ -0,0 +1,21 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"div">; + +export const Body = ({ class: cls, ...props }: Props) => ( +
+); diff --git a/src/ui/objects/Button.tsx b/src/ui/objects/Button.tsx new file mode 100644 index 0000000..68e1776 --- /dev/null +++ b/src/ui/objects/Button.tsx @@ -0,0 +1,26 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +import type { Props as ActionButtonProps } from "./ActionButton.tsx"; + +import { ActionButton } from "./ActionButton.tsx"; + +export type Props = Polymorphic<{ + as?: Tag; + variant?: "primary" | ActionButtonProps["variant"]; + size?: ActionButtonProps["size"]; + wide?: boolean; + justify?: boolean; + children?: JSX.Element; +}>; + +export const Button = ({ as = "a" as T, variant, ...props }: Props) => ( + +); diff --git a/src/ui/objects/Card.tsx b/src/ui/objects/Card.tsx new file mode 100644 index 0000000..fd565e1 --- /dev/null +++ b/src/ui/objects/Card.tsx @@ -0,0 +1,47 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + "as"?: Tag; + "list"?: boolean; + "quiet"?: boolean; + "thumbnail"?: boolean; + "border"?: boolean; + "no-shadow"?: boolean; + "horizontal"?: boolean; + "interactive"?: boolean; + "highlight"?: boolean; + "children"?: JSX.Element; +}>; + +export const Card = ({ + as: Tag = "article" as T, + list, + quiet, + thumbnail, + border, + "no-shadow": noShadow, + horizontal, + interactive, + highlight, + class: cls, + ...props +}: Props) => ( + +); diff --git a/src/ui/objects/CardBody.tsx b/src/ui/objects/CardBody.tsx new file mode 100644 index 0000000..aa5df3d --- /dev/null +++ b/src/ui/objects/CardBody.tsx @@ -0,0 +1,32 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + "as"?: Tag; + "list"?: boolean; + "main"?: boolean; + "no-mbs-neutralize"?: boolean; + "children"?: JSX.Element; +}>; + +export const CardBody = ({ + as: Tag = "div" as T, + list, + main, + "no-mbs-neutralize": noMbsNeutralize, + class: cls, + ...props +}: Props) => ( + +); diff --git a/src/ui/objects/CardFooter.tsx b/src/ui/objects/CardFooter.tsx new file mode 100644 index 0000000..63d74d0 --- /dev/null +++ b/src/ui/objects/CardFooter.tsx @@ -0,0 +1,11 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + as?: Tag; +}>; + +export const CardFooter = ({ + as: Tag = "div" as T, + class: cls, + ...props +}: Props) => ; diff --git a/src/ui/objects/CardImage.tsx b/src/ui/objects/CardImage.tsx new file mode 100644 index 0000000..12b9735 --- /dev/null +++ b/src/ui/objects/CardImage.tsx @@ -0,0 +1,40 @@ +import type { Props as ImageProps } from "corvos/plugins/images.ts"; +import type { CSSProperties } from "https://cdn.jsdelivr.net/gh/oscarotero/ssx@0.1.14/css.ts"; + +import { Image } from "corvos/plugins/images.ts"; + +export type Props = ImageProps & { + "list"?: boolean; + "img-class"?: unknown; + "img-style"?: CSSProperties; + "aspect"?: `${number} / ${number}`; + "content-class"?: unknown; + "children"?: JSX.Element; +}; + +export const CardImage = ({ + children, + list, + "img-class": imgClass, + aspect, + "content-class": contentClass, + alt, + class: cls, + style, + "img-style": imgStyle, + ...props +}: Props) => ( +
+ {alt} + {children &&
{children}
} +
+); diff --git a/src/ui/objects/CollapseCard.tsx b/src/ui/objects/CollapseCard.tsx new file mode 100644 index 0000000..27e1ce2 --- /dev/null +++ b/src/ui/objects/CollapseCard.tsx @@ -0,0 +1,27 @@ +import { Card, type Props as CardProps } from "./Card.tsx"; +import { CardBody } from "./CardBody.tsx"; +import { Heading } from "./Heading.tsx"; + +export type Props = CardProps<"details"> & { + "heading-level": "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + "title"?: JSX.Element; + "header"?: JSX.Element; +}; + +export const CollapseCard = ({ + "heading-level": headingLevel, + children, + title, + header, + ...props +}: Props) => ( + + + + {title} + + {header} + + {children} + +); diff --git a/src/ui/objects/ContentImage.tsx b/src/ui/objects/ContentImage.tsx new file mode 100644 index 0000000..ef45f89 --- /dev/null +++ b/src/ui/objects/ContentImage.tsx @@ -0,0 +1,15 @@ +import type { ClassValue } from "corvos/deps/clsx.ts"; +import type { Props as ImageProps } from "corvos/plugins/images.ts"; + +import { Image } from "corvos/plugins/images.ts"; + +export type Props = Omit & { + "src"?: string; + "img-class"?: ClassValue; +}; + +export const ContentImage = ({ src, "img-class": imgClass, ...props }: Props) => ( + + + +); diff --git a/src/ui/objects/Deflist.tsx b/src/ui/objects/Deflist.tsx new file mode 100644 index 0000000..53d60d6 --- /dev/null +++ b/src/ui/objects/Deflist.tsx @@ -0,0 +1,7 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"dl">; + +export const Deflist = ({ class: cls, ...props }: Props) => ( +
+); diff --git a/src/ui/objects/Dialog.tsx b/src/ui/objects/Dialog.tsx new file mode 100644 index 0000000..8ab6c4f --- /dev/null +++ b/src/ui/objects/Dialog.tsx @@ -0,0 +1,41 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +import { Icon } from "$/ui/objects/Icon.tsx"; + +import { Container } from "../layouts/Container.tsx"; +import { ActionButton } from "./ActionButton.tsx"; +import { Divider } from "./Divider.tsx"; +import { Heading } from "./Heading.tsx"; + +export type Props = HTMLAttributes<"dialog"> & { + title: string; + divider?: boolean; + children?: JSX.Element; +}; + +export const Dialog = ({ children, title, divider, class: cls, ...props }: Props) => ( + + +
+ + {title} + + + } + class="u-mis-auto" + /> +
+ + {divider && } + +
{children}
+
+
+); diff --git a/src/ui/objects/Divider.tsx b/src/ui/objects/Divider.tsx new file mode 100644 index 0000000..8f7efcd --- /dev/null +++ b/src/ui/objects/Divider.tsx @@ -0,0 +1,51 @@ +import type { HTMLAttributes, HTMLTag } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"div" | "hr"> & { + "level"?: "strong" | "medium" | "quiet" | "faint"; + "variant"?: "static-black" | "static-white"; + "vertical"?: boolean | "dot"; + "label-as"?: HTMLTag; + "children"?: JSX.Element; +}; + +export const Divider = ({ + children, + level = "strong", + variant, + vertical, + "label-as": Label = "span", + class: cls, + ...props +}: Props) => + children ? ( +
+ +
+ ) : ( +
+ ); diff --git a/src/ui/objects/Emoji.tsx b/src/ui/objects/Emoji.tsx new file mode 100644 index 0000000..f04f776 --- /dev/null +++ b/src/ui/objects/Emoji.tsx @@ -0,0 +1,21 @@ +import site from "@/corvos.config.ts"; + +export interface Props { + pack: string; + emoji: string; + size?: "125" | "150" | "200"; + class?: string; +} + +export const Emoji = site.$.component( + ({ pack, emoji, size, class: cls, ...props }: Props, pages) => { + const query = site.$.query(pages); + const sprite = query.type(".svg").match({ emojiPack: pack }).one(); + + return ( + + + + ); + }, +); diff --git a/src/ui/objects/Figure.tsx b/src/ui/objects/Figure.tsx new file mode 100644 index 0000000..7f5e20a --- /dev/null +++ b/src/ui/objects/Figure.tsx @@ -0,0 +1,13 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"figure"> & { + children?: JSX.Element; + caption?: JSX.Element; +}; + +export const Figure = ({ children, caption, class: cls, ...props }: Props) => ( +
+ {children} + {caption &&
{caption}
} +
+); diff --git a/src/ui/objects/FigureCaption.tsx b/src/ui/objects/FigureCaption.tsx new file mode 100644 index 0000000..4112170 --- /dev/null +++ b/src/ui/objects/FigureCaption.tsx @@ -0,0 +1,11 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"figcaption"> & { + children?: JSX.Element; +}; + +export const FigureCaption = ({ children, class: cls, ...props }: Props) => ( +
+ {children} +
+); diff --git a/src/ui/objects/Heading.tsx b/src/ui/objects/Heading.tsx new file mode 100644 index 0000000..089de3a --- /dev/null +++ b/src/ui/objects/Heading.tsx @@ -0,0 +1,49 @@ +import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; + +export type Props = Polymorphic<{ + as: Tag; + level?: "xxl" | "xl" | "lg" | "md" | "sm" | "xs"; + variant?: "static-black" | "static-white"; + display?: boolean; + highlight?: boolean; + children?: JSX.Element; +}>; + +const levelTagMap = { + lg: "h3", + md: "h4", + sm: "h5", + xl: "h2", + xs: "h6", + xxl: "h1", +} as const; + +export const Heading = ({ + as, + children, + display, + level, + variant, + highlight, + class: cls, + ...props +}: Props) => { + const Tag = as ?? (level ? levelTagMap[level] : "strong"); + return ( + + {highlight ? {children} : children} + + ); +}; diff --git a/src/ui/objects/Icon.tsx b/src/ui/objects/Icon.tsx new file mode 100644 index 0000000..e8af718 --- /dev/null +++ b/src/ui/objects/Icon.tsx @@ -0,0 +1,20 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +import site from "@/corvos.config.ts"; + +export type Props = HTMLAttributes<"svg"> & { + icon: Parameters[0]; + variant?: Parameters[1]; +}; + +export const Icon = ({ icon, variant, class: cls, ...props }: Props) => ( + +); diff --git a/src/ui/objects/Lightbox.tsx b/src/ui/objects/Lightbox.tsx new file mode 100644 index 0000000..00268cc --- /dev/null +++ b/src/ui/objects/Lightbox.tsx @@ -0,0 +1,122 @@ +/** Biome-ignore-all lint/a11y/useValidAnchor: */ +/** Biome-ignore-all lint/a11y/useAltText: */ + +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +import { Icon } from "$/ui/objects/Icon.tsx"; + +import { ActionButton } from "./ActionButton.tsx"; +import { LoadingIndicator } from "./LoadingIndicator.tsx"; +import { Thumbnail } from "./Thumbnail.tsx"; + +export type Props = HTMLAttributes<"dialog">; + +export const Lightbox = ({ class: cls, ...props }: Props) => ( + +
+ + {" "} + + + } + variant="static-white" + /> + } + variant="static-white" + /> +
+ +
+ } + variant="static-white" + /> + } + variant="static-white" + /> + } + variant="static-white" + /> +
+ + + +
+ +
+ +
+ +
+
+); diff --git a/src/ui/objects/LoadingIndicator.tsx b/src/ui/objects/LoadingIndicator.tsx new file mode 100644 index 0000000..325cbb3 --- /dev/null +++ b/src/ui/objects/LoadingIndicator.tsx @@ -0,0 +1,10 @@ +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"svg">; + +export const LoadingIndicator = (props: Props) => ( + + Loading indicator + + +); diff --git a/src/ui/objects/Navbar.tsx b/src/ui/objects/Navbar.tsx new file mode 100644 index 0000000..4439e58 --- /dev/null +++ b/src/ui/objects/Navbar.tsx @@ -0,0 +1,37 @@ +/** Biome-ignore-all lint/a11y/noNoninteractiveElementToInteractiveRole: */ + +import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; + +export type Props = HTMLAttributes<"nav"> & { + "adapt"?: boolean; + "quiet"?: boolean; + "align-block"?: boolean; + "variant"?: "static-black" | "static-white"; +}; + +export const Navbar = ({ + adapt, + quiet, + "align-block": alignBlock, + variant, + class: cls, + ...props +}: Props) => ( +