update
This commit is contained in:
parent
ddb6f41eec
commit
b8107c8f04
|
|
@ -173,3 +173,4 @@ dist
|
|||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
target
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
preload = ["./src/myPlugin.ts"]
|
||||
10
package.json
10
package.json
|
|
@ -4,17 +4,21 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "bun run src/index.tsx",
|
||||
"dev": "bun run --hot src/index.tsx"
|
||||
"dev": "bun run --watch --hot src/index.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"csstype": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.29.1",
|
||||
"serve-static-bun": "^0.5.3"
|
||||
"pokedex-promise-v2": "^4.2.1",
|
||||
"serve-static-bun": "^0.5.3",
|
||||
"tree-sitter-cli": "^0.25.2",
|
||||
"tree-sitter-typescript": "^0.23.2",
|
||||
"web-tree-sitter": "^0.25.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { createState } from "~/lib/state";
|
||||
|
||||
export default createState(
|
||||
{ value: 0 },
|
||||
{
|
||||
add: (ctx) => ({
|
||||
onClick: () => {
|
||||
const { value } = ctx.get();
|
||||
ctx.set({ value: value + 1 });
|
||||
ctx.$counter.innerText = String(value + 1);
|
||||
},
|
||||
}),
|
||||
counter: (_ctx) => ({}),
|
||||
sub: (ctx) => ({
|
||||
onClick: () => {
|
||||
const { value } = ctx.get();
|
||||
ctx.set({ value: value - 1 });
|
||||
ctx.$counter.innerText = String(value - 1);
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { useState } from "~/lib/hooks";
|
||||
import counterState from "./Counter.state";
|
||||
|
||||
export const Counter = () => {
|
||||
const { sub, counter, add } = useState(counterState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button {...sub}>-</button>
|
||||
<span {...counter}>0</span>
|
||||
<button {...add}>+</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import type { Styles } from "~/types";
|
||||
|
||||
interface HeadingProps {
|
||||
children: JSX.Children;
|
||||
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "div" | "span";
|
||||
size?:
|
||||
| "xs"
|
||||
| "sm"
|
||||
| "base"
|
||||
| "lg"
|
||||
| "xl"
|
||||
| "2xl"
|
||||
| "3xl"
|
||||
| "4xl"
|
||||
| "5xl"
|
||||
| "6xl"
|
||||
| "7xl"
|
||||
| "8xl"
|
||||
| "9xl";
|
||||
style?: Styles;
|
||||
}
|
||||
|
||||
const Heading = ({
|
||||
children,
|
||||
as = "span",
|
||||
size = "base",
|
||||
style,
|
||||
}: HeadingProps) => {
|
||||
const Tag = as;
|
||||
return (
|
||||
<Tag
|
||||
style={{
|
||||
fontSize: "$" + size,
|
||||
fontWeight: "$extrabold",
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export default Heading;
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import Global from "~/lib/Global";
|
||||
import Navigation from "./Navigation";
|
||||
import Heading from "./Heading";
|
||||
import { Link } from "./Link";
|
||||
|
||||
interface LayoutProps {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
|
|
@ -7,29 +9,72 @@ interface LayoutProps {
|
|||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginX: "auto",
|
||||
paddingX: "$4",
|
||||
marginTop: "$2",
|
||||
maxWidth: "1100px",
|
||||
gap: "$4",
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Global
|
||||
css={{
|
||||
body: {
|
||||
backgroundColor: "$gray.950",
|
||||
backgroundColor: "$gray.900",
|
||||
color: "$gray.50",
|
||||
fontFamily: "$sans",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<header>Header</header>
|
||||
<Navigation />
|
||||
{children}
|
||||
</div>
|
||||
<div>
|
||||
<header
|
||||
style={{
|
||||
pY: "$4",
|
||||
pX: "$6",
|
||||
gap: "$2",
|
||||
display: "grid",
|
||||
maxWidth: "1280px",
|
||||
gridTemplateColumns: "auto 1fr auto",
|
||||
mX: "auto",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Link>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
fillRule="evenodd"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="2"
|
||||
clipRule="evenodd"
|
||||
viewbox="0 0 3307 593"
|
||||
style={{
|
||||
height: "$7",
|
||||
width: "auto",
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
style={{
|
||||
color: "$white",
|
||||
}}
|
||||
fill="currentColor"
|
||||
fill-rule="nonzero"
|
||||
d="M1053.02 205.51c35.59 0 64.27 10.1 84.98 30.81 20.72 21.25 31.34 52.05 31.34 93.48v162.53h-66.4V338.3c0-24.96-5.3-43.55-16.46-56.3-11.15-12.22-26.55-18.6-47.27-18.6-22.3 0-40.37 7.45-53.65 21.79-13.27 14.87-20.18 36.11-20.18 63.2v143.94h-66.4V338.3c0-24.96-5.3-43.55-16.46-56.3-11.15-12.22-26.56-18.6-47.27-18.6-22.84 0-40.37 7.45-53.65 21.79-13.27 14.34-20.18 35.58-20.18 63.2v143.94h-66.4V208.7h63.21v36.12c10.63-12.75 23.9-22.3 39.84-29.21 15.93-6.9 33.46-10.1 53.11-10.1 21.25 0 40.37 3.72 56.84 11.69 16.46 8.5 29.21 20.18 38.77 35.59 11.69-14.88 26.56-26.56 45.15-35.06 18.59-7.97 38.77-12.22 61.08-12.22Zm329.84 290.54c-28.68 0-54.7-6.37-77.54-18.59a133.19 133.19 0 0 1-53.65-52.05c-13.28-21.78-19.65-46.74-19.65-74.9 0-28.14 6.37-53.1 19.65-74.88a135.4 135.4 0 0 1 53.65-51.53c22.84-12.21 48.86-18.59 77.54-18.59 29.22 0 55.24 6.38 78.08 18.6 22.84 12.21 40.9 29.74 54.18 51.52 12.75 21.77 19.12 46.74 19.12 74.89s-6.37 53.11-19.12 74.89c-13.28 22.3-31.34 39.83-54.18 52.05-22.84 12.22-48.86 18.6-78.08 18.6Zm0-56.83c24.44 0 44.62-7.97 60.55-24.43 15.94-16.47 23.9-37.72 23.9-64.27 0-26.56-7.96-47.8-23.9-64.27-15.93-16.47-36.11-24.43-60.55-24.43-24.43 0-44.61 7.96-60.02 24.43-15.93 16.46-23.9 37.71-23.9 64.27 0 26.55 7.97 47.8 23.9 64.27 15.4 16.46 35.6 24.43 60.02 24.43Zm491.32-341v394.11h-63.74v-36.65a108.02 108.02 0 0 1-40.37 30.28c-16.46 6.9-34 10.1-53.65 10.1-27.08 0-51.52-5.85-73.3-18.07-21.77-12.21-39.3-29.21-51.52-51.52-12.21-21.78-18.59-47.27-18.59-75.95s6.38-54.18 18.6-75.96c12.21-21.77 29.74-38.77 51.52-50.99 21.77-12.21 46.2-18.06 73.3-18.06 18.59 0 36.11 3.2 51.52 9.56a106.35 106.35 0 0 1 39.83 28.69V98.22h66.4Zm-149.79 341c15.94 0 30.28-3.72 43.03-11.16 12.74-6.9 22.83-17.52 30.27-30.8 7.44-13.28 11.15-29.21 11.15-46.74s-3.71-33.46-11.15-46.74c-7.44-13.28-17.53-23.9-30.27-31.34-12.75-6.9-27.1-10.62-43.03-10.62s-30.27 3.71-43.02 10.62c-12.75 7.43-22.84 18.06-30.28 31.34-7.43 13.28-11.15 29.2-11.15 46.74 0 17.53 3.72 33.46 11.15 46.74 7.44 13.28 17.53 23.9 30.28 30.8 12.75 7.44 27.09 11.16 43.02 11.16Zm298.51-189.09c19.12-29.74 52.58-44.62 100.92-44.62v63.21a84.29 84.29 0 0 0-15.4-1.6c-26.03 0-46.22 7.44-60.56 22.32-14.34 15.4-21.78 37.18-21.78 65.33v137.56h-66.39V208.7h63.2v41.43Zm155.63-41.43h66.39v283.63h-66.4V208.7Zm33.46-46.74c-12.22 0-22.31-3.72-30.28-11.68a37.36 37.36 0 0 1-12.21-28.16c0-11.15 4.25-20.71 12.21-28.68 7.97-7.43 18.06-11.15 30.28-11.15 12.21 0 22.3 3.72 30.27 10.62 7.97 7.44 12.22 16.47 12.22 27.62 0 11.69-3.72 21.25-11.69 29.21-7.96 7.97-18.59 12.22-30.8 12.22Zm279.38 43.55c35.59 0 64.27 10.63 86.05 31.34 21.78 20.72 32.4 52.05 32.4 92.95v162.53h-66.4V338.3c0-24.96-5.84-43.55-17.52-56.3-11.69-12.22-28.15-18.6-49.93-18.6-24.43 0-43.55 7.45-57.9 21.79-14.34 14.87-21.24 36.11-21.24 63.73v143.41h-66.4V208.7h63.21v36.65c11.16-13.28 24.97-22.84 41.43-29.74 16.47-6.9 35.59-10.1 56.3-10.1Zm371.81 271.42a78.34 78.34 0 0 1-28.15 14.34 130.83 130.83 0 0 1-35.6 4.78c-31.33 0-55.23-7.97-72.23-24.43-17-16.47-25.5-39.84-25.5-71.17V263.94h-46.73v-53.11h46.74v-64.8h66.4v64.8h75.95v53.11h-75.96v134.91c0 13.81 3.19 24.43 10.1 31.34 6.9 7.44 16.46 11.15 29.2 11.15 14.88 0 27.1-3.71 37.19-11.68l18.59 47.27Zm214.05-271.42c35.59 0 64.27 10.63 86.05 31.34 21.77 20.72 32.4 52.05 32.4 92.95v162.53h-66.4V338.3c0-24.96-5.84-43.55-17.53-56.3-11.68-12.22-28.15-18.6-49.92-18.6-24.44 0-43.56 7.45-57.9 21.79-14.34 14.87-21.24 36.11-21.24 63.73v143.41h-66.4V98.23h66.4v143.4c11.15-11.68 24.43-20.71 40.9-27.09 15.93-5.84 33.99-9.03 53.64-9.03Z"
|
||||
></path>
|
||||
<g
|
||||
fill="currentColor"
|
||||
style={{
|
||||
color: "$green.500",
|
||||
}}
|
||||
>
|
||||
<path d="m29 424.4 188.2-112.95-17.15-45.48 53.75-55.21 67.93-14.64 19.67 24.21-31.32 31.72-27.3 8.6-19.52 20.05 9.56 26.6 19.4 20.6 27.36-7.28 19.47-21.38 42.51-13.47 12.67 28.5-43.87 53.78-73.5 23.27-32.97-36.7L55.06 467.94C46.1 456.41 35.67 440.08 29 424.4Zm543.03-230.25-149.5 40.32c8.24 21.92 10.95 34.8 13.23 49l149.23-40.26c-2.38-15.94-6.65-32.17-12.96-49.06Z"></path>
|
||||
<path d="M51.28 316.13c10.59 125 115.54 223.3 243.27 223.3 96.51 0 180.02-56.12 219.63-137.46l48.61 16.83c-46.78 101.34-149.35 171.75-268.24 171.75C138.6 590.55 10.71 469.38 0 316.13h51.28ZM.78 265.24C15.86 116.36 141.73 0 294.56 0c162.97 0 295.28 132.31 295.28 295.28 0 26.14-3.4 51.49-9.8 75.63l-48.48-16.78a244.28 244.28 0 0 0 7.15-58.85c0-134.75-109.4-244.15-244.15-244.15-124.58 0-227.49 93.5-242.32 214.11H.8Z"></path>
|
||||
<path d="M293.77 153.17c-78.49.07-142.2 63.83-142.2 142.34 0 78.56 63.79 142.34 142.35 142.34 3.98 0 7.93-.16 11.83-.49l14.22 49.76a194.65 194.65 0 0 1-26.05 1.74c-106.72 0-193.36-86.64-193.36-193.35 0-106.72 86.64-193.35 193.36-193.35 2.64 0 5.28.05 7.9.16l-8.05 50.85Zm58.2-42.13c78.39 24.67 135.3 97.98 135.3 184.47 0 80.07-48.77 148.83-118.2 178.18l-14.17-49.55c48.08-22.85 81.36-71.89 81.36-128.63 0-60.99-38.44-113.07-92.39-133.32l8.1-51.15Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
<Navigation />
|
||||
<div></div>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
<footer></footer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
type Props = JSX.IntrinsicElements["a"];
|
||||
|
||||
export const Link = (props: Props) => {
|
||||
return <a {...props} />;
|
||||
};
|
||||
|
|
@ -13,13 +13,21 @@ export default function Navigation() {
|
|||
<nav
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "1rem",
|
||||
alignItems: "center",
|
||||
margin: "auto",
|
||||
gap: "$2",
|
||||
}}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<a
|
||||
style={{
|
||||
backgroundColor: "$gray.700",
|
||||
_hover: {
|
||||
backgroundColor: "$gray.900",
|
||||
},
|
||||
fontWeight: "$semibold",
|
||||
paddingY: "$2",
|
||||
paddingX: "$4",
|
||||
borderRadius: "$md",
|
||||
}}
|
||||
href={item.href}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { myPlugin } from "./myPlugin";
|
||||
import { fillTemplate, render } from "./lib/fast-web";
|
||||
import { requestLog } from "./middlewares/request-log";
|
||||
import template from "./template.html" with { type: "text" };
|
||||
import theme from "./theme.css" with { type: "text" };
|
||||
import staticDir from "serve-static-bun";
|
||||
|
|
@ -10,11 +12,11 @@ const router = new Bun.FileSystemRouter({
|
|||
|
||||
const staticPublic = staticDir("./public");
|
||||
|
||||
const glob = new Bun.Glob(__dirname + "/scripts/*");
|
||||
const scriptsGlob = new Bun.Glob(__dirname + "/scripts/*");
|
||||
|
||||
const server = Bun.serve({
|
||||
port: 8080,
|
||||
fetch: async (req): Promise<Response> => {
|
||||
fetch: requestLog(async (req): Promise<Response> => {
|
||||
if (req.url.endsWith("ws")) {
|
||||
if (server.upgrade(req)) return new Response("ok");
|
||||
}
|
||||
|
|
@ -23,30 +25,37 @@ const server = Bun.serve({
|
|||
if (!match) return staticPublic(req);
|
||||
const Comp = (await import(__dirname + "/pages/" + match?.pathname))
|
||||
.default;
|
||||
const renderResult = render(<Comp />, [theme]);
|
||||
const renderResult = await render(<Comp />, [theme], new URL(req.url));
|
||||
|
||||
const systemScripts = await Bun.build({
|
||||
entrypoints: [...glob.scanSync()],
|
||||
entrypoints: [
|
||||
...scriptsGlob.scanSync(),
|
||||
"./src/components/Counter.state.ts",
|
||||
],
|
||||
minify: true,
|
||||
plugins: [myPlugin],
|
||||
});
|
||||
const scripts = await Promise.all(
|
||||
systemScripts.outputs.map(
|
||||
async (script) => `<script>${await script.text()}</script>`,
|
||||
async (script) =>
|
||||
`<script type="module">${await script.text()}</script>`,
|
||||
),
|
||||
);
|
||||
|
||||
return new Response(
|
||||
fillTemplate(template, {
|
||||
head: renderResult.head + scripts,
|
||||
body: renderResult.html,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "text/html",
|
||||
},
|
||||
const responseData = fillTemplate(template, {
|
||||
head: renderResult.head + scripts.join(""),
|
||||
body: renderResult.html,
|
||||
});
|
||||
|
||||
const gzipResponseData = Bun.gzipSync(responseData);
|
||||
|
||||
return new Response(gzipResponseData, {
|
||||
headers: {
|
||||
"content-type": "text/html",
|
||||
"Content-Encoding": "gzip",
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
}),
|
||||
websocket: {
|
||||
message: () => {},
|
||||
open: async (ws) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
namespace JSX {
|
||||
interface FC {
|
||||
(props: any): JSX.Element | string | null;
|
||||
(
|
||||
props: any,
|
||||
):
|
||||
| JSX.Element
|
||||
| string
|
||||
| null
|
||||
| Promise<JSX.Element>
|
||||
| Promise<string>
|
||||
| Promsie<null>;
|
||||
}
|
||||
type Children =
|
||||
| JSX.Element
|
||||
|
|
@ -8,20 +16,102 @@ namespace JSX {
|
|||
| string
|
||||
| null
|
||||
| undefined;
|
||||
type CSSTypeProperties = import("csstype").Properties;
|
||||
type CSSProperties = import("csstype").Properties & {
|
||||
marginX?: CSSTypeProperties["marginLeft"];
|
||||
marginY?: CSSTypeProperties["marginTop"];
|
||||
paddingX?: CSSTypeProperties["paddingLeft"];
|
||||
paddingY?: CSSTypeProperties["paddingTop"];
|
||||
};
|
||||
type BaseElementPropsWithoutChildren = {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
id?: string;
|
||||
tabindex?: number | string;
|
||||
style?: import("./types").Styles;
|
||||
[`data-${string}`]?: string | boolean;
|
||||
} & Partial<ARIAMixin>;
|
||||
type BaseElementProps = BaseElementPropsWithoutChildren & {
|
||||
children?: JSX.Children;
|
||||
};
|
||||
type SvgProps = BaseElementProps & {
|
||||
["xml:lang"]?: string;
|
||||
["xml:space"]?: string;
|
||||
xmlns?: string;
|
||||
// XLink attributes
|
||||
["xlink:hrefDeprecated"]?: string;
|
||||
["xlink:type"]?: string;
|
||||
["xlink:role"]?: string;
|
||||
["xlink:arcrole"]?: string;
|
||||
["xlink:title"]?: string;
|
||||
["xlink:show"]?: string;
|
||||
["xlink:actuate"]?: string;
|
||||
// Presentation attributes
|
||||
["alignment-baseline"]?: string;
|
||||
["baseline-shift"]?: string;
|
||||
["clip"]?: string;
|
||||
["clipPath"]?: string;
|
||||
["clipRule"]?: string;
|
||||
["color"]?: string;
|
||||
["colorInterpolation"]?: string;
|
||||
["colorInterpolationFilters"]?: string;
|
||||
["cursor"]?: string;
|
||||
["cx"]?: string;
|
||||
["cy"]?: string;
|
||||
["d"]?: string;
|
||||
["direction"]?: string;
|
||||
["display"]?: string;
|
||||
["dominantBaseline"]?: string;
|
||||
["fill"]?: string;
|
||||
["fillOpacity"]?: string;
|
||||
["fillRule"]?: string;
|
||||
["filter"]?: string;
|
||||
["floodColor"]?: string;
|
||||
["floodOpacity"]?: string;
|
||||
["fontFamily"]?: string;
|
||||
["fontSize"]?: string;
|
||||
["fontSize-adjust"]?: string;
|
||||
["fontStretch"]?: string;
|
||||
["fontStyle"]?: string;
|
||||
["fontVariant"]?: string;
|
||||
["fontWeight"]?: string;
|
||||
["glyphOrientation-horizontal"]?: string;
|
||||
["glyphOrientation-vertical"]?: string;
|
||||
["height"]?: string;
|
||||
["imageRendering"]?: string;
|
||||
["letterSpacing"]?: string;
|
||||
["lightingColor"]?: string;
|
||||
["markerEnd"]?: string;
|
||||
["markerMid"]?: string;
|
||||
["markerStart"]?: string;
|
||||
["mask"]?: string;
|
||||
["maskType"]?: string;
|
||||
["opacity"]?: string;
|
||||
["overflow"]?: string;
|
||||
["pointerEvents"]?: string;
|
||||
["r"]?: string;
|
||||
["rx"]?: string;
|
||||
["ry"]?: string;
|
||||
["shapeRendering"]?: string;
|
||||
["stopColor"]?: string;
|
||||
["stopOpacity"]?: string;
|
||||
["stroke"]?: string;
|
||||
["strokeDasharray"]?: string;
|
||||
["strokeDashoffset"]?: string;
|
||||
["strokeLinecap"]?: string;
|
||||
["strokeLinejoin"]?: string;
|
||||
["strokeMiterlimit"]?: string;
|
||||
["strokeOpacity"]?: string;
|
||||
["strokeWidth"]?: string;
|
||||
["textAnchor"]?: string;
|
||||
["textDecoration"]?: string;
|
||||
["textOverflow"]?: string;
|
||||
["textRendering"]?: string;
|
||||
["transform"]?: string;
|
||||
["transformOrigin"]?: string;
|
||||
["unicodeBidi"]?: string;
|
||||
["vectorEffect"]?: string;
|
||||
["visibility"]?: string;
|
||||
["whiteSpace"]?: string;
|
||||
["width"]?: string;
|
||||
["wordSpacing"]?: string;
|
||||
["writingMode"]?: string;
|
||||
["x"]?: string;
|
||||
["y"]?: string;
|
||||
viewbox?: string;
|
||||
};
|
||||
interface IntrinsicElements {
|
||||
a: BaseElementProps & {
|
||||
href?: string;
|
||||
|
|
@ -50,6 +140,9 @@ namespace JSX {
|
|||
source: BaseElementPropsWithoutChildren;
|
||||
track: BaseElementPropsWithoutChildren;
|
||||
wbr: BaseElementPropsWithoutChildren;
|
||||
svg: SvgProps;
|
||||
path: SvgProps;
|
||||
g: SvgProps;
|
||||
[tagName: string]: BaseElementProps;
|
||||
}
|
||||
interface Element {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
import { getStyleRules } from "~/system/style-rules";
|
||||
import { renderStyle } from "~/system/style-rules";
|
||||
import { pushStyle } from "./fast-web";
|
||||
import type { Styles } from "~/types";
|
||||
|
||||
interface GlobalProps {
|
||||
css?: Record<string, JSX.CSSProperties>;
|
||||
css?: Record<string, Styles>;
|
||||
}
|
||||
|
||||
export default function Global({ css = {} }: GlobalProps) {
|
||||
const stylesArray = Object.entries(css).map<[string, string[]]>(
|
||||
([key, value]) => {
|
||||
return [key, getStyleRules(value)];
|
||||
},
|
||||
);
|
||||
|
||||
stylesArray.forEach(([key, value]) => {
|
||||
pushStyle(`${key} {${value.join(" ")}}`);
|
||||
Object.entries(css).forEach(([key, value]) => {
|
||||
const style = renderStyle(value, key);
|
||||
pushStyle(style.css);
|
||||
});
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
import { camelToKebab, renderStyle } from "~/system/style-rules";
|
||||
import { transform } from "lightningcss";
|
||||
import reset from "./reset.css" with { type: "text" };
|
||||
import { AsyncLocalStorage } from "async_hooks";
|
||||
|
||||
const renderChildren = (elements: JSX.Children): string => {
|
||||
interface RenderContext {
|
||||
styles: string[];
|
||||
url: URL;
|
||||
states: Record<string, { state: any; handlerId: string }>;
|
||||
// Mapping handlerHash to file
|
||||
handlers: Record<string, string>;
|
||||
}
|
||||
|
||||
const asyncLocalStorage = new AsyncLocalStorage<RenderContext>();
|
||||
|
||||
const renderChildren = async (elements: JSX.Children): Promise<string> => {
|
||||
if (Array.isArray(elements)) {
|
||||
return elements.map((element) => renderChildren(element)).join("");
|
||||
return (
|
||||
await Promise.all(elements.map((element) => renderChildren(element)))
|
||||
).join("");
|
||||
}
|
||||
if (typeof elements === "string") {
|
||||
return elements;
|
||||
|
|
@ -12,15 +25,18 @@ const renderChildren = (elements: JSX.Children): string => {
|
|||
return renderElement(elements);
|
||||
};
|
||||
|
||||
let styles: string[] = [];
|
||||
const getStyles = () => asyncLocalStorage.getStore()!.styles;
|
||||
export const getUrl = () => asyncLocalStorage.getStore()!.url;
|
||||
export const getHandlers = () => asyncLocalStorage.getStore()!.handlers;
|
||||
export const getStates = () => asyncLocalStorage.getStore()!.states;
|
||||
|
||||
export const pushStyle = (style: string) => {
|
||||
styles.push(style);
|
||||
export const pushStyle = (...styles: string[]) => {
|
||||
getStyles().push(...styles);
|
||||
};
|
||||
|
||||
const renderElement = (
|
||||
const renderElement = async (
|
||||
element: JSX.Element | string | null | undefined,
|
||||
): string => {
|
||||
): Promise<string> => {
|
||||
if (!element) {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -34,7 +50,7 @@ const renderElement = (
|
|||
let { className, ...otherProps } = element.props;
|
||||
if ("style" in element.props) {
|
||||
const res = renderStyle(element.props.style);
|
||||
styles.push(res.css);
|
||||
pushStyle(res.css);
|
||||
className = className ? `${className} ${res.className}` : res.className;
|
||||
}
|
||||
const props = { ...otherProps, class: className };
|
||||
|
|
@ -50,32 +66,54 @@ const renderElement = (
|
|||
}
|
||||
return ` ${camelToKebab(key)}="${value}"`;
|
||||
});
|
||||
return `<${element.type}${attrs.join("")}>${element.children ? renderChildren(element.children) : ""}</${element.type}>`;
|
||||
return `<${element.type}${attrs.join("")}>${element.children ? await renderChildren(element.children) : ""}</${element.type}>`;
|
||||
}
|
||||
if (typeof element.type === "function") {
|
||||
return renderElement(
|
||||
element.type({ ...element.props, children: element.children }),
|
||||
await element.type({ ...element.props, children: element.children }),
|
||||
);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const render = (
|
||||
const render = async (
|
||||
element: JSX.Element | string | null,
|
||||
extraCss: string[] = [],
|
||||
) => {
|
||||
styles = [reset, ...extraCss];
|
||||
const html = renderElement(element);
|
||||
pushStyle(reset, ...extraCss);
|
||||
const html = await renderElement(element);
|
||||
const encoder = new TextEncoder();
|
||||
const css = transform({
|
||||
code: encoder.encode(styles.join("\n")),
|
||||
code: encoder.encode(getStyles().join("\n")),
|
||||
minify: true,
|
||||
filename: "render-style.ts",
|
||||
}).code;
|
||||
const head = `<style>${css.toString()}</style>`;
|
||||
const head = `<style>${css.toString()}</style>
|
||||
<script>
|
||||
window.stateManager ??= {
|
||||
init: false,
|
||||
states: {},
|
||||
stateRegistry: new Map()
|
||||
}
|
||||
window.stateManager.states = {...window.stateManager.states,...${JSON.stringify(getStates())}}
|
||||
</script>
|
||||
`;
|
||||
return { html, head };
|
||||
};
|
||||
|
||||
const runRender = (
|
||||
element: JSX.Element | string | null,
|
||||
extraCss: string[] = [],
|
||||
url: URL,
|
||||
) => {
|
||||
return asyncLocalStorage.run(
|
||||
{ styles: [], url, states: {}, handlers: {} },
|
||||
() => render(element, extraCss),
|
||||
);
|
||||
};
|
||||
|
||||
export { runRender as render };
|
||||
|
||||
export const fillTemplate = (
|
||||
template: string,
|
||||
data: Record<string, string>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { crazyHash } from "~/system/crazyHash";
|
||||
import { getHandlers, getStates, getUrl } from "./fast-web";
|
||||
import type { CreateStateResult } from "./state";
|
||||
|
||||
export const useUrl = (): URL => {
|
||||
return getUrl();
|
||||
};
|
||||
|
||||
export const useParams = (): URLSearchParams => {
|
||||
return getUrl().searchParams;
|
||||
};
|
||||
|
||||
export const usePath = (): string => {
|
||||
return getUrl().pathname;
|
||||
};
|
||||
|
||||
export const useState = <S, T extends string>(
|
||||
state: CreateStateResult<S, T>,
|
||||
) => {
|
||||
const handlerId = state.meta.hash;
|
||||
const handlers = getHandlers();
|
||||
handlers[handlerId] = state.meta.path;
|
||||
const stateId = crazyHash(Bun.randomUUIDv7());
|
||||
const parts = Object.keys(state.handler);
|
||||
getStates()[stateId] = {
|
||||
handlerId,
|
||||
state: state.initial,
|
||||
};
|
||||
return Object.fromEntries(
|
||||
parts.map((part) => [
|
||||
part,
|
||||
{
|
||||
"data-c": part + "-" + stateId,
|
||||
},
|
||||
]),
|
||||
) as unknown as Record<T, Record<`data-${string}`, string>>;
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import Pokedex from "pokedex-promise-v2";
|
||||
|
||||
export const pokeapi = new Pokedex();
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
type StateContext<S extends any, T extends string> = Record<
|
||||
`$${T}`,
|
||||
HTMLElement
|
||||
> & {
|
||||
get: () => S;
|
||||
set: (state: Partial<S>) => void;
|
||||
};
|
||||
|
||||
type StateHandler<S extends any, T extends string> = Record<
|
||||
T,
|
||||
(ctx: StateContext<S, T>) => {
|
||||
onClick?: (event: MouseEvent) => void;
|
||||
}
|
||||
>;
|
||||
|
||||
interface StateMeta {
|
||||
path: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
interface StateRegistryEntry<S, T extends string> {
|
||||
handler: StateHandler<S, T>;
|
||||
}
|
||||
|
||||
interface StateManager {
|
||||
init: boolean;
|
||||
states: Record<string, { state: any; handlerId: string }>;
|
||||
stateRegistry: Map<string, StateRegistryEntry<any, string>>;
|
||||
}
|
||||
|
||||
interface MyWindow extends Window {
|
||||
stateManager: StateManager;
|
||||
}
|
||||
|
||||
declare let window: MyWindow;
|
||||
|
||||
const stateManager: StateManager =
|
||||
typeof window !== "undefined" && "stateManager" in window
|
||||
? window.stateManager
|
||||
: {
|
||||
init: false,
|
||||
states: {},
|
||||
stateRegistry: new Map(),
|
||||
};
|
||||
|
||||
export type CreateStateResult<S, T extends string> = {
|
||||
meta: StateMeta;
|
||||
initial: S;
|
||||
handler: StateHandler<S, T>;
|
||||
};
|
||||
|
||||
export const createState = <S, T extends string>(
|
||||
initialState: S,
|
||||
stateHandler: StateHandler<S, T>,
|
||||
meta?: StateMeta,
|
||||
): CreateStateResult<S, T> => {
|
||||
if (!meta) {
|
||||
throw new Error("Missing meta please check if bun plugin is loaded");
|
||||
}
|
||||
if (!stateManager.stateRegistry.has(meta.hash)) {
|
||||
stateManager.stateRegistry.set(meta.hash, {
|
||||
handler: stateHandler,
|
||||
});
|
||||
}
|
||||
return {
|
||||
meta,
|
||||
initial: initialState,
|
||||
handler: stateHandler,
|
||||
};
|
||||
};
|
||||
|
||||
if (!stateManager.init && typeof window != "undefined") {
|
||||
stateManager.init = true;
|
||||
window.stateManager = stateManager;
|
||||
document.addEventListener("click", (e) => {
|
||||
const target = e.target;
|
||||
if (target instanceof HTMLElement && target.dataset["c"]) {
|
||||
const [part, stateId] = target.dataset["c"].split("-");
|
||||
const { handler } = stateManager.stateRegistry.get(
|
||||
stateManager.states[stateId].handlerId,
|
||||
)!;
|
||||
const parts = Object.keys(handler);
|
||||
const { state } = stateManager.states[stateId];
|
||||
const context = Object.fromEntries([
|
||||
["get", () => state],
|
||||
[
|
||||
"set",
|
||||
(newState: any) => {
|
||||
stateManager.states[stateId].state = { ...state, ...newState };
|
||||
},
|
||||
],
|
||||
...parts.map((part) => [
|
||||
`$$${part}`,
|
||||
document.querySelector(`[data-c=${part}-${stateId}]`),
|
||||
]),
|
||||
]);
|
||||
handler[part](context).onClick?.(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
const formatBytes = (bytes: number) => {
|
||||
if (bytes > 1024 * 1024 * 1024) {
|
||||
return Math.floor((bytes * 100) / (1024 * 1024 * 1024)) / 100 + "GB";
|
||||
}
|
||||
if (bytes > 1024 * 1024) {
|
||||
return Math.floor((bytes * 100) / (1024 * 1024)) / 100 + "MB";
|
||||
}
|
||||
if (bytes > 1024) {
|
||||
return Math.floor((bytes * 100) / 1024) / 100 + "KB";
|
||||
}
|
||||
return bytes + "B";
|
||||
};
|
||||
|
||||
export const requestLog = (handler: (req: Request) => Promise<Response>) => {
|
||||
return async (req: Request) => {
|
||||
const start = performance.now();
|
||||
const response = await handler(req);
|
||||
const bytes = await response.clone().bytes();
|
||||
const delta = performance.now() - start;
|
||||
console.log(
|
||||
`[${req.method}] ${req.url} -> ${response.headers.get("Content-Type")} ${formatBytes(bytes.length)} in ${delta}ms`,
|
||||
);
|
||||
return response;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { plugin, type BunPlugin } from "bun";
|
||||
import { crazyHash } from "./system/crazyHash";
|
||||
|
||||
const createStateString = "createState(";
|
||||
const createStateStringRegex = /createState\(/g;
|
||||
export const myPlugin: BunPlugin = {
|
||||
name: "Custom loader",
|
||||
setup(build) {
|
||||
build.onLoad(
|
||||
{
|
||||
filter: /\.tsx?$/,
|
||||
},
|
||||
async (args) => {
|
||||
console.log("loading", args.path);
|
||||
const path = require.resolve(args.path);
|
||||
let contents = await Bun.file(path).text();
|
||||
const createStateCalls = contents.matchAll(createStateStringRegex);
|
||||
createStateCalls.forEach((match) => {
|
||||
console.log(match);
|
||||
let braces = 1;
|
||||
let index = match.index + createStateString.length;
|
||||
let needsComma = false;
|
||||
console.log(contents[index]);
|
||||
do {
|
||||
index++;
|
||||
if (contents[index] == "(") braces++;
|
||||
else if (contents[index] == ")") braces--;
|
||||
else if (contents[index] == ",") needsComma = false;
|
||||
else if (contents[index].trim().length) needsComma = true;
|
||||
} while (braces > 0);
|
||||
console.log(contents[index]);
|
||||
const meta = {
|
||||
path,
|
||||
hash: crazyHash(contents),
|
||||
};
|
||||
contents =
|
||||
contents.substring(0, index) +
|
||||
(needsComma ? ", " : "") +
|
||||
JSON.stringify(meta) +
|
||||
contents.substring(index);
|
||||
console.log(contents);
|
||||
});
|
||||
// console.log(contents);
|
||||
return {
|
||||
contents,
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
plugin(myPlugin);
|
||||
|
|
@ -1,9 +1,30 @@
|
|||
import { Counter } from "~/components/Counter";
|
||||
import Layout from "~/components/Layout";
|
||||
import { Link } from "~/components/Link";
|
||||
import { useParams } from "~/lib/hooks";
|
||||
import { pokeapi } from "~/lib/pokeapi";
|
||||
|
||||
export default function Index() {
|
||||
export default async function Index() {
|
||||
const params = useParams();
|
||||
const page = Number(params.get("page") ?? 0);
|
||||
const pokemon = await pokeapi.getPokemonsList({
|
||||
limit: 20,
|
||||
offset: page * 20,
|
||||
});
|
||||
return (
|
||||
<Layout>
|
||||
<div>Hello World</div>
|
||||
<>
|
||||
<div>Hello World</div>
|
||||
<Counter />
|
||||
<Counter />
|
||||
<ul>
|
||||
{pokemon.results.map((r) => (
|
||||
<li>{r.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link href={`/?page=${page - 1}`}>Prev</Link>
|
||||
<Link href={`/?page=${page + 1}`}>Next</Link>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
document.addEventListener("pointerdown", (e) => {
|
||||
const target = e.target;
|
||||
if (target instanceof HTMLElement) {
|
||||
if (target.tagName == "A" && !target.getAttribute("target")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const href = target.getAttribute("href")!;
|
||||
window.history.pushState({}, "", href);
|
||||
fetch(href).then(async (res) => {
|
||||
window.document.documentElement.innerHTML = await res.text();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export function crazyHash(data: string) {
|
||||
let num = Bun.hash.murmur32v3(data);
|
||||
// const chars =
|
||||
// "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const chars =
|
||||
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
|
||||
let result = "";
|
||||
do {
|
||||
result = chars[num % chars.length] + result;
|
||||
num = Math.floor(num / chars.length);
|
||||
} while (num > 0);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import type { BaseCSSProperty, CSSDuration, EasingFunction } from "~/types";
|
||||
|
||||
type Transition =
|
||||
| [BaseCSSProperty]
|
||||
| [BaseCSSProperty, CSSDuration]
|
||||
| [BaseCSSProperty, CSSDuration, EasingFunction]
|
||||
| [BaseCSSProperty, CSSDuration, EasingFunction, CSSDuration];
|
||||
|
||||
export const transition = (transitions: Transition[]) => {
|
||||
return transitions.map((t) => t.join(" ")).join(",");
|
||||
};
|
||||
|
|
@ -1,55 +1,99 @@
|
|||
import { pseudoClasses, type Styles } from "~/types";
|
||||
import { getCategory, shorthand } from "./token-categories";
|
||||
import { crazyHash } from "./crazyHash";
|
||||
|
||||
export const camelToKebab = (str: string) => {
|
||||
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
|
||||
};
|
||||
|
||||
export const getStyleRules = (style: JSX.CSSProperties) => {
|
||||
const styles = Object.entries(style).map(([key, value]) => {
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
};
|
||||
});
|
||||
const rules = [];
|
||||
while (styles.length) {
|
||||
const { key, value } = styles.shift()!;
|
||||
if (key in shorthand) {
|
||||
const shorthandRules = shorthand[key as keyof typeof shorthand](value);
|
||||
styles.unshift(...shorthandRules);
|
||||
continue;
|
||||
}
|
||||
const resolveVars = (key: string, value: string) => {
|
||||
const match = value.match(/\$([a-zA-Z0-9\.\-]+)/g);
|
||||
if (match) {
|
||||
const category = getCategory(key);
|
||||
const replaceValue = match.reduce((v, m) => {
|
||||
return v.replace(
|
||||
m,
|
||||
`var(--${category}${m.replaceAll("$", "").replaceAll(".", "-")})`,
|
||||
);
|
||||
}, value);
|
||||
return replaceValue;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const kKey = camelToKebab(key);
|
||||
if (typeof value === "number") {
|
||||
rules.push(`${kKey}: ${value}px;`);
|
||||
} else if (typeof value === "string") {
|
||||
const match = value.match(/\$([a-zA-Z0-9\.\-]+)/g);
|
||||
if (match) {
|
||||
const category = getCategory(key);
|
||||
const replaceValue = match.reduce((v, m) => {
|
||||
return v.replace(
|
||||
m,
|
||||
`var(--${category}${m.replaceAll("$", "").replaceAll(".", "-")})`,
|
||||
);
|
||||
}, value);
|
||||
rules.push(`${kKey}: ${replaceValue};`);
|
||||
} else {
|
||||
rules.push(`${kKey}: ${value};`);
|
||||
}
|
||||
const renderRule = (key: string, value: string | number | string[]): string => {
|
||||
const kKey = camelToKebab(key);
|
||||
if (typeof value === "number") {
|
||||
return `${kKey}: ${value}px;`;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return `${kKey}: ${resolveVars(key, value)};`;
|
||||
}
|
||||
return `${kKey}: ${value.join(" ")};`;
|
||||
};
|
||||
|
||||
const partitionStyles = (style: Styles) => {
|
||||
const selectorStylesMapping: Record<
|
||||
string,
|
||||
Record<string, string | number | string[]>
|
||||
> = {
|
||||
"&": {},
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(style)) {
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
const selector =
|
||||
pseudoClasses[key.substring(1) as keyof typeof pseudoClasses] ?? key;
|
||||
selectorStylesMapping[selector] = value;
|
||||
} else {
|
||||
rules.push(`${kKey}: ${value.join(" ")};`);
|
||||
selectorStylesMapping["&"][key] = value;
|
||||
}
|
||||
}
|
||||
return selectorStylesMapping;
|
||||
};
|
||||
|
||||
const resolveShorthand = (
|
||||
key: string,
|
||||
value: string | number | string[],
|
||||
): { key: string; value: string | number | string[] }[] => {
|
||||
if (key in shorthand) {
|
||||
return shorthand[key as keyof typeof shorthand](value);
|
||||
}
|
||||
return [{ key, value }];
|
||||
};
|
||||
|
||||
export const getStyleRules = (style: Styles) => {
|
||||
const selectorStylesMapping = partitionStyles(style);
|
||||
const rules: Record<string, string[]> = {};
|
||||
for (const [selector, styles] of Object.entries(selectorStylesMapping)) {
|
||||
const stylePairs = Object.entries(styles).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const resolvedShorthand = resolveShorthand(key, value);
|
||||
return [...acc, ...resolvedShorthand];
|
||||
},
|
||||
[] as { key: string; value: string | number | string[] }[],
|
||||
);
|
||||
rules[selector] = stylePairs.map(({ key, value }) => {
|
||||
return renderRule(key, value);
|
||||
});
|
||||
}
|
||||
return rules;
|
||||
};
|
||||
|
||||
export const renderStyle = (style: JSX.CSSProperties) => {
|
||||
const rules = getStyleRules(style);
|
||||
const sortedRules = rules.sort();
|
||||
const rulesString = sortedRules.join(" ");
|
||||
const hash = Bun.hash(rulesString);
|
||||
const className = `style-${hash.toString(36)}`;
|
||||
const css = `.${className} {${rulesString}}`;
|
||||
const getRulesClassName = (rules: Record<string, string[]>) => {
|
||||
const flatRules = Object.values(rules).flat();
|
||||
flatRules.push(...Object.keys(rules));
|
||||
const sortedRules = flatRules.sort();
|
||||
const rulesString = sortedRules.join(";");
|
||||
return `f${crazyHash(rulesString)}`;
|
||||
};
|
||||
|
||||
export const renderStyle = (style: Styles, selectorOverride?: string) => {
|
||||
const rulesMapping = getStyleRules(style);
|
||||
const className = selectorOverride ?? getRulesClassName(rulesMapping);
|
||||
let css = "";
|
||||
for (const [selector, rules] of Object.entries(rulesMapping)) {
|
||||
css += `${selector.replaceAll("&", selectorOverride ?? "." + className)} {${rules.join(" ")}}`;
|
||||
}
|
||||
return { className, css };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
const tokenTypes = ["color", "space", "font"] as const;
|
||||
import type { CSSProperties } from "~/types";
|
||||
|
||||
const tokenTypes = [
|
||||
"color",
|
||||
"space",
|
||||
"font",
|
||||
"font-size",
|
||||
"font-weight",
|
||||
"border-radius",
|
||||
] as const;
|
||||
type TokenType = (typeof tokenTypes)[number];
|
||||
|
||||
export const shorthand = {
|
||||
marginX: (value: string) => [
|
||||
marginX: <T>(value: T) => [
|
||||
{
|
||||
key: "marginLeft",
|
||||
value,
|
||||
|
|
@ -12,7 +21,7 @@ export const shorthand = {
|
|||
value,
|
||||
},
|
||||
],
|
||||
marginY: (value: string) => [
|
||||
marginY: <T>(value: T) => [
|
||||
{
|
||||
key: "marginTop",
|
||||
value,
|
||||
|
|
@ -22,7 +31,27 @@ export const shorthand = {
|
|||
value,
|
||||
},
|
||||
],
|
||||
paddingX: (value: string) => [
|
||||
mX: <T>(value: T) => [
|
||||
{
|
||||
key: "marginLeft",
|
||||
value,
|
||||
},
|
||||
{
|
||||
key: "marginRight",
|
||||
value,
|
||||
},
|
||||
],
|
||||
mY: <T>(value: T) => [
|
||||
{
|
||||
key: "marginTop",
|
||||
value,
|
||||
},
|
||||
{
|
||||
key: "marginBottom",
|
||||
value,
|
||||
},
|
||||
],
|
||||
paddingX: <T>(value: T) => [
|
||||
{
|
||||
key: "paddingLeft",
|
||||
value,
|
||||
|
|
@ -32,7 +61,27 @@ export const shorthand = {
|
|||
value,
|
||||
},
|
||||
],
|
||||
paddingY: (value: string) => [
|
||||
paddingY: <T>(value: T) => [
|
||||
{
|
||||
key: "paddingTop",
|
||||
value,
|
||||
},
|
||||
{
|
||||
key: "paddingBottom",
|
||||
value,
|
||||
},
|
||||
],
|
||||
pX: <T>(value: T) => [
|
||||
{
|
||||
key: "paddingLeft",
|
||||
value,
|
||||
},
|
||||
{
|
||||
key: "paddingRight",
|
||||
value,
|
||||
},
|
||||
],
|
||||
pY: <T>(value: T) => [
|
||||
{
|
||||
key: "paddingTop",
|
||||
value,
|
||||
|
|
@ -63,8 +112,13 @@ export const categoryMap = {
|
|||
paddingX: "space",
|
||||
paddingY: "space",
|
||||
gap: "space",
|
||||
height: "space",
|
||||
width: "space",
|
||||
fontFamily: "font",
|
||||
} as const satisfies Partial<Record<keyof JSX.CSSProperties, TokenType>>;
|
||||
fontSize: "font-size",
|
||||
fontWeight: "font-weight",
|
||||
borderRadius: "border-radius",
|
||||
} as const satisfies Partial<Record<keyof CSSProperties, TokenType>>;
|
||||
|
||||
export const getCategory = (value: string) => {
|
||||
return value in categoryMap
|
||||
|
|
|
|||
|
|
@ -280,5 +280,38 @@
|
|||
--space-72: 18rem;
|
||||
--space-80: 20rem;
|
||||
|
||||
--font-sans: "Rubik", sans-serif;
|
||||
--font-sans: sans-serif;
|
||||
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--font-size-2xl: 1.5rem;
|
||||
--font-size-3xl: 1.875rem;
|
||||
--font-size-4xl: 2.25rem;
|
||||
--font-size-5xl: 3rem;
|
||||
--font-size-6xl: 3.75rem;
|
||||
--font-size-7xl: 4.5rem;
|
||||
--font-size-8xl: 6rem;
|
||||
--font-size-9xl: 8rem;
|
||||
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
--font-weight-black: 900;
|
||||
|
||||
--border-radius-xs: 0.125rem;
|
||||
--border-radius-sm: 0.25rem;
|
||||
--border-radius-md: 0.375rem;
|
||||
--border-radius-lg: 0.5rem;
|
||||
--border-radius-xl: 0.75rem;
|
||||
--border-radius-2xl: 1rem;
|
||||
--border-radius-3xl: 1.5rem;
|
||||
--border-radius-4xl: 2rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import * as CSS from "csstype";
|
||||
|
||||
export const pseudoClasses = {
|
||||
hover: "&:hover",
|
||||
focus: "&:focus",
|
||||
active: "&:active",
|
||||
visited: "&:visited",
|
||||
first: "&:first-child",
|
||||
last: "&:last-child",
|
||||
odd: "&:nth-child(odd)",
|
||||
even: "&:nth-child(even)",
|
||||
disabled: "&:disabled",
|
||||
checked: "&:checked",
|
||||
selected: "&:selected",
|
||||
readOnly: "&:read-only",
|
||||
empty: "&:empty",
|
||||
before: "&:before",
|
||||
after: "&:after",
|
||||
link: "&:link",
|
||||
visitedLink: "&:visited",
|
||||
activeLink: "&:active",
|
||||
focusWithin: "&:focus-within",
|
||||
} as const;
|
||||
|
||||
export type PseudoClassProperty = `_${keyof typeof pseudoClasses}`;
|
||||
|
||||
export type BaseCSSProperty = CSS.Properties;
|
||||
export type CSSProperties = CSS.Properties & {
|
||||
marginX?: CSS.Properties["marginLeft"];
|
||||
marginY?: CSS.Properties["marginTop"];
|
||||
paddingX?: CSS.Properties["paddingLeft"];
|
||||
paddingY?: CSS.Properties["paddingTop"];
|
||||
mX?: CSS.Properties["marginLeft"];
|
||||
mY?: CSS.Properties["marginTop"];
|
||||
pX?: CSS.Properties["paddingLeft"];
|
||||
pY?: CSS.Properties["paddingTop"];
|
||||
};
|
||||
export type CSSDuration = `${number}s` | `${number}ms`;
|
||||
export type EasingFunction =
|
||||
| (string & {})
|
||||
| "linear"
|
||||
| "ease"
|
||||
| "ease-in"
|
||||
| "ease-in-out"
|
||||
| "ease-out";
|
||||
|
||||
type ArbitrarySelectors = Record<`&${string}`, CSSProperties>;
|
||||
|
||||
type PseudoClassSelectors = Partial<Record<PseudoClassProperty, CSSProperties>>;
|
||||
|
||||
export type Styles = ArbitrarySelectors & PseudoClassSelectors & CSSProperties;
|
||||
Loading…
Reference in New Issue