initial commit

This commit is contained in:
thislight 2024-07-14 20:28:44 +08:00
commit 5449e361d5
46 changed files with 8309 additions and 0 deletions

21
src/material/Button.tsx Normal file
View file

@ -0,0 +1,21 @@
import { Component, JSX, splitProps } from "solid-js";
import materialStyles from "./material.module.css";
/**
* Material-styled button.
*
* @param type Same as `<button>`'s type property, the default is 'button'
*/
const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = (
props,
) => {
const [managed, passthough] = splitProps(props, ["class", 'type']);
const classes = () =>
managed.class
? [materialStyles.button, managed.class].join(" ")
: materialStyles.button;
const type = () => managed.type ?? 'button'
return <button type={type()} class={classes()} {...passthough}></button>;
};
export default Button;

121
src/material/Img.tsx Normal file
View file

@ -0,0 +1,121 @@
import {
JSX,
splitProps,
Component,
createSignal,
createEffect,
onMount,
createRenderEffect,
Show,
} from "solid-js";
import { css } from "solid-styled";
import { decode } from "blurhash";
import { mergeClass } from "../utils";
type ImgProps = {
blurhash?: string;
keepBlur?: boolean;
} & JSX.HTMLElementTags["img"];
const Img: Component<ImgProps> = (props) => {
let canvas: HTMLCanvasElement;
let imgE: HTMLImageElement;
const [managed, passthough] = splitProps(props, [
"blurhash",
"keepBlur",
"class",
"style",
]);
const [isImgLoaded, setIsImgLoaded] = createSignal(false);
const [imgSize, setImgSize] = createSignal<{
width: number;
height: number;
}>();
const isBlurEnabled = () => managed.keepBlur || !isImgLoaded();
css`
:where(.img-root) {
display: inline-block;
position: relative;
> img:first-of-type {
object-fit: contain;
object-position: center;
width: 100%;
height: 100%;
visibility: ${isBlurEnabled() ? "hidden" : "visible"};
}
}
:where(.cover) {
display: ${isBlurEnabled() ? "block" : "none"};
position: absolute;
left: 0;
top: 0;
height: ${`${imgSize()?.height ?? 0}px`};
width: ${`${imgSize()?.width ?? 0}px`};
}
`;
const onImgLoaded = () => {
setIsImgLoaded(true);
setImgSize({
width: imgE.width,
height: imgE.height,
});
};
const onMetadataLoaded = () => {
setImgSize({
width: imgE.width,
height: imgE.height,
});
};
onMount(() => {
setImgSize((x) => {
const parent = imgE.parentElement;
if (!parent) return x;
return x
? x
: {
width: parent.clientWidth,
height: parent.clientHeight,
};
});
});
return (
<div class={mergeClass(managed.class, "img-root")} style={managed.style}>
<Show when={managed.blurhash}>
<canvas
ref={(canvas) => {
createRenderEffect(() => {
if (!managed.blurhash) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const size = imgSize();
if (!size) return;
const imgd = ctx?.createImageData(size.width, size.height);
const pixels = decode(managed.blurhash, size.width, size.height);
imgd.data.set(pixels);
ctx.putImageData(imgd, 0, 0);
});
}}
class="cover"
role="presentation"
/>
</Show>
<img
ref={imgE!}
{...passthough}
onLoad={onImgLoaded}
onLoadedMetadata={onMetadataLoaded}
/>
</div>
);
};
export default Img;

58
src/material/Scaffold.tsx Normal file
View file

