diff options
| author | Volpeon <git@volpeon.ink> | 2026-07-22 19:40:59 +0200 |
|---|---|---|
| committer | Volpeon <git@volpeon.ink> | 2026-07-22 19:40:59 +0200 |
| commit | c1e28224aa2e1450031669d9a7e359b63e04687d (patch) | |
| tree | 68f3d8905c763a91f68001d868197effa92adafa /src/ui/objects | |
| download | corvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.tar.gz corvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.tar.bz2 corvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.zip | |
Init
Diffstat (limited to 'src/ui/objects')
32 files changed, 1045 insertions, 0 deletions
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | "as"?: Tag; | ||
| 5 | "label-as"?: string; | ||
| 6 | "pill"?: boolean; | ||
| 7 | "icon"?: JSX.Element; | ||
| 8 | "pre"?: JSX.Element; | ||
| 9 | "post"?: JSX.Element; | ||
| 10 | "highlighted"?: boolean; | ||
| 11 | "selected"?: boolean; | ||
| 12 | "wide"?: boolean; | ||
| 13 | "justify"?: boolean; | ||
| 14 | "align-block"?: boolean; | ||
| 15 | "level"?: "button" | "badge" | "quiet"; | ||
| 16 | "variant"?: "accent" | "positive" | "negative" | "warning" | "static-black" | "static-white"; | ||
| 17 | "size"?: "xs" | "sm" | "lg" | "xl"; | ||
| 18 | "children"?: JSX.Element; | ||
| 19 | }>; | ||
| 20 | |||
| 21 | export const ActionButton = <T extends HTMLTag = "a">({ | ||
| 22 | children, | ||
| 23 | as: Tag = "a" as T, | ||
| 24 | "label-as": Label = "span", | ||
| 25 | pill, | ||
| 26 | pre, | ||
| 27 | post, | ||
| 28 | wide, | ||
| 29 | justify, | ||
| 30 | icon, | ||
| 31 | "align-block": alignBlock, | ||
| 32 | highlighted, | ||
| 33 | selected, | ||
| 34 | level, | ||
| 35 | variant, | ||
| 36 | size, | ||
| 37 | class: cls, | ||
| 38 | ...props | ||
| 39 | }: Props<T>) => ( | ||
| 40 | <Tag | ||
| 41 | class:list={[ | ||
| 42 | "o-button", | ||
| 43 | { | ||
| 44 | "o-button--pill": pill, | ||
| 45 | "o-button--wide": wide, | ||
| 46 | "o-button--justify": justify, | ||
| 47 | "o-button--icon": icon, | ||
| 48 | "o-button--align-block": alignBlock, | ||
| 49 | "is-highlighted": highlighted, | ||
| 50 | "is-selected": selected, | ||
| 51 | [`o-button--${level}`]: level && level !== "button", | ||
| 52 | [`o-button--${variant}`]: variant, | ||
| 53 | [`o-button--${size}`]: size, | ||
| 54 | }, | ||
| 55 | cls, | ||
| 56 | ]} | ||
| 57 | {...props} | ||
| 58 | > | ||
| 59 | {pre} {icon ?? <Label class="o-button__label">{children}</Label>} {post} | ||
| 60 | </Tag> | ||
| 61 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as?: Tag; | ||
| 5 | icon?: JSX.Element; | ||
| 6 | variant?: "accent" | "positive" | "negative" | "warning"; | ||
| 7 | }>; | ||
| 8 | |||
| 9 | export const Alert = <T extends HTMLTag = "div">({ | ||
| 10 | children, | ||
| 11 | as: Tag = "div" as T, | ||
| 12 | variant, | ||
| 13 | icon, | ||
| 14 | class: cls, | ||
| 15 | ...props | ||
| 16 | }: Props<T>) => ( | ||
| 17 | <Tag class:list={["o-alert", { [`o-alert--${variant}`]: variant }, cls]} {...props}> | ||
| 18 | {icon && <span class="u-mie-100">{icon}</span>} | ||
| 19 | {children} | ||
| 20 | </Tag> | ||
| 21 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | import { StatusIndicator } from "$/ui/objects/StatusIndicator.tsx"; | ||
| 4 | |||
| 5 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 6 | "as"?: Tag; | ||
| 7 | "src"?: string; | ||
| 8 | "icon"?: JSX.Element; | ||
| 9 | "children"?: JSX.Element; | ||
| 10 | "class"?: string; | ||
| 11 | "circle"?: boolean; | ||
| 12 | "placeholder"?: boolean; | ||
| 13 | "color"?: `hsl(${number}, ${number}%, ${number}%)`; | ||
| 14 | "color2"?: `hsl(${number}, ${number}%, ${number}%)`; | ||
| 15 | "color-angle"?: number | `${number}`; | ||
| 16 | "size"?: "xs" | "sm" | "lg" | "xl" | "xxl" | "xxxl"; | ||
| 17 | "status"?: "accent" | "positive" | "negative" | "warning"; | ||
| 18 | }>; | ||
| 19 | |||
| 20 | const hslRegex = /^hsl\(([\d.]+), ([\d.]+%), ([\d.]+%)\)$/; | ||
| 21 | |||
| 22 | export const Avatar = <T extends HTMLTag = "div">({ | ||
| 23 | children, | ||
| 24 | as: Tag = "div" as T, | ||
| 25 | src, | ||
| 26 | icon, | ||
| 27 | circle, | ||
| 28 | placeholder, | ||
| 29 | color, | ||
| 30 | color2, | ||
| 31 | "color-angle": colorAngle, | ||
| 32 | size, | ||
| 33 | status, | ||
| 34 | class: cls, | ||
| 35 | ...props | ||
| 36 | }: Props<T>) => ( | ||
| 37 | <Tag | ||
| 38 | class:list={[ | ||
| 39 | "o-avatar", | ||
| 40 | { | ||
| 41 | "o-avatar--circle": circle, | ||
| 42 | "o-avatar--placeholder": placeholder, | ||
| 43 | "o-avatar--colored": color && !color2, | ||
| 44 | "o-avatar--colored-gradient": color && !!color2, | ||
| 45 | [`o-avatar--${size}`]: !!size, | ||
| 46 | }, | ||
| 47 | cls, | ||
| 48 | ]} | ||
| 49 | style={{ | ||
| 50 | "--o-avatar--bg-angle": colorAngle, | ||
| 51 | "--o-avatar--bg-color--h": color?.match(hslRegex)?.[1], | ||
| 52 | "--o-avatar--bg-color--l": color?.match(hslRegex)?.[3], | ||
| 53 | "--o-avatar--bg-color--s": color?.match(hslRegex)?.[2], | ||
| 54 | "--o-avatar--bg-color-2--h": color2?.match(hslRegex)?.[1], | ||
| 55 | "--o-avatar--bg-color-2--l": color2?.match(hslRegex)?.[3], | ||
| 56 | "--o-avatar--bg-color-2--s": color2?.match(hslRegex)?.[2], | ||
| 57 | }} | ||
| 58 | {...props} | ||
| 59 | > | ||
| 60 | {status && <StatusIndicator class="o-avatar__status" status={status} />} | ||
| 61 | {icon && <div class="o-avatar__icon u-ai-end u-jc-end">{icon}</div>} | ||
| 62 | |||
| 63 | {src ? ( | ||
| 64 | <img alt="" class="o-avatar__content" src={src} /> | ||
| 65 | ) : ( | ||
| 66 | <span class="o-avatar__content">{children}</span> | ||
| 67 | )} | ||
| 68 | </Tag> | ||
| 69 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | import type { Props as ActionButtonProps } from "./ActionButton.tsx"; | ||
| 4 | |||
| 5 | import { ActionButton } from "./ActionButton.tsx"; | ||
| 6 | |||
| 7 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 8 | as?: Tag; | ||
| 9 | variant?: ActionButtonProps<Tag>["variant"]; | ||
| 10 | pill?: boolean; | ||
| 11 | selected?: boolean; | ||
| 12 | size?: ActionButtonProps<Tag>["size"]; | ||
| 13 | children?: JSX.Element; | ||
| 14 | }>; | ||
| 15 | |||
| 16 | export const Badge = <T extends HTMLTag = "span">({ as = "span" as T, ...props }: Props<T>) => ( | ||
| 17 | // deno-lint-ignore no-explicit-any | ||
| 18 | <ActionButton as={as} level="badge" {...(props as any)} /> | ||
| 19 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"div">; | ||
| 4 | |||
| 5 | export const Body = ({ class: cls, ...props }: Props) => ( | ||
| 6 | <div | ||
| 7 | class:list={[ | ||
| 8 | "s-body", | ||
| 9 | "s-headings", | ||
| 10 | "s-headings--display", | ||
| 11 | "s-links", | ||
| 12 | "s-links--colored", | ||
| 13 | "s-code", | ||
| 14 | "s-figures", | ||
| 15 | "s-blockquotes", | ||
| 16 | "s-lists", | ||
| 17 | cls, | ||
| 18 | ]} | ||
| 19 | {...props} | ||
| 20 | /> | ||
| 21 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | import type { Props as ActionButtonProps } from "./ActionButton.tsx"; | ||
| 4 | |||
| 5 | import { ActionButton } from "./ActionButton.tsx"; | ||
| 6 | |||
| 7 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 8 | as?: Tag; | ||
| 9 | variant?: "primary" | ActionButtonProps<Tag>["variant"]; | ||
| 10 | size?: ActionButtonProps<Tag>["size"]; | ||
| 11 | wide?: boolean; | ||
| 12 | justify?: boolean; | ||
| 13 | children?: JSX.Element; | ||
| 14 | }>; | ||
| 15 | |||
| 16 | export const Button = <T extends HTMLTag = "a">({ as = "a" as T, variant, ...props }: Props<T>) => ( | ||
| 17 | <ActionButton | ||
| 18 | as={as} | ||
| 19 | label-as={variant ? "strong" : "span"} | ||
| 20 | pill | ||
| 21 | selected={!!variant} | ||
| 22 | variant={variant === "primary" ? undefined : variant} | ||
| 23 | // deno-lint-ignore no-explicit-any | ||
| 24 | {...(props as any)} | ||
| 25 | /> | ||
| 26 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | "as"?: Tag; | ||
| 5 | "list"?: boolean; | ||
| 6 | "quiet"?: boolean; | ||
| 7 | "thumbnail"?: boolean; | ||
| 8 | "border"?: boolean; | ||
| 9 | "no-shadow"?: boolean; | ||
| 10 | "horizontal"?: boolean; | ||
| 11 | "interactive"?: boolean; | ||
| 12 | "highlight"?: boolean; | ||
| 13 | "children"?: JSX.Element; | ||
| 14 | }>; | ||
| 15 | |||
| 16 | export const Card = <T extends HTMLTag = "article">({ | ||
| 17 | as: Tag = "article" as T, | ||
| 18 | list, | ||
| 19 | quiet, | ||
| 20 | thumbnail, | ||
| 21 | border, | ||
| 22 | "no-shadow": noShadow, | ||
| 23 | horizontal, | ||
| 24 | interactive, | ||
| 25 | highlight, | ||
| 26 | class: cls, | ||
| 27 | ...props | ||
| 28 | }: Props<T>) => ( | ||
| 29 | <Tag | ||
| 30 | class:list={[ | ||
| 31 | "o-card", | ||
| 32 | { | ||
| 33 | "l-card-list__card": list, | ||
| 34 | "o-card--borderless": !border, | ||
| 35 | "o-card--horizontal": horizontal, | ||
| 36 | "o-card--interactive": interactive, | ||
| 37 | "o-card--highlight": highlight, | ||
| 38 | "o-card--quiet": quiet, | ||
| 39 | "o-card--collapsible": Tag === "details", | ||
| 40 | "o-card--shadow": !noShadow, | ||
| 41 | "o-card--thumbnail": thumbnail, | ||
| 42 | }, | ||
| 43 | cls, | ||
| 44 | ]} | ||
| 45 | {...props} | ||
| 46 | /> | ||
| 47 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | "as"?: Tag; | ||
| 5 | "list"?: boolean; | ||
| 6 | "main"?: boolean; | ||
| 7 | "no-mbs-neutralize"?: boolean; | ||
| 8 | "children"?: JSX.Element; | ||
| 9 | }>; | ||
| 10 | |||
| 11 | export const CardBody = <T extends HTMLTag = "div">({ | ||
| 12 | as: Tag = "div" as T, | ||
| 13 | list, | ||
| 14 | main, | ||
| 15 | "no-mbs-neutralize": noMbsNeutralize, | ||
| 16 | class: cls, | ||
| 17 | ...props | ||
| 18 | }: Props<T>) => ( | ||
| 19 | <Tag | ||
| 20 | class:list={[ | ||
| 21 | "o-card__body", | ||
| 22 | { | ||
| 23 | "o-card__body--main": main, | ||
| 24 | "o-card__header": Tag === "summary", | ||
| 25 | "l-card-list__card-body": list, | ||
| 26 | "o-card__body--no-mbs-neutralize": noMbsNeutralize, | ||
| 27 | }, | ||
| 28 | cls, | ||
| 29 | ]} | ||
| 30 | {...props} | ||
| 31 | /> | ||
| 32 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as?: Tag; | ||
| 5 | }>; | ||
| 6 | |||
| 7 | export const CardFooter = <T extends HTMLTag = "div">({ | ||
| 8 | as: Tag = "div" as T, | ||
| 9 | class: cls, | ||
| 10 | ...props | ||
| 11 | }: Props<T>) => <Tag class:list={["o-card__footer", cls]} {...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 @@ | |||
| 1 | import type { Props as ImageProps } from "corvos/plugins/images.ts"; | ||
| 2 | import type { CSSProperties } from "https://cdn.jsdelivr.net/gh/oscarotero/ssx@0.1.14/css.ts"; | ||
| 3 | |||
| 4 | import { Image } from "corvos/plugins/images.ts"; | ||
| 5 | |||
| 6 | export type Props = ImageProps & { | ||
| 7 | "list"?: boolean; | ||
| 8 | "img-class"?: unknown; | ||
| 9 | "img-style"?: CSSProperties; | ||
| 10 | "aspect"?: `${number} / ${number}`; | ||
| 11 | "content-class"?: unknown; | ||
| 12 | "children"?: JSX.Element; | ||
| 13 | }; | ||
| 14 | |||
| 15 | export const CardImage = ({ | ||
| 16 | children, | ||
| 17 | list, | ||
| 18 | "img-class": imgClass, | ||
| 19 | aspect, | ||
| 20 | "content-class": contentClass, | ||
| 21 | alt, | ||
| 22 | class: cls, | ||
| 23 | style, | ||
| 24 | "img-style": imgStyle, | ||
| 25 | ...props | ||
| 26 | }: Props) => ( | ||
| 27 | <div class:list={["o-card__image", cls]} style={style}> | ||
| 28 | <Image | ||
| 29 | class:list={["o-card__image-img", { "l-card-list__card-image": list }, imgClass]} | ||
| 30 | style={{ | ||
| 31 | "aspect-ratio": aspect, | ||
| 32 | "block-size": aspect ? "auto" : undefined, | ||
| 33 | ...imgStyle, | ||
| 34 | }} | ||
| 35 | alt={alt} | ||
| 36 | {...props} | ||
| 37 | /> | ||
| 38 | {children && <div class:list={["o-card__image-overlay", contentClass]}>{children}</div>} | ||
| 39 | </div> | ||
| 40 | ); | ||
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 @@ | |||
| 1 | import { Card, type Props as CardProps } from "./Card.tsx"; | ||
| 2 | import { CardBody } from "./CardBody.tsx"; | ||
| 3 | import { Heading } from "./Heading.tsx"; | ||
| 4 | |||
| 5 | export type Props = CardProps<"details"> & { | ||
| 6 | "heading-level": "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; | ||
| 7 | "title"?: JSX.Element; | ||
| 8 | "header"?: JSX.Element; | ||
| 9 | }; | ||
| 10 | |||
| 11 | export const CollapseCard = ({ | ||
| 12 | "heading-level": headingLevel, | ||
| 13 | children, | ||
| 14 | title, | ||
| 15 | header, | ||
| 16 | ...props | ||
| 17 | }: Props) => ( | ||
| 18 | <Card as="details" {...props}> | ||
| 19 | <CardBody as="summary"> | ||
| 20 | <Heading as={headingLevel} class="u-mbs-0" level="lg"> | ||
| 21 | {title} | ||
| 22 | </Heading> | ||
| 23 | {header} | ||
| 24 | </CardBody> | ||
| 25 | <CardBody>{children}</CardBody> | ||
| 26 | </Card> | ||
| 27 | ); | ||
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 @@ | |||
| 1 | import type { ClassValue } from "corvos/deps/clsx.ts"; | ||
| 2 | import type { Props as ImageProps } from "corvos/plugins/images.ts"; | ||
| 3 | |||
| 4 | import { Image } from "corvos/plugins/images.ts"; | ||
| 5 | |||
| 6 | export type Props = Omit<ImageProps, "src"> & { | ||
| 7 | "src"?: string; | ||
| 8 | "img-class"?: ClassValue; | ||
| 9 | }; | ||
| 10 | |||
| 11 | export const ContentImage = ({ src, "img-class": imgClass, ...props }: Props) => ( | ||
| 12 | <a href={src} data-lightbox {...props}> | ||
| 13 | <Image src={src} width="1400" class:list={imgClass} /> | ||
| 14 | </a> | ||
| 15 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"dl">; | ||
| 4 | |||
| 5 | export const Deflist = ({ class: cls, ...props }: Props) => ( | ||
| 6 | <dl class={`o-deflist ${cls ?? ""}`} {...props} /> | ||
| 7 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | import { Icon } from "$/ui/objects/Icon.tsx"; | ||
| 4 | |||
| 5 | import { Container } from "../layouts/Container.tsx"; | ||
| 6 | import { ActionButton } from "./ActionButton.tsx"; | ||
| 7 | import { Divider } from "./Divider.tsx"; | ||
| 8 | import { Heading } from "./Heading.tsx"; | ||
| 9 | |||
| 10 | export type Props = HTMLAttributes<"dialog"> & { | ||
| 11 | title: string; | ||
| 12 | divider?: boolean; | ||
| 13 | children?: JSX.Element; | ||
| 14 | }; | ||
| 15 | |||
| 16 | export const Dialog = ({ children, title, divider, class: cls, ...props }: Props) => ( | ||
| 17 | <dialog popover="auto" class:list={["o-dialog is-modal", cls]} closedby="any" {...props}> | ||
| 18 | <Container fixed class="o-dialog__content"> | ||
| 19 | <header class="o-dialog__header"> | ||
| 20 | <Heading as="h1" class="u-mbs-0" level="xxl"> | ||
| 21 | {title} | ||
| 22 | </Heading> | ||
| 23 | |||
| 24 | <ActionButton | ||
| 25 | command="close" | ||
| 26 | commandfor={props.id} | ||
| 27 | as="button" | ||
| 28 | pill | ||
| 29 | size="lg" | ||
| 30 | level="quiet" | ||
| 31 | icon={<Icon icon="x" variant="bold" />} | ||
| 32 | class="u-mis-auto" | ||
| 33 | /> | ||
| 34 | </header> | ||
| 35 | |||
| 36 | {divider && <Divider level="faint" class="o-dialog__divider" />} | ||
| 37 | |||
| 38 | <div class="o-dialog__body">{children}</div> | ||
| 39 | </Container> | ||
| 40 | </dialog> | ||
| 41 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes, HTMLTag } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"div" | "hr"> & { | ||
| 4 | "level"?: "strong" | "medium" | "quiet" | "faint"; | ||
| 5 | "variant"?: "static-black" | "static-white"; | ||
| 6 | "vertical"?: boolean | "dot"; | ||
| 7 | "label-as"?: HTMLTag; | ||
| 8 | "children"?: JSX.Element; | ||
| 9 | }; | ||
| 10 | |||
| 11 | export const Divider = ({ | ||
| 12 | children, | ||
| 13 | level = "strong", | ||
| 14 | variant, | ||
| 15 | vertical, | ||
| 16 | "label-as": Label = "span", | ||
| 17 | class: cls, | ||
| 18 | ...props | ||
| 19 | }: Props) => | ||
| 20 | children ? ( | ||
| 21 | <div | ||
| 22 | class:list={[ | ||
| 23 | "o-divider", | ||
| 24 | "o-divider--labelled", | ||
| 25 | `o-divider--${level}`, | ||
| 26 | { | ||
| 27 | [`o-divider--${variant}`]: variant, | ||
| 28 | "o-divider--vertical": vertical === true, | ||
| 29 | "o-divider--dot": vertical === "dot", | ||
| 30 | }, | ||
| 31 | cls, | ||
| 32 | ]} | ||
| 33 | {...props} | ||
| 34 | > | ||
| 35 | <Label class="o-divider__label u-mbs-0">{children}</Label> | ||
| 36 | </div> | ||
| 37 | ) : ( | ||
| 38 | <hr | ||
| 39 | class:list={[ | ||
| 40 | "o-divider", | ||
| 41 | `o-divider--${level}`, | ||
| 42 | { | ||
| 43 | [`o-divider--${variant}`]: variant, | ||
| 44 | "o-divider--vertical": vertical === true, | ||
| 45 | "o-divider--dot": vertical === "dot", | ||
| 46 | }, | ||
| 47 | cls, | ||
| 48 | ]} | ||
| 49 | {...props} | ||
| 50 | /> | ||
| 51 | ); | ||
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 @@ | |||
| 1 | import site from "@/corvos.config.ts"; | ||
| 2 | |||
| 3 | export interface Props { | ||
| 4 | pack: string; | ||
| 5 | emoji: string; | ||
| 6 | size?: "125" | "150" | "200"; | ||
| 7 | class?: string; | ||
| 8 | } | ||
| 9 | |||
| 10 | export const Emoji = site.$.component( | ||
| 11 | ({ pack, emoji, size, class: cls, ...props }: Props, pages) => { | ||
| 12 | const query = site.$.query(pages); | ||
| 13 | const sprite = query.type(".svg").match({ emojiPack: pack }).one(); | ||
| 14 | |||
| 15 | return ( | ||
| 16 | <svg class:list={["o-emoji o-emoji--sprite", { [`o-emoji--${size}`]: size }, cls]} {...props}> | ||
| 17 | <use href={`${sprite.url}#${emoji}`} /> | ||
| 18 | </svg> | ||
| 19 | ); | ||
| 20 | }, | ||
| 21 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"figure"> & { | ||
| 4 | children?: JSX.Element; | ||
| 5 | caption?: JSX.Element; | ||
| 6 | }; | ||
| 7 | |||
| 8 | export const Figure = ({ children, caption, class: cls, ...props }: Props) => ( | ||
| 9 | <figure class:list={["o-figure", cls]} {...props}> | ||
| 10 | {children} | ||
| 11 | {caption && <figcaption class="o-figure__caption">{caption}</figcaption>} | ||
| 12 | </figure> | ||
| 13 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"figcaption"> & { | ||
| 4 | children?: JSX.Element; | ||
| 5 | }; | ||
| 6 | |||
| 7 | export const FigureCaption = ({ children, class: cls, ...props }: Props) => ( | ||
| 8 | <figcaption class:list={["o-figure__caption", cls]} {...props}> | ||
| 9 | {children} | ||
| 10 | </figcaption> | ||
| 11 | ); | ||
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 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as: Tag; | ||
| 5 | level?: "xxl" | "xl" | "lg" | "md" | "sm" | "xs"; | ||
| 6 | variant?: "static-black" | "static-white"; | ||
| 7 | display?: boolean; | ||
| 8 | highlight?: boolean; | ||
| 9 | children?: JSX.Element; | ||
| 10 | }>; | ||
| 11 | |||
| 12 | const levelTagMap = { | ||
| 13 | lg: "h3", | ||
| 14 | md: "h4", | ||
| 15 | sm: "h5", | ||
| 16 | xl: "h2", | ||
| 17 | xs: "h6", | ||
| 18 | xxl: "h1", | ||
| 19 | } as const; | ||
| 20 | |||
| 21 | export const Heading = <T extends HTMLTag = "div">({ | ||
| 22 | as, | ||
| 23 | children, | ||
| 24 | display, | ||
| 25 | level, | ||
| 26 | variant, | ||
| 27 | highlight, | ||
| 28 | class: cls, | ||
| 29 | ...props | ||
| 30 | }: Props<T>) => { | ||
| 31 | const Tag = as ?? (level ? levelTagMap[level] : "strong"); | ||
| 32 | return ( | ||
| 33 | <Tag | ||
| 34 | class:list={[ | ||
| 35 | "o-heading", | ||
| 36 | { | ||
| 37 | [`o-heading--${level}`]: level, | ||
| 38 | [`o-heading--${variant}`]: variant, | ||
| 39 | "o-heading--display": display, | ||
| 40 | "u-c-heading": Tag === "strong", | ||
| 41 | }, | ||
| 42 | cls, | ||
| 43 | ]} | ||
| 44 | {...props} | ||
| 45 | > | ||
| 46 | {highlight ? <span class="o-heading__highlight">{children}</span> : children} | ||
| 47 | </Tag> | ||
| 48 | ); | ||
| 49 | }; | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | import site from "@/corvos.config.ts"; | ||
| 4 | |||
| 5 | export type Props = HTMLAttributes<"svg"> & { | ||
| 6 | icon: Parameters<typeof site.$.icon>[0]; | ||
| 7 | variant?: Parameters<typeof site.$.icon>[1]; | ||
| 8 | }; | ||
| 9 | |||
| 10 | export const Icon = ({ icon, variant, class: cls, ...props }: Props) => ( | ||
| 11 | <svg | ||
| 12 | aria-hidden="true" | ||
| 13 | class:list={["o-icon", cls]} | ||
| 14 | height="1.142857143em" | ||
| 15 | width="1.142857143em" | ||
| 16 | {...props} | ||
| 17 | > | ||
| 18 | <use href={site.$.icon(icon, variant)} /> | ||
| 19 | </svg> | ||
| 20 | ); | ||
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 @@ | |||
| 1 | /** Biome-ignore-all lint/a11y/useValidAnchor: <explanation> */ | ||
| 2 | /** Biome-ignore-all lint/a11y/useAltText: <explanation> */ | ||
| 3 | |||
| 4 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 5 | |||
| 6 | import { Icon } from "$/ui/objects/Icon.tsx"; | ||
| 7 | |||
| 8 | import { ActionButton } from "./ActionButton.tsx"; | ||
| 9 | import { LoadingIndicator } from "./LoadingIndicator.tsx"; | ||
| 10 | import { Thumbnail } from "./Thumbnail.tsx"; | ||
| 11 | |||
| 12 | export type Props = HTMLAttributes<"dialog">; | ||
| 13 | |||
| 14 | export const Lightbox = ({ class: cls, ...props }: Props) => ( | ||
| 15 | <dialog | ||
| 16 | class:list={["o-lightbox o-lightbox--static-black is-modal t-static-white", cls]} | ||
| 17 | closedby="any" | ||
| 18 | x-id="['lightbox']" | ||
| 19 | x-bind:id="$id('lightbox')" | ||
| 20 | x-data="lightbox" | ||
| 21 | x-on:close="handleClose" | ||
| 22 | x-on:touchstart="handleTouchStart" | ||
| 23 | x-on:touchend="handleTouchEnd" | ||
| 24 | {...{ | ||
| 25 | "x-on:keydown.document.capture": "handleKeyDown", | ||
| 26 | }} | ||
| 27 | {...props} | ||
| 28 | > | ||
| 29 | <div x-show="items.length > 1" class="o-lightbox__info"> | ||
| 30 | <small class="u-mis-100 u-mie-200 u-d-none@sm-lo"> | ||
| 31 | <strong x-text="current + 1"></strong> <span class="u-c-mute-more u-mi-50">⁄</span>{" "} | ||
| 32 | <strong x-text="items.length"></strong> | ||
| 33 | </small> | ||
| 34 | <ActionButton | ||
| 35 | x-on:click="prev" | ||
| 36 | as="button" | ||
| 37 | pill | ||
| 38 | level="quiet" | ||
| 39 | icon={<Icon icon="caret-left" variant="bold" />} | ||
| 40 | variant="static-white" | ||
| 41 | /> | ||
| 42 | <ActionButton | ||
| 43 | x-on:click="next" | ||
| 44 | as="button" | ||
| 45 | pill | ||
| 46 | level="quiet" | ||
| 47 | icon={<Icon icon="caret-right" variant="bold" />} | ||
| 48 | variant="static-white" | ||
| 49 | /> | ||
| 50 | </div> | ||
| 51 | |||
| 52 | <div class="o-lightbox__controls"> | ||
| 53 | <ActionButton | ||
| 54 | x-show="zoomedFull" | ||
| 55 | x-on:click="toggleZoom" | ||
| 56 | as="button" | ||
| 57 | class="u-mie-100" | ||
| 58 | pill | ||
| 59 | level="quiet" | ||
| 60 | icon={<Icon icon="magnifying-glass-minus" variant="bold" />} | ||
| 61 | variant="static-white" | ||
| 62 | /> | ||
| 63 | <ActionButton | ||
| 64 | x-show="!zoomedFull" | ||
| 65 | x-on:click="toggleZoom" | ||
| 66 | as="button" | ||
| 67 | class="u-mie-100" | ||
| 68 | pill | ||
| 69 | level="quiet" | ||
| 70 | icon={<Icon icon="magnifying-glass-plus" variant="bold" />} | ||
| 71 | variant="static-white" | ||
| 72 | /> | ||
| 73 | <ActionButton | ||
| 74 | x-bind:commandfor="$id('lightbox')" | ||
| 75 | command="close" | ||
| 76 | as="button" | ||
| 77 | pill | ||
| 78 | level="quiet" | ||
| 79 | icon={<Icon icon="x" variant="bold" />} | ||
| 80 | variant="static-white" | ||
| 81 | /> | ||
| 82 | </div> | ||
| 83 | |||
| 84 | <img | ||
| 85 | class="o-lightbox__img" | ||
| 86 | x-ref="img" | ||
| 87 | x-bind:src="currentItem?.src ?? ''" | ||
| 88 | x-bind:srcset="currentItem?.srcset ?? ''" | ||
| 89 | x-bind:alt="currentItem?.alt ?? ''" | ||
| 90 | x-on:load="loading = false" | ||
| 91 | x-on:mousemove="handleMouseMove" | ||
| 92 | x-on:touchmove="handleTouchMove" | ||
| 93 | x-on:dblclick="handleDoubleClick" | ||
| 94 | x-bind:class="{ 'is-hidden': loading }" | ||
| 95 | x-bind:style="{ transform: `translate(${offsetX}px, ${offsetY}px) scale(${zoom})` }" | ||
| 96 | {...{ | ||
| 97 | "x-on:mousedown.prevent": "handleMouseDown", | ||
| 98 | "x-on:mouseup.window": "handleMouseUp", | ||
| 99 | "x-on:wheel.prevent": "handleScroll", | ||
| 100 | }} | ||
| 101 | /> | ||
| 102 | |||
| 103 | <div class="o-lightbox__placeholder"> | ||
| 104 | <LoadingIndicator /> | ||
| 105 | </div> | ||
| 106 | |||
| 107 | <div class="o-lightbox__thumbnails" x-show="items.length > 1" x-ref="thumbnails"> | ||
| 108 | <template x-for="(item, i) in items"> | ||
| 109 | <Thumbnail | ||
| 110 | class="o-lightbox__thumbnail" | ||
| 111 | variant="static-white" | ||
| 112 | x-bind:src="item.thumbnail ?? item.src" | ||
| 113 | x-bind:srcset="item.srcset" | ||
| 114 | x-bind:alt="item.alt" | ||
| 115 | x-bind:href="item.src" | ||
| 116 | x-bind:class="{ 'is-selected': i === current }" | ||
| 117 | {...{ "x-on:click.prevent": "current = i" }} | ||
| 118 | /> | ||
| 119 | </template> | ||
| 120 | </div> | ||
| 121 | </dialog> | ||
| 122 | ); | ||
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 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"svg">; | ||
| 4 | |||
| 5 | export const LoadingIndicator = (props: Props) => ( | ||
| 6 | <svg height="50" width="50" {...props}> | ||
| 7 | <title>Loading indicator</title> | ||
| 8 | <use href="#loading-indicator" /> | ||
| 9 | </svg> | ||
| 10 | ); | ||
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 @@ | |||
| 1 | /** Biome-ignore-all lint/a11y/noNoninteractiveElementToInteractiveRole: <explanation> */ | ||
| 2 | |||
| 3 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 4 | |||
| 5 | export type Props = HTMLAttributes<"nav"> & { | ||
| 6 | "adapt"?: boolean; | ||
| 7 | "quiet"?: boolean; | ||
| 8 | "align-block"?: boolean; | ||
| 9 | "variant"?: "static-black" | "static-white"; | ||
| 10 | }; | ||
| 11 | |||
| 12 | export const Navbar = ({ | ||
| 13 | adapt, | ||
| 14 | quiet, | ||
| 15 | "align-block": alignBlock, | ||
| 16 | variant, | ||
| 17 | class: cls, | ||
| 18 | ...props | ||
| 19 | }: Props) => ( | ||
| 20 | <nav | ||
| 21 | class:list={[ | ||
| 22 | "o-navbar", | ||
| 23 | { | ||
| 24 | "o-navbar--adapt": adapt, | ||
| 25 | "o-navbar--quiet": quiet, | ||
| 26 | "o-navbar--align-block": alignBlock, | ||
| 27 | [`o-navbar--${variant}`]: variant, | ||
| 28 | }, | ||
| 29 | "js-keyboard-nav", | ||
| 30 | cls, | ||
| 31 | ]} | ||
| 32 | data-keyboard-nav-item=".o-navbar__item" | ||
| 33 | data-keyboard-nav-mode="nav" | ||
| 34 | role="menubar" | ||
| 35 | {...props} | ||
| 36 | /> | ||
| 37 | ); | ||
diff --git a/src/ui/objects/NavbarItem.tsx b/src/ui/objects/NavbarItem.tsx new file mode 100644 index 0000000..c0e347f --- /dev/null +++ b/src/ui/objects/NavbarItem.tsx | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as: Tag; | ||
| 5 | icon?: JSX.Element; | ||
| 6 | pre?: JSX.Element; | ||
| 7 | post?: JSX.Element; | ||
| 8 | selected?: boolean; | ||
| 9 | }>; | ||
| 10 | |||
| 11 | export const NavbarItem = <T extends HTMLTag = "a">({ | ||
| 12 | children, | ||
| 13 | as: Tag = "a" as T, | ||
| 14 | icon, | ||
| 15 | pre, | ||
| 16 | post, | ||
| 17 | selected, | ||
| 18 | class: cls, | ||
| 19 | ...props | ||
| 20 | }: Props<T>) => ( | ||
| 21 | <Tag | ||
| 22 | aria-current={selected ? "page" : undefined} | ||
| 23 | class:list={["o-navbar__item", { "is-selected": selected }, cls]} | ||
| 24 | role="menuitem" | ||
| 25 | {...props} | ||
| 26 | > | ||
| 27 | {icon && <div class="o-navbar__item-label">{children}</div>} | ||
| 28 | |||
| 29 | <div class="o-navbar__item-content"> | ||
| 30 | {pre} {icon ?? <span class="o-navbar__item-content-text">{children}</span>} {post} | ||
| 31 | </div> | ||
| 32 | </Tag> | ||
| 33 | ); | ||
diff --git a/src/ui/objects/PlaceholderBox.tsx b/src/ui/objects/PlaceholderBox.tsx new file mode 100644 index 0000000..d64866f --- /dev/null +++ b/src/ui/objects/PlaceholderBox.tsx | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as: Tag; | ||
| 5 | }>; | ||
| 6 | |||
| 7 | export const PlaceholderBox = <T extends HTMLTag = "div">({ | ||
| 8 | as: Tag = "div" as T, | ||
| 9 | class: cls, | ||
| 10 | ...props | ||
| 11 | }: Props<T>) => <Tag class:list={["o-placeholder-box u-ta-center u-c-mute", cls]} {...props} />; | ||
diff --git a/src/ui/objects/SideNav.tsx b/src/ui/objects/SideNav.tsx new file mode 100644 index 0000000..f96ce7a --- /dev/null +++ b/src/ui/objects/SideNav.tsx | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | /** Biome-ignore-all lint/a11y/noNoninteractiveElementToInteractiveRole: <explanation> */ | ||
| 2 | |||
| 3 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 4 | |||
| 5 | export type Props = HTMLAttributes<"nav">; | ||
| 6 | |||
| 7 | export const SideNav = ({ class: cls, ...props }: Props) => ( | ||
| 8 | <nav | ||
| 9 | class:list={["o-side-nav", "js-keyboard-nav", cls]} | ||
| 10 | data-keyboard-nav-item=".o-side-nav__item" | ||
| 11 | data-keyboard-nav-mode="list" | ||
| 12 | {...props} | ||
| 13 | /> | ||
| 14 | ); | ||
diff --git a/src/ui/objects/SideNavItem.tsx b/src/ui/objects/SideNavItem.tsx new file mode 100644 index 0000000..fb8f523 --- /dev/null +++ b/src/ui/objects/SideNavItem.tsx | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | import type { HTMLTag, Polymorphic } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props<Tag extends HTMLTag> = Polymorphic<{ | ||
| 4 | as: Tag; | ||
| 5 | icon?: JSX.Element; | ||
| 6 | pre?: JSX.Element; | ||
| 7 | post?: JSX.Element; | ||
| 8 | selected?: boolean; | ||
| 9 | }>; | ||
| 10 | |||
| 11 | export const SideNavItem = <T extends HTMLTag = "a">({ | ||
| 12 | children, | ||
| 13 | as: Tag = "a" as T, | ||
| 14 | icon, | ||
| 15 | pre, | ||
| 16 | post, | ||
| 17 | selected, | ||
| 18 | class: cls, | ||
| 19 | ...props | ||
| 20 | }: Props<T>) => ( | ||
| 21 | <Tag | ||
| 22 | aria-current={selected ? "location" : undefined} | ||
| 23 | class:list={["o-side-nav__item", { "is-selected": selected }, cls]} | ||
| 24 | role="menuitem" | ||
| 25 | {...props} | ||
| 26 | > | ||
| 27 | {icon && <div class="l-media__block o-side-nav__icon-slot">{icon}</div>} | ||
| 28 | |||
| 29 | <div class="l-media__block l-media__block--main"> | ||
| 30 | {pre} {children} {post} | ||
| 31 | </div> | ||
| 32 | </Tag> | ||
| 33 | ); | ||
diff --git a/src/ui/objects/StatusIndicator.tsx b/src/ui/objects/StatusIndicator.tsx new file mode 100644 index 0000000..06b6481 --- /dev/null +++ b/src/ui/objects/StatusIndicator.tsx | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"div"> & { | ||
| 4 | status?: "accent" | "positive" | "negative" | "warning"; | ||
| 5 | }; | ||
| 6 | |||
| 7 | export const StatusIndicator = ({ status, class: cls, ...props }: Props) => ( | ||
| 8 | <div | ||
| 9 | class:list={[ | ||
| 10 | "o-status-indicator", | ||
| 11 | { | ||
| 12 | [`is-${status}`]: status, | ||
| 13 | }, | ||
| 14 | cls, | ||
| 15 | ]} | ||
| 16 | {...props} | ||
| 17 | /> | ||
| 18 | ); | ||
diff --git a/src/ui/objects/Tab.tsx b/src/ui/objects/Tab.tsx new file mode 100644 index 0000000..15c1e5d --- /dev/null +++ b/src/ui/objects/Tab.tsx | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"a"> & { | ||
| 4 | selected?: boolean; | ||
| 5 | }; | ||
| 6 | |||
| 7 | export const Tab = ({ selected, class: cls, ...props }: Props) => ( | ||
| 8 | <a | ||
| 9 | aria-selected={`${selected}`} | ||
| 10 | class:list={["o-tabbar__tab", { "is-selected": selected }, cls]} | ||
| 11 | role="tab" | ||
| 12 | {...props} | ||
| 13 | /> | ||
| 14 | ); | ||
diff --git a/src/ui/objects/Tabbar.tsx b/src/ui/objects/Tabbar.tsx new file mode 100644 index 0000000..58c58fa --- /dev/null +++ b/src/ui/objects/Tabbar.tsx | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"nav"> & { | ||
| 4 | "variant"?: "accent"; | ||
| 5 | "quiet"?: boolean; | ||
| 6 | "adapt"?: boolean; | ||
| 7 | "tabs-class"?: unknown; | ||
| 8 | }; | ||
| 9 | |||
| 10 | export const Tabbar = ({ | ||
| 11 | children, | ||
| 12 | variant, | ||
| 13 | quiet, | ||
| 14 | adapt, | ||
| 15 | "tabs-class": tabsClass, | ||
| 16 | class: cls, | ||
| 17 | ...props | ||
| 18 | }: Props) => ( | ||
| 19 | <nav | ||
| 20 | class:list={[ | ||
| 21 | "o-tabbar", | ||
| 22 | { | ||
| 23 | [`o-tabbar--${variant}`]: variant, | ||
| 24 | "o-tabbar--quiet": quiet, | ||
| 25 | "o-tabbar--adapt": adapt, | ||
| 26 | }, | ||
| 27 | "js-keyboard-nav", | ||
| 28 | cls, | ||
| 29 | ]} | ||
| 30 | data-keyboard-nav-item=".o-tabbar__tab" | ||
| 31 | data-keyboard-nav-mode="nav" | ||
| 32 | {...props} | ||
| 33 | > | ||
| 34 | <div | ||
| 35 | aria-label={props["aria-label"]} | ||
| 36 | aria-orientation="horizontal" | ||
| 37 | class:list={["o-tabbar__tabs", tabsClass]} | ||
| 38 | role="tablist" | ||
| 39 | > | ||
| 40 | {children} | ||
| 41 | </div> | ||
| 42 | </nav> | ||
| 43 | ); | ||
diff --git a/src/ui/objects/TextField.tsx b/src/ui/objects/TextField.tsx new file mode 100644 index 0000000..dc7893a --- /dev/null +++ b/src/ui/objects/TextField.tsx | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 2 | |||
| 3 | export type Props = HTMLAttributes<"input"> & { | ||
| 4 | "pre"?: JSX.Element; | ||
| 5 | "post"?: JSX.Element; | ||
| 6 | "invalid"?: boolean; | ||
| 7 | "pill"?: boolean; | ||
| 8 | "x-model"?: string; | ||
| 9 | }; | ||
| 10 | |||
| 11 | export const TextField = ({ | ||
| 12 | pre, | ||
| 13 | post, | ||
| 14 | disabled, | ||
| 15 | invalid, | ||
| 16 | pill, | ||
| 17 | placeholder, | ||
| 18 | readonly, | ||
| 19 | value, | ||
| 20 | pattern, | ||
| 21 | required, | ||
| 22 | "x-model": xModel, | ||
| 23 | class: cls, | ||
| 24 | ...props | ||
| 25 | }: Props) => { | ||
| 26 | const extended = !!pre || !!post; | ||
| 27 | |||
| 28 | return ( | ||
| 29 | <div | ||
| 30 | class:list={[ | ||
| 31 | "o-text-field", | ||
| 32 | { | ||
| 33 | "is-disabled": disabled, | ||
| 34 | "is-invalid": invalid, | ||
| 35 | "l-media": extended, | ||
| 36 | "l-media--gapless": extended, | ||
| 37 | "o-text-field--extended": extended, | ||
| 38 | "o-text-field--pill": pill, | ||
| 39 | }, | ||
| 40 | cls, | ||
| 41 | ]} | ||
| 42 | {...props} | ||
| 43 | > | ||
| 44 | {pre}{" "} | ||
| 45 | <input | ||
| 46 | class="o-text-field__native" | ||
| 47 | class:list={{ | ||
| 48 | "l-media__block": extended, | ||
| 49 | "l-media__block--main": extended, | ||
| 50 | }} | ||
| 51 | disabled={disabled} | ||
| 52 | pattern={pattern} | ||
| 53 | placeholder={placeholder} | ||
| 54 | readonly={readonly} | ||
| 55 | required={required} | ||
| 56 | value={value} | ||
| 57 | x-model={xModel} | ||
| 58 | />{" "} | ||
| 59 | {post} <div class="o-text-field__bg"></div> | ||
| 60 | </div> | ||
| 61 | ); | ||
| 62 | }; | ||
diff --git a/src/ui/objects/Thumbnail.tsx b/src/ui/objects/Thumbnail.tsx new file mode 100644 index 0000000..4ef76d3 --- /dev/null +++ b/src/ui/objects/Thumbnail.tsx | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | import type { Props as ImageProps } from "corvos/plugins/images.ts"; | ||
| 2 | import type { HTMLAttributes } from "corvos/plugins/jsx.ts"; | ||
| 3 | |||
| 4 | import { Image } from "corvos/plugins/images.ts"; | ||
| 5 | |||
| 6 | export type Props = HTMLAttributes<"a"> & | ||
| 7 | ImageProps & { | ||
| 8 | "x-bind:src"?: string; | ||
| 9 | "x-bind:srcset"?: string; | ||
| 10 | "x-bind:alt"?: string; | ||
| 11 | "variant"?: "static-black" | "static-white"; | ||
| 12 | }; | ||
| 13 | |||
| 14 | export const Thumbnail = ({ | ||
| 15 | src, | ||
| 16 | srcset, | ||
| 17 | alt, | ||
| 18 | "x-bind:src": xbindSrc, | ||
| 19 | "x-bind:srcset": xbindSrcset, | ||
| 20 | "x-bind:alt": xbindAlt, | ||
| 21 | variant, | ||
| 22 | class: cls, | ||
| 23 | widths, | ||
| 24 | width, | ||
| 25 | height, | ||
| 26 | densities, | ||
| 27 | "no-transform": noTransform, | ||
| 28 | ...props | ||
| 29 | }: Props) => ( | ||
| 30 | <a class:list={["o-thumbnail", { [`o-thumbnail--${variant}`]: variant }, cls]} {...props}> | ||
| 31 | <Image | ||
| 32 | class="o-thumbnail__image" | ||
| 33 | src={src} | ||
| 34 | srcset={srcset} | ||
| 35 | widths={widths as undefined} | ||
| 36 | width={width} | ||
| 37 | height={height} | ||
| 38 | densities={densities} | ||
| 39 | no-transform={noTransform} | ||
| 40 | alt={alt} | ||
| 41 | x-bind:src={xbindSrc} | ||
| 42 | x-bind:srcset={xbindSrcset} | ||
| 43 | x-bind:alt={xbindAlt} | ||
| 44 | /> | ||
| 45 | </a> | ||
| 46 | ); | ||
