diff --git a/.ladle/components.tsx b/.ladle/components.tsx new file mode 100644 index 0000000..f019c99 --- /dev/null +++ b/.ladle/components.tsx @@ -0,0 +1,29 @@ +import React, { useEffect } from "react"; +import "../styles/globals.css"; +import "./styles.css"; + +import type { GlobalProvider } from "@ladle/react"; +import { ThemeProvider, useTheme } from "next-themes"; + +export const Provider: GlobalProvider = ({ children, globalState }) => { + const { setTheme } = useTheme(); + + useEffect(() => { + setTheme(globalState.theme); + }, [globalState.theme, setTheme]); + + return ( +
+ + {children} + +
+ ); +}; diff --git a/.ladle/config.mjs b/.ladle/config.mjs new file mode 100644 index 0000000..c9beb4f --- /dev/null +++ b/.ladle/config.mjs @@ -0,0 +1,4 @@ +/** @type {import('@ladle/react').UserConfig} */ +export default { + stories: "**/*.stories.{js,jsx,ts,tsx,mdx}", +}; diff --git a/.ladle/fonts/EuropaGroteskSH-Lig.otf b/.ladle/fonts/EuropaGroteskSH-Lig.otf new file mode 100644 index 0000000..d777259 Binary files /dev/null and b/.ladle/fonts/EuropaGroteskSH-Lig.otf differ diff --git a/.ladle/fonts/EuropaGroteskSH-Med.otf b/.ladle/fonts/EuropaGroteskSH-Med.otf new file mode 100644 index 0000000..394dac1 Binary files /dev/null and b/.ladle/fonts/EuropaGroteskSH-Med.otf differ diff --git a/.ladle/fonts/EuropaGroteskSH-Reg.otf b/.ladle/fonts/EuropaGroteskSH-Reg.otf new file mode 100644 index 0000000..d4a8d02 Binary files /dev/null and b/.ladle/fonts/EuropaGroteskSH-Reg.otf differ diff --git a/.ladle/styles.css b/.ladle/styles.css new file mode 100644 index 0000000..c0fbdad --- /dev/null +++ b/.ladle/styles.css @@ -0,0 +1,41 @@ +@font-face { + /* includes font-weights from 600-900 */ + font-family: "Europa"; + src: url("/fonts/EuropaGroteskSH-Med.otf"); + font-weight: 600 900; + font-style: normal; + font-display: swap; +} + +@font-face { + /* includes font-weights from 400-500 */ + font-family: "Europa"; + src: url("/fonts/EuropaGroteskSH-Reg.otf"); + font-weight: 400 500; + font-style: normal; + font-display: swap; +} + +@font-face { + /* includes font-weights from 100-300 */ + font-family: "Europa"; + src: url("/fonts/EuropaGroteskSH-Lig.otf"); + font-weight: 100 300; + font-style: normal; + font-display: swap; +} + +:root { + /* define custom css var for the Europa font */ + --font-europa-sans: "Europa", sans-serif; +} + +* { + /* apply letter-spacing to all html elements */ + @apply tracking-wider; +} + +.font-mono { + /* reset the `tracking-wider` on the monospace font */ + letter-spacing: initial; +} diff --git a/.npmignore b/.npmignore index aadad2d..8c1c460 100644 --- a/.npmignore +++ b/.npmignore @@ -9,4 +9,8 @@ tsconfig.tsbuildinfo *.spec.* vitest.config.ts setupTests.ts -coverage \ No newline at end of file +coverage +.ladle +tailwind.config.ts +postcss.config.cjs +stories diff --git a/README.md b/README.md index 2f59a1a..2cc51de 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ https://www.npmjs.com/package/@risc0/ui | Statements | Branches | Functions | Lines | | --------------------------- | ----------------------- | ------------------------- | ----------------- | -| ![Statements](https://img.shields.io/badge/statements-40.64%25-red.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-72.85%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-54.54%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-40.64%25-red.svg?style=flat) | +| ![Statements](https://img.shields.io/badge/statements-39.66%25-red.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-66.19%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-41.17%25-red.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-39.66%25-red.svg?style=flat) | diff --git a/libs/core.ts b/libs/core.ts deleted file mode 100644 index efd3683..0000000 --- a/libs/core.ts +++ /dev/null @@ -1,324 +0,0 @@ -/* c8 ignore start */ -/** - * This is the core package of t3-env. - * It contains the `createEnv` function that you can use to create your schema. - * @module - */ - -import type { InferOutput, ObjectSchema } from "valibot"; -import { flatten as vFlatten, object as vObject, safeParse as vSafeParse } from "valibot"; -import type { AnySchema } from "./utils"; - -/** @internal */ -export type ErrorMessage = T; - -/** @internal */ -export type Simplify = { - [P in keyof T]: T[P]; -} & {}; - -type Impossible> = Partial>; - -type UnReadonlyObject = T extends Readonly ? U : T; - -type Reduce[], TAcc> = TArr extends [] - ? TAcc - : TArr extends [infer Head, ...infer Tail] - ? Tail extends Record[] - ? Head & Reduce - : never - : never; - -/** - * The options that can be passed to the `createEnv` function. - */ -export interface BaseOptions, TExtends extends Record[]> { - /** - * How to determine whether the app is running on the server or the client. - * @default typeof window === "undefined" - */ - isServer?: boolean; - - /** - * Shared variables, often those that are provided by build tools and is available to both client and server, - * but isn't prefixed and doesn't require to be manually supplied. For example `NODE_ENV`, `VERCEL_URL` etc. - */ - shared?: TShared; - - /** - * Extend presets - */ - extends?: TExtends; - - /** - * Called when validation fails. By default the error is logged, - * and an error is thrown telling what environment variables are invalid. - */ - onValidationError?: (error) => never; - - /** - * Called when a server-side environment variable is accessed on the client. - * By default an error is thrown. - */ - onInvalidAccess?: (variable: string) => never; - - /** - * Whether to skip validation of environment variables. - * @default false - */ - skipValidation?: boolean; - - /** - * By default, this library will feed the environment variables directly to - * the Valibot validator. - * - * This means that if you have an empty string for a value that is supposed - * to be a number (e.g. `PORT=` in a ".env" file), Valibot will incorrectly flag - * it as a type mismatch violation. Additionally, if you have an empty string - * for a value that is supposed to be a string with a default value (e.g. - * `DOMAIN=` in an ".env" file), the default value will never be applied. - * - * In order to solve these issues, we recommend that all new projects - * explicitly specify this option as true. - */ - emptyStringAsUndefined?: boolean; -} - -/** - * Using this interface doesn't validate all environment variables are specified - * in the `runtimeEnv` object. You may want to use `StrictOptions` instead if - * your framework performs static analysis and tree-shakes unused variables. - */ -export interface LooseOptions, TExtends extends Record[]> - extends BaseOptions { - runtimeEnvStrict?: never; - - /** - * What object holds the environment variables at runtime. This is usually - * `process.env` or `import.meta.env`. - */ - // Unlike `runtimeEnvStrict`, this doesn't enforce that all environment variables are set. - runtimeEnv: Record; -} - -/** - * Using this interface validates all environment variables are specified - * in the `runtimeEnv` object. If you miss one, you'll get a type error. Useful - * if you want to make sure all environment variables are set for frameworks that - * perform static analysis and tree-shakes unused variables. - */ -export interface StrictOptions< - TPrefix extends string | undefined, - TServer extends Record, - TClient extends Record, - TShared extends Record, - TExtends extends Record[], -> extends BaseOptions { - /** - * Runtime Environment variables to use for validation - `process.env`, `import.meta.env` or similar. - * Enforces all environment variables to be set. Required in for example Next.js Edge and Client runtimes. - */ - runtimeEnvStrict: Record< - | { - [TKey in keyof TClient]: TPrefix extends undefined ? never : TKey extends `${TPrefix}${string}` ? TKey : never; - }[keyof TClient] - | { - [TKey in keyof TServer]: TPrefix extends undefined ? TKey : TKey extends `${TPrefix}${string}` ? never : TKey; - }[keyof TServer] - | { - [TKey in keyof TShared]: TKey extends string ? TKey : never; - }[keyof TShared], - string | boolean | number | undefined - >; - runtimeEnv?: never; -} - -/** - * This interface is used to define the client-side environment variables. - * It's used in conjunction with the `clientPrefix` option to ensure - * that all client-side variables are prefixed with the same string. - * Common examples of prefixes are `NEXT_PUBLIC_`, `NUXT_PUBLIC` or `PUBLIC_`. - */ -export interface ClientOptions> { - /** - * The prefix that client-side variables must have. This is enforced both at - * a type-level and at runtime. - */ - clientPrefix: TPrefix; - - /** - * Specify your client-side environment variables schema here. This way you can ensure the app isn't - * built with invalid env vars. - */ - client: Partial<{ - [TKey in keyof TClient]: TKey extends `${TPrefix}${string}` - ? TClient[TKey] - : ErrorMessage<`${TKey extends string ? TKey : never} is not prefixed with ${TPrefix}.`>; - }>; -} - -/** - * This interface is used to define the schema for your - * server-side environment variables. - */ -export interface ServerOptions> { - /** - * Specify your server-side environment variables schema here. This way you can ensure the app isn't - * built with invalid env vars. - */ - server: Partial<{ - [TKey in keyof TServer]: TPrefix extends undefined - ? TServer[TKey] - : TPrefix extends "" - ? TServer[TKey] - : TKey extends `${TPrefix}${string}` - ? ErrorMessage<`${TKey extends `${TPrefix}${string}` ? TKey : never} should not prefixed with ${TPrefix}.`> - : TServer[TKey]; - }>; -} - -export type ServerClientOptions< - TPrefix extends string | undefined, - TServer extends Record, - TClient extends Record, -> = - | (ClientOptions & ServerOptions) - | (ServerOptions & Impossible>) - | (ClientOptions & Impossible>); - -export type EnvOptions< - TPrefix extends string | undefined, - TServer extends Record, - TClient extends Record, - TShared extends Record, - TExtends extends Record[], -> = - | (LooseOptions & ServerClientOptions) - | (StrictOptions & ServerClientOptions); - -type TPrefixFormat = string | undefined; -type TServerFormat = Record; -type TClientFormat = Record; -type TSharedFormat = Record; -type TExtendsFormat = Record[]; - -/** - * Creates a new environment variable schema. - */ -export type CreateEnv< - TServer extends TServerFormat, - TClient extends TClientFormat, - TShared extends TSharedFormat, - TExtends extends TExtendsFormat, -> = Readonly< - Simplify< - InferOutput> & - InferOutput> & - InferOutput> & - UnReadonlyObject> - > ->; - -/** - * Create a new environment variable schema. - */ -export function createEnv< - TPrefix extends TPrefixFormat, - TServer extends TServerFormat = NonNullable, - TClient extends TClientFormat = NonNullable, - TShared extends TSharedFormat = NonNullable, - const TExtends extends TExtendsFormat = [], ->(opts: EnvOptions): CreateEnv { - const runtimeEnv = opts.runtimeEnvStrict ?? opts.runtimeEnv ?? process.env; - - const emptyStringAsUndefined = opts.emptyStringAsUndefined ?? false; - if (emptyStringAsUndefined) { - for (const [key, value] of Object.entries(runtimeEnv)) { - if (value === "") { - delete runtimeEnv[key]; - } - } - } - - const skip = !!opts.skipValidation; - - if (skip) { - return runtimeEnv as any; - } - - const _client = typeof opts.client === "object" ? opts.client : {}; - const _server = typeof opts.server === "object" ? opts.server : {}; - const _shared = typeof opts.shared === "object" ? opts.shared : {}; - const client = vObject(_client); - const server = vObject(_server); - const shared = vObject(_shared); - const isServer = opts.isServer ?? (typeof window === "undefined" || "Deno" in window); - - const allClient = vObject({ ...client.entries, ...shared.entries }); - const allServer = vObject({ ...server.entries, ...shared.entries, ...client.entries }); - const parsed = isServer - ? vSafeParse(allServer, runtimeEnv) // on server we can validate all env vars - : vSafeParse(allClient, runtimeEnv); // on client we can only validate the ones that are exposed - - const onValidationError = - opts.onValidationError ?? - ((error) => { - console.error("❌ Invalid environment variables:", vFlatten(error).nested); - throw new Error("Invalid environment variables"); - }); - - const onInvalidAccess = - opts.onInvalidAccess ?? - ((_variable: string) => { - throw new Error("❌ Attempted to access a server-side environment variable on the client"); - }); - - if (parsed.success === false) { - return onValidationError(parsed.issues); - } - - const isServerAccess = (prop: string) => { - if (!opts.clientPrefix) { - return true; - } - return !prop.startsWith(opts.clientPrefix) && !(prop in shared.entries); - }; - const isValidServerAccess = (prop: string) => { - return isServer || !isServerAccess(prop); - }; - const ignoreProp = (prop: string) => { - return prop === "__esModule" || prop === "$$typeof"; - }; - - const extendedObj = (opts.extends ?? []).reduce((acc, curr) => { - return Object.assign(acc, curr); - }, {}); - const fullObj = Object.assign(parsed.output, extendedObj); - - const env = new Proxy(fullObj, { - get(target, prop) { - if (typeof prop !== "string") { - return undefined; - } - if (ignoreProp(prop)) { - return undefined; - } - if (!isValidServerAccess(prop)) { - return onInvalidAccess(prop); - } - return Reflect.get(target, prop); - }, - // Maybe reconsider this in the future: - // https://github.com/t3-oss/t3-env/pull/111#issuecomment-1682931526 - // set(_target, prop) { - // // Readonly - this is the error message you get from assigning to a frozen object - // throw new Error( - // typeof prop === "string" - // ? `Cannot assign to read only property ${prop} of object #` - // : `Cannot assign to read only property of object #` - // ); - // }, - }); - - return env as any; -} diff --git a/libs/nextjs.ts b/libs/nextjs.ts deleted file mode 100644 index b46edaf..0000000 --- a/libs/nextjs.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* c8 ignore start */ -import { type CreateEnv, type ServerClientOptions, type StrictOptions, createEnv as createEnvCore } from "./core"; -import type { AnySchema } from "./utils"; - -const CLIENT_PREFIX = "NEXT_PUBLIC_" as const; -type ClientPrefix = typeof CLIENT_PREFIX; - -type Options< - TServer extends Record = NonNullable, - TClient extends Record<`${ClientPrefix}${string}`, AnySchema> = NonNullable, - TShared extends Record = NonNullable, - TExtends extends Record[] = [], -> = Omit< - StrictOptions & - ServerClientOptions, - "runtimeEnvStrict" | "runtimeEnv" | "clientPrefix" -> & - ( - | { - /** - * Manual destruction of `process.env`. Required for Next.js < 13.4.4. - */ - runtimeEnv: StrictOptions["runtimeEnvStrict"]; - experimental__runtimeEnv?: never; - } - | { - runtimeEnv?: never; - /** - * Can be used for Next.js ^13.4.4 since they stopped static analysis of server side `process.env`. - * Only client side `process.env` is statically analyzed and needs to be manually destructured. - */ - experimental__runtimeEnv: Record< - | { - [TKey in keyof TClient]: TKey extends `${ClientPrefix}${string}` ? TKey : never; - }[keyof TClient] - | { - [TKey in keyof TShared]: TKey extends string ? TKey : never; - }[keyof TShared], - string | boolean | number | undefined - >; - } - ); - -export function createNextjsEnv< - TServer extends Record = NonNullable, - TClient extends Record<`${ClientPrefix}${string}`, AnySchema> = NonNullable, - TShared extends Record = NonNullable, - const TExtends extends Record[] = [], ->(opts: Options): CreateEnv { - const client = typeof opts.client === "object" ? opts.client : {}; - const server = typeof opts.server === "object" ? opts.server : {}; - const shared = opts.shared; - - const runtimeEnv = opts.runtimeEnv - ? opts.runtimeEnv - : { - ...process.env, - ...opts.experimental__runtimeEnv, - }; - - return createEnvCore({ - ...opts, - shared, - client, - server, - clientPrefix: CLIENT_PREFIX, - runtimeEnv, - }); -} diff --git a/libs/presets.ts b/libs/presets.ts deleted file mode 100644 index 724c343..0000000 --- a/libs/presets.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* c8 ignore start */ -/** - * This contains presets for common environment variables used - * in 3rd party services so you don't have to write them yourself. - * Include them in your `createEnv.extends` option array. - * @module - */ - -import { optional as vOptional, picklist as vPicklist, string as vString } from "valibot"; -import { createEnv } from "./core"; - -/** - * Vercel System Environment Variables - * @see https://vercel.com/docs/projects/environment-variables/system-environment-variables#system-environment-variables - */ -export function vercel(): Readonly<{ - VERCEL?: string; - VERCEL_ENV?: "development" | "preview" | "production"; - VERCEL_URL?: string; - VERCEL_BRANCH_URL?: string; - VERCEL_REGION?: string; - VERCEL_AUTOMATION_BYPASS_SECRET?: string; - VERCEL_GIT_PROVIDER?: string; - VERCEL_GIT_REPO_SLUG?: string; - VERCEL_GIT_REPO_OWNER?: string; - VERCEL_GIT_REPO_ID?: string; - VERCEL_GIT_COMMIT_REF?: string; - VERCEL_GIT_COMMIT_SHA?: string; - VERCEL_GIT_COMMIT_MESSAGE?: string; - VERCEL_GIT_COMMIT_AUTHOR_LOGIN?: string; - VERCEL_GIT_COMMIT_AUTHOR_NAME?: string; - VERCEL_GIT_PREVIOUS_SHA?: string; - VERCEL_GIT_PULL_REQUEST_ID?: string; -}> { - return createEnv({ - server: { - VERCEL: vOptional(vString()), - VERCEL_ENV: vOptional(vPicklist(["development", "preview", "production"])), - VERCEL_URL: vOptional(vString()), - VERCEL_BRANCH_URL: vOptional(vString()), - VERCEL_REGION: vOptional(vString()), - VERCEL_AUTOMATION_BYPASS_SECRET: vOptional(vString()), - VERCEL_GIT_PROVIDER: vOptional(vString()), - VERCEL_GIT_REPO_SLUG: vOptional(vString()), - VERCEL_GIT_REPO_OWNER: vOptional(vString()), - VERCEL_GIT_REPO_ID: vOptional(vString()), - VERCEL_GIT_COMMIT_REF: vOptional(vString()), - VERCEL_GIT_COMMIT_SHA: vOptional(vString()), - VERCEL_GIT_COMMIT_MESSAGE: vOptional(vString()), - VERCEL_GIT_COMMIT_AUTHOR_LOGIN: vOptional(vString()), - VERCEL_GIT_COMMIT_AUTHOR_NAME: vOptional(vString()), - VERCEL_GIT_PREVIOUS_SHA: vOptional(vString()), - VERCEL_GIT_PULL_REQUEST_ID: vOptional(vString()), - }, - runtimeEnv: process.env, - }); -} diff --git a/libs/utils.ts b/libs/utils.ts deleted file mode 100644 index 26d1f88..0000000 --- a/libs/utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { GenericSchema } from "valibot"; - -/** @internal */ -export type AnySchema = GenericSchema; diff --git a/package.json b/package.json index b600910..77296bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@risc0/ui", - "version": "0.0.135", + "version": "0.0.139", "private": false, "sideEffects": false, "type": "module", @@ -11,7 +11,8 @@ "check:version": "bunx changelogen --bump --no-output", "pre-commit": "run-p check:*", "prepare": "npx husky", - "sort-package": "bunx sort-package-json 'package.json'" + "sort-package": "bunx sort-package-json 'package.json'", + "story": "ladle serve" }, "dependencies": { "@radix-ui/react-alert-dialog": "^1.1.1", @@ -36,6 +37,7 @@ "clsx": "2.1.1", "cmdk": "1.0.0", "lucide-react": "0.417.0", + "next": "15.0.0-canary.91", "next-themes": "0.3.0", "radash": "12.1.0", "react-hook-form": "7.52.1", @@ -46,21 +48,22 @@ "tailwind-merge": "2.4.0", "tailwindcss": "3.4.7", "tailwindcss-animate": "1.0.7", - "typescript": "5.6.0-dev.20240729", + "typescript": "5.6.0-dev.20240730", "vaul": "0.9.1" }, "devDependencies": { "@biomejs/biome": "1.8.4-nightly.a579bf7", + "@ladle/react": "4.1.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/react-hooks": "8.0.1", "@types/jest": "29.5.12", "@vitejs/plugin-react-swc": "3.7.0", "@vitest/coverage-v8": "2.0.4", + "deepmerge": "4.3.1", "happy-dom": "14.12.3", "istanbul-badges-readme": "1.9.0", - "npm-run-all": "4.1.5", - "vitest": "2.0.4" + "npm-run-all": "4.1.5" }, "peerDependencies": { "@types/react": "^18", diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..2a98071 --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1 @@ +module.exports = require("./config/postcss.config.base.cjs"); diff --git a/stories/alert.stories.tsx b/stories/alert.stories.tsx new file mode 100644 index 0000000..9f651ec --- /dev/null +++ b/stories/alert.stories.tsx @@ -0,0 +1,18 @@ +import { Alert, AlertDescription, AlertTitle } from "alert"; +import { RocketIcon } from "lucide-react"; + +export const Default = () => ( + + + Heads up! + You can add components to your app using the cli. + +); + +export const Destructive = () => ( + + + Heads up! + You can add components to your app using the cli. + +); diff --git a/stories/avatar.stories.tsx b/stories/avatar.stories.tsx new file mode 100644 index 0000000..cc38e85 --- /dev/null +++ b/stories/avatar.stories.tsx @@ -0,0 +1,8 @@ +import { Avatar, AvatarFallback, AvatarImage } from "avatar"; + +export const Default = () => ( + + + CN + +); diff --git a/stories/skeleton.stories.tsx b/stories/skeleton.stories.tsx new file mode 100644 index 0000000..77480db --- /dev/null +++ b/stories/skeleton.stories.tsx @@ -0,0 +1,11 @@ +import { Skeleton } from "skeleton"; + +export const Default = () => ( +
+ +
+ + +
+
+); diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..013a0eb --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,16 @@ +import deepmerge from "deepmerge"; +import type { Config } from "tailwindcss"; +import tailwindConfig from "./config/tailwind.config.base"; + +const config = deepmerge(tailwindConfig, { + theme: { + extend: { + fontFamily: { + sans: ["var(--font-europa-sans)", "system-ui"], + mono: ["var(--font-jetbrains-mono)"], + }, + }, + }, +}) satisfies Config; + +export default config; diff --git a/vitest.config.ts b/vitest.config.ts index 239795c..c1be9b2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,9 @@ +// @ts-nocheck + /// import react from "@vitejs/plugin-react-swc"; +import type { PluginOption } from "vite"; import { defineConfig } from "vitest/config"; export default defineConfig({