@ -0,0 +1,58 @@
import { createElementSize } from "@solid-primitives/resize-observer";
import {
Show,
createRenderEffect,
createSignal,
onCleanup,
type JSX,
type ParentComponent,
} from "solid-js";
import { css } from "solid-styled";
interface ScaffoldProps {
topbar?: JSX.Element;
fab?: JSX.Element;
}
const Scaffold: ParentComponent<ScaffoldProps> = (props) => {
const [topbarElement, setTopbarElement] = createSignal<HTMLElement>();
const topbarSize = createElementSize(topbarElement);
css`
.scaffold-content {
--scaffold-topbar-height: ${(topbarSize.height?.toString() ?? 0) + "px"};
height: 100%;
width: 100%;
}
.topbar {
position: sticky;
top: 0px;
z-index: var(--tutu-zidx-nav, auto);
}
.fab-dock {
position: fixed;
bottom: 40px;
right: 40px;
z-index: var(--tutu-zidx-nav, auto);
}
`;
return (
<>
<Show when={props.topbar}>
<div class="topbar" ref={setTopbarElement}>
{props.topbar}
</div>
</Show>
<Show when={props.fab}>
<div class="fab-dock">{props.fab}</div>
</Show>
<div class="scaffold-content">{props.children}</div>
</>
);
};
export default Scaffold;

80
src/material/Tab.tsx Normal file
View file

@ -0,0 +1,80 @@
import {
Component,
createEffect,
splitProps,
type JSX,
type ParentComponent,
} from "solid-js";
import { css } from "solid-styled";
import { useTabListContext } from "./Tabs";
const Tab: ParentComponent<
{
focus?: boolean;
large?: boolean;
} & JSX.ButtonHTMLAttributes<HTMLButtonElement>
> = (props) => {
const [managed, rest] = splitProps(props, [
"focus",
"large",
"type",
"role",
"ref",
]);
let self: HTMLButtonElement;
const {
focusOn: [, setFocusOn],
} = useTabListContext();
createEffect<boolean | undefined>((lastStatus) => {
if (managed.focus && !lastStatus) {
setFocusOn((x) => [...x, self]);
}
if (!managed.focus && lastStatus) {
setFocusOn((x) => x.filter((e) => e !== self));
}
return managed.focus;
});
css`
.tab {
cursor: pointer;
background: none;
border: none;
min-width: ${managed.large ? "160px" : "72px"};
height: 48px;
max-width: min(calc(100% - 56px), 264px);
padding: 10px 24px;
font-size: 0.8135rem;
font-weight: 600;
text-transform: uppercase;
transition: color 120ms var(--tutu-anim-curve-std);
}
:global(.MuiToolbar-root) .tab {
color: rgba(255, 255, 255, 0.7);
&:hover,
&:focus,
&.focus,
&:global(.tablist-focus) {
color: white;
}
}
`;
return (
<button
ref={(x) => {
self = x;
(managed.ref as (e: HTMLButtonElement) => void)?.(x);
}}
type={managed.type ?? "button"}
classList={{ tab: true, focus: managed.focus }}
role={managed.role ?? "tab"}
{...rest}
>
{props.children}
</button>
);
};
export default Tab;

165
src/material/Tabs.tsx Normal file
View file

@ -0,0 +1,165 @@
import {
ParentComponent,
createContext,
createEffect,
createMemo,
createRenderEffect,
createSignal,
useContext,
type Signal,
} from "solid-js";
import { css } from "solid-styled";
const TabListContext = /* @__PURE__ */ createContext<{
focusOn: Signal<HTMLElement[]>;
}>();
export function useTabListContext() {
const result = useContext(TabListContext);
if (!result) {
throw new TypeError("tab list context is not found");
}
return result;
}
const ANIM_SPEED = 160 / 110; // 160px/110ms
const TABLIST_FOCUS_CLASS = "tablist-focus";
const Tabs: ParentComponent<{
offset?: number;
onFocusChanged?: (element: HTMLElement[]) => void;
}> = (props) => {
let self: HTMLDivElement;
const [focusOn, setFocusOn] = createSignal<HTMLElement[]>([]);
createRenderEffect<HTMLElement[] | undefined>((lastFocusElement) => {
const current = focusOn();
if (lastFocusElement) {
for (const e of lastFocusElement) {
e.classList.remove(TABLIST_FOCUS_CLASS);
}
}
for (const e of current) {
e.classList.add("tablist-focus");
}
return current;
});
createRenderEffect(() => {
const callback = props.onFocusChanged;
if (!callback) return;
callback(focusOn());
});
let lastLeft = 0;
let lastWidth = 0;
const getNearestDistance = (
srcRect: { x: number; width: number },
prevEl: Element | null,
nextEl: Element | null,
offset?: number,
) => {
if (!offset || offset === 0) return [0, 0] as const;
if (offset > 0) {
if (!nextEl) return [0, 0] as const;
const rect = nextEl.getBoundingClientRect();
return [
(rect.x - srcRect.x) * offset,
(rect.width - srcRect.width) * offset,
] as const;
} else {
if (!prevEl) return [0, 0] as const;
const rect = prevEl.getBoundingClientRect();
return [
(rect.x - srcRect.x) * offset,
(srcRect.width - rect.width) * offset,
] as const;
}
};
const focusBoundingClientRect = () => {
return focusOn()
.map((x) => x.getBoundingClientRect())
.reduce(
(p, c) => {
return {
x: Math.min(p.x, c.x),
width: p.width + c.width,
};
},
{ x: +Infinity, width: 0 },
);
};
const focusSiblings = () => {
const rects = focusOn().map((x) => [x, x.getBoundingClientRect()] as const);
if (rects.length === 0) return [null, null] as const;
rects.sort(([, rect1], [, rect2]) => rect1.x - rect2.x);
return [
rects[0][0].previousElementSibling,
rects[rects.length - 1][0].nextElementSibling,
] as const;
};
const indicator = () => {
const el = focusOn();
if (!el) {
return ["0px", "0px", "110ms", "110ms"] as const;
}
const rect = focusBoundingClientRect();
const rootRect = self.getBoundingClientRect();
const left = rect.x - rootRect.x;
const width = rect.width;
const [prevEl, nextEl] = focusSiblings();
const [offset, widthChange] = getNearestDistance(
rect,
prevEl,
nextEl,
props.offset,
);
const result = [
`${left + offset}px`,
`${width + widthChange}px`,
`${Math.max(Math.floor(Math.abs(left + offset - lastLeft)), 160) * ANIM_SPEED}ms`,
`${Math.max(Math.floor(Math.abs(width - lastWidth)), 160) * ANIM_SPEED}ms`,
] as const;
lastLeft = left;
lastWidth = width;
return result;
};
css`
.tablist {
width: 100%;
position: relative;
white-space: nowrap;
overflow-x: auto;
&::after {
transition:
left ${indicator()[2]} var(--tutu-anim-curve-std),
width ${indicator()[3]} var(--tutu-anim-curve-std);
position: absolute;
content: "";
display: block;
background-color: white;
height: 2px;
width: ${indicator()[1]};
left: ${indicator()[0]};
bottom: 0;
}
}
`;
return (
<TabListContext.Provider value={{ focusOn: [focusOn, setFocusOn] }}>
<div ref={self!} class="tablist" role="tablist">
{props.children}
</div>
</TabListContext.Provider>
);
};
export default Tabs;

View file

@ -0,0 +1,80 @@
import {
Component,
createEffect,
createSignal,
createUniqueId,
onMount,
Show,
} from "solid-js";
import formStyles from "./form.module.css";
export type TextFieldProps = {
label?: string;
helperText?: string;
type?: "text" | "password";
onChange?: (value: string) => void;
onInput?: (value: string) => void;
inputId?: string;
error?: boolean;
required?: boolean;
name?: string;
};
const TextField: Component<TextFieldProps> = (props) => {
let input: HTMLInputElement;
let field: HTMLDivElement;
const [hasContent, setHasContent] = createSignal(false);
const altInputId = createUniqueId();
createEffect(() => {
if (hasContent()) {
field.classList.add("float-label");
} else {
field.classList.remove("float-label");
}
});
onMount(() => {
setHasContent(input.value.length > 0);
});
const onInputChange = (e: { currentTarget: HTMLInputElement }) => {
const value = (e.currentTarget as HTMLInputElement).value;
setHasContent(value.length > 0);
props.onInput?.(value);
};
const inputId = () => props.inputId ?? altInputId;
const fieldClass = () => {
const cls = [formStyles.textfield];
if (typeof props.helperText !== "undefined") {
cls.push(formStyles.withHelperText);
}
if (props.error) {
cls.push(formStyles.error);
}
return cls.join(" ");
};
return (
<div ref={field!} class={fieldClass()}>
<label for={inputId()}>{props.label}</label>
<input
ref={input!}
id={inputId()}
type={props.type ?? "text"}
onInput={onInputChange}
onChange={(e) => props.onChange?.(e.currentTarget.value)}
placeholder=""
required={props.required}
name={props.name}
/>
<Show when={typeof props.helperText !== "undefined"}>
<span class={formStyles.helperText}>{props.helperText}</span>
</Show>
</div>
);
};
export default TextField;

View file

@ -0,0 +1,55 @@
.card {
composes: surface from 'material.module.css';
border-radius: 2px;
box-shadow: var(--tutu-shadow-e2);
transition: var(--tutu-transition-shadow);
overflow: hidden;
background-color: var(--tutu-color-surface-l);
&:focus-within,
&:focus-visible {
box-shadow: var(--tutu-shadow-e8);
}
&:not(.manualMargin) {
&>:not(.cardNoPad) {
margin-inline: var(--card-pad, 20px);
}
> :not(.cardGutSkip):first-child {
margin-top: var(--card-gut, 20px);
}
>.cardGutSkip+*:not(.cardGutSkip) {
margin-top: var(--card-gut, 20px);
}
> :not(.cardGutSkip):last-child {
margin-bottom: var(--card-gut, 20px);
}
}
}
.layoutCentered {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 448px;
@media (max-width: 600px) {
& {
position: static;
height: 100%;
width: 100%;
left: 0;
right: 0;
transform: none;
display: grid;
grid-template-rows: 1fr auto;
height: 100vh;
overflow: auto;
}
}
}

View file

@ -0,0 +1,80 @@
.textfield {
composes: touchTarget from 'material.module.css';
--border-color: var(--tutu-color-inactive-on-surface);
--active-border-color: var(--tutu-color-primary);
--label-color: var(--tutu-color-inactive-on-surface);
--active-label-color: var(--tutu-color-primary);
--helper-text-color: var(--tutu-color-inactive-on-surface);
&>* {
width: 100%;
}
&.error, &:has(>input[aria-invalid="true"]) {
&:not(:focus-within) {
--border-color: var(--tutu-color-error-on-surface);
--label-color: var(--tutu-color-error-on-surface);
--helper-text-color: var(--tutu-color-error-on-surface);
}
&:focus-within {
--helper-text-color: var(--tutu-color-error-on-surface);
}
}
position: relative;
&>label {
position: absolute;
left: 0;
bottom: calc(10px + var(--bottom-height, 0px));
color: var(--label-color);
transition: bottom .2s ease-in-out, font-size .2s ease-in-out, color .2s ease-in-out;
cursor: text;
font-size: 0.8125rem;
}
&>label:has(+ input:not(:placeholder-shown)) {
bottom: calc(100% - 0.8125rem);
}
&:focus-within>label, &.float-label>label {
bottom: calc(100% - 0.8125rem);
color: var(--active-label-color);
}
&>input[type='text'],
&>input[type='password'] {
border: none;
outline: none;
border-bottom: 1px solid var(--border-color);
background-color: transparent;
padding-top: 16px;
padding-bottom: 8px;
margin-bottom: 1px;
transition: border-color .2s ease-in-out;
&:focus {
border-bottom: 2px solid var(--active-border-color);
margin-bottom: 0;
}
}
&.withHelperText {
--bottom-height: 0.8125rem;
}
& .helperText {
color: var(--helper-text-color);
font-size: 0.8125rem;
line-height: 100%;
-webkit-line-clamp: 1;
line-clamp: 1;
display: flex;
justify-content: space-between;
min-height: 0.8125rem;
cursor: auto;
}
}

View file

@ -0,0 +1,65 @@
.surface {
background-color: var(--tutu-color-surface);
color: var(--tutu-color-on-surface);
}
.touchTarget {
min-width: 44px;
min-height: 44px;
cursor: pointer;
}
.button {
composes: buttonText from './typography.module.css';
composes: touchTarget;
border: none;
background-color: transparent;
color: var(--tutu-color-primary);
font-family: inherit;
&:focus,&:hover,&:focus-visible {
background-color: var(--tutu-color-surface-dd);
}
&.pressed {
background-color: var(--tutu-color-surface-d);
}
&.raised {
background-color: var(--tutu-color-primary);
color: var(--tutu-color-on-primary);
}
&:disabled, &[aria-disabled]:not([aria-disabled="false"]) {
color: #9e9e9e;
&:focus,&:hover,&:focus-visible {
background-color: transparent;
}
}
.toolbar &, .appbar & {
height: 100%;
margin-block: 0;
padding-block: 0;
border-radius: 0;
}
.appbar & {
color: var(--tutu-color-on-primary);
&:focus,&:hover,&:focus-visible {
background-color: var(--tutu-color-primary-ll);
}
&.pressed {
background-color: var(--tutu-color-primary-l);
}
}
.toolbar & {
color: var(--tutu-color-on-surface);
}
}

16
src/material/mui.ts Normal file
View file

@ -0,0 +1,16 @@
import { Theme, createTheme } from "@suid/material/styles";
import { deepPurple, amber } from "@suid/material/colors";
import { Accessor } from "solid-js";
export function useRootTheme() : Accessor<Theme> {
return () => createTheme({
palette: {
primary: {
main: deepPurple[500]
},
secondary: {
main: amber.A200
}
}
})
}

135
src/material/theme.css Normal file
View file

@ -0,0 +1,135 @@
:root,
[lang^="en"], [lang="en"] {
--md-typography-type: "regular";
--title-size: 1.25rem;
--title-weight: 500;
--subheading-size: 1.125rem;
--body-size: 1rem;
--body2-weight: 500;
--caption-size: 0.875rem;
--button-size: 1rem;
--button-weight: 500;
--button-text-transform: uppercase;
@media (min-width: 1024px) {
& {
--subheading-size: 1.0625rem;
--body-size: 0.9375rem;
}
}
}
[lang^="zh"], [lang="zh"],
[lang^="kr"], [lang="kr"],
[lang^="ja"], [lang="ja"] {
--md-typography-type: "dense";
--title-size: 1.4375rem;
--subheading-size: 1.1875rem;
--body-size: 1.0625rem;
--caption-size: 0.9375rem;
--button-size: 1.0625rem;
--button-text-transform: none;
@media (min-width: 1024px) {
& {
--subheading-size: 1.125rem;
--body-size: 1rem;
}
}
}
:root {
--tutu-color-primary: #673ab7;
/* Deep Purple 500 */
--tutu-color-on-primary: white;
--tutu-color-primary-d: #512da8;
/* 700 */
--tutu-color-on-primary-d: white;
--tutu-color-primary-dd: #4527a0;
/* 800 */
--tutu-color-on-primary-dd: white;
--tutu-color-primary-l: #9575cd;
/* 200 */
--tutu-color-on-primary-l: white;
--tutu-color-primary-ll: #b39ddb;
/* 100 */
--tutu-color-on-primary-ll: black;
--tutu-color-secondary: #ffd740;
/* Amber A200 */
--tutu-color-on-secondary: black;
--tutu-color-surface-l: white;
--tutu-color-surface: #fafafa;
--tutu-color-surface-d: #99999928;
--tutu-color-surface-dd: #99999920;
--tutu-color-on-surface: black;
--tutu-color-secondary-text-on-surface: rgba(0, 0, 0, 0.5);
--tutu-color-error-on-surface: #d32f2f;
--tutu-color-inactive-on-surface: #757575;
--tutu-shadow-e1: 0px 1px 2px 0px #9e9e9e;
/* Switch */
--tutu-shadow-e2: 0px 2px 4px 0px #9e9e9e;
/* (Resting) cards, raised button, quick entry / search bar */
--tutu-shadow-e3: 0px 3px 6px 0px #9e9e9e;
/* Refresh indicator, quick entry / search bar (scrolled) */
--tutu-shadow-e4: 0px 4px 8px 0px #9e9e9e;
/* App bar */
--tutu-shadow-e6: 0px 6px 12px 0px #9e9e9e;
/* Snack bar, FAB (resting) */
--tutu-shadow-e8: 0px 8px 16px 0px #9e9e9e;
/* Menu, (picked-up) cards, (pressed) raise button */
--tutu-shadow-e9: 0px 9px 18px 0px #9e9e9e;
/* Submenu (+1dp for each submenu) */
--tutu-shadow-e12: 0px 12px 24px 0px #9e9e9e;
/* (pressed) FAB */
--tutu-shadow-e16: 0px 16px 32px 0px #9e9e9e;
/* Nav drawer, right drawer, modal bottom sheet */
--tutu-shadow-e24: 0px 24px 48px 0px #9e9e9e;
/* Dialog, picker */
--tutu-anim-curve-std: cubic-bezier(0.4, 0, 0.2, 1);
--tutu-anim-curve-deceleration: cubic-bezier(0, 0, 0.2, 1);
--tutu-anim-curve-aceleration: cubic-bezier(0.4, 0, 1, 1);
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
@media (max-width: 300px) {
/* XS screen, like wearables */
& {
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
}
}
@media (max-width: 600px) {
/* Mobile */
& {
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
}
}
@media (max-width: 1200px) {
/* Tablet */
& {
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
}
}
/* Desktop */
--tutu-transition-shadow: box-shadow 175ms var(--tutu-anim-curve-std);
--tutu-zidx-nav: 1100;
}
* {
font-family: Roboto, "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
box-sizing: border-box;
margin: 0;
}
body {
font-size: var(--body-size, 1rem);
}

View file

View file

@ -0,0 +1,48 @@
.display4 {
font-size: 7rem;
font-weight: 300;
}
.display3 {
font-size: 3.5rem;
}
.display2 {
font-size: 2.8125rem;
}
.display1 {
font-size: 2.125rem;
}
.headline {
font-size: 1.5rem;
}
.title {
font-size: var(--title-size);
font-weight: var(--title-weight);
}
.subheading {
font-size: var(--subheading-size);
}
.body1 {
font-size: var(--body-size);
}
.body2 {
composes: body1;
font-weight: var(--body2-weight);
}
.caption {
font-size: var(--caption-size);
}
.buttonText {
font-weight: var(--button-weight);
font-size: var(--button-size);
text-transform: var(--button-text-transform);
}

View file

@ -0,0 +1,87 @@
import { JSX, ParentComponent, splitProps, type Ref } from "solid-js";
import { Dynamic } from "solid-js/web";
import typography from "./typography.module.css";
import { mergeClass } from "../utils";
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>
type PropsOf<E extends AnyElement> =
E extends ParentComponent<infer Props>
? Props
: E extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[E]
: JSX.HTMLAttributes<HTMLElement>;
export type TypographyProps<
E extends AnyElement,
> = {
ref?: Ref<E>;
component?: E;
class?: string;
} & PropsOf<E>;
type TypographyKind =
| "display4"
| "display3"
| "display2"
| "display1"
| "headline"
| "title"
| "subheading"
| "body1"
| "body2"
| "caption"
| "buttonText";
export function Typography<T extends AnyElement>(props: {typography: TypographyKind } & TypographyProps<T>) {
const [managed, passthough] = splitProps(props, [
"ref",
"component",
"class",
"typography",
]);
const classes = () =>
mergeClass(managed.class, typography[managed.typography]);
return (
<Dynamic
ref={managed.ref}
component={managed.component ?? "span"}
class={classes()}
{...passthough}
></Dynamic>
);
};
export function Display4<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display4"} {...props}></Typography>
}
export function Display3<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display3"} {...props}></Typography>
}
export function Display2<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display2"} {...props}></Typography>
}
export function Display1<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display1"} {...props}></Typography>
}
export function Headline<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"headline"} {...props}></Typography>
}
export function Title<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"title"} {...props}></Typography>
}
export function Subheading<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"subheading"} {...props}></Typography>
}
export function Body1<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"body1"} {...props}></Typography>
}
export function Body2<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"body2"} {...props}></Typography>
}
export function Caption<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"caption"} {...props}></Typography>
}
export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"buttonText"} {...props}></Typography>
}