diff --git a/.changeset/humble-vans-make.md b/.changeset/humble-vans-make.md new file mode 100644 index 00000000000..3668f7a1f15 --- /dev/null +++ b/.changeset/humble-vans-make.md @@ -0,0 +1,6 @@ +--- +"@hashintel/ds-components": minor +"@hashintel/ds-helpers": minor +--- + +Expose preset-owned `tokens` and `semanticTokens` from `@hashintel/ds-components/tokens` in place of the old `./theme` export, and add generated `@hashintel/ds-helpers/recipes` exports for recipe consumers. diff --git a/libs/@hashintel/ds-components/.ladle/config.mjs b/libs/@hashintel/ds-components/.ladle/config.mjs index 8af35f6ba46..057c8344fc5 100644 --- a/libs/@hashintel/ds-components/.ladle/config.mjs +++ b/libs/@hashintel/ds-components/.ladle/config.mjs @@ -6,6 +6,7 @@ export default { viteConfig: "./vite.config.ts", outDir: ".build/ladle", envDir: ".", + storyOrder: ["primitives--*", "tokens--*", "legacy--*", "*"], addons: { rtl: { enabled: false }, a11y: { enabled: false }, diff --git a/libs/@hashintel/ds-components/AGENTS.md b/libs/@hashintel/ds-components/AGENTS.md index 2f6e110b470..5a353b8855e 100644 --- a/libs/@hashintel/ds-components/AGENTS.md +++ b/libs/@hashintel/ds-components/AGENTS.md @@ -9,7 +9,7 @@ It owns: - the Panda preset source in `src/preset/**` - token/codegen scripts in `scripts/**` - the component library in `src/components/**` -- the token/demo surface in `src/stories/**`, `.ladle/`, and `tests/**` +- the token/demo surface in `src/tokens/**`, `src/stories/Intro.mdx`, `.ladle/`, and `tests/**` It still consumes the generated runtime styling utilities from `@hashintel/ds-helpers`. @@ -36,7 +36,8 @@ Boundary rules: - `ds-components` generates `../ds-helpers/styled-system` via Panda `outdir`. - `ds-helpers` must not depend on `ds-components`. -- `@hashintel/ds-components/preset` and `@hashintel/ds-components/theme` are the canonical public preset/theme entrypoints. +- `@hashintel/ds-components/preset` is the canonical public styling entrypoint. +- `@hashintel/ds-components/tokens` is the public package-owned token export for `tokens` and `semanticTokens`. ## Panda CSS Configuration @@ -242,8 +243,9 @@ libs/@hashintel/ds-components/ ├── src/ │ ├── components/ │ ├── preset/ # Panda preset source of truth -│ ├── stories/ -│ ├── theme.ts # Public `./theme` facade +│ ├── stories/ # Shared docs such as Intro.mdx +│ ├── tokens/ # Token stories and fixtures +│ ├── tokens.ts # Public `./tokens` facade ├── scripts/ # Token/codegen scripts ├── tests/ # Snapshot/demo tests ├── panda.config.ts diff --git a/libs/@hashintel/ds-components/CONTRIBUTING.md b/libs/@hashintel/ds-components/CONTRIBUTING.md index 365d65fcf8c..e39a8090638 100644 --- a/libs/@hashintel/ds-components/CONTRIBUTING.md +++ b/libs/@hashintel/ds-components/CONTRIBUTING.md @@ -7,9 +7,9 @@ This guide covers the current post-FE-612 package layout for humans and coding a - `@hashintel/ds-components` is the source-owning package. - `src/preset/**` contains the Panda preset source and theme/token inputs used by the preset. - `scripts/**` owns token and color code generation. -- `src/components/**`, `src/stories/**`, `.ladle/**`, and `tests/**` are owned here as the component, demo, and snapshot surfaces. +- `src/components/**`, `src/tokens/**`, `src/stories/Intro.mdx`, `.ladle/**`, and `tests/**` are owned here as the component, demo, and snapshot surfaces. - `@hashintel/ds-helpers` is the generated Panda `styled-system` artifact only. -- The old `@hashintel/ds-theme` package has been retired; use `@hashintel/ds-components/preset` and `@hashintel/ds-components/theme` instead. +- The old `@hashintel/ds-theme` package has been retired; use `@hashintel/ds-components/preset` instead. For new internal work, treat `ds-components` as the only source of truth. @@ -19,9 +19,10 @@ For new internal work, treat `ds-components` as the only source of truth. | --- | --- | --- | | Components | `src/components//.tsx` and `src/components/*/*.stories.tsx` | Public component entrypoints live at the top of `src/components/` and are built by `tsdown`. | | Panda preset source | `src/preset.ts`, `src/preset/**` | This is the live preset consumed by `@hashintel/ds-components/preset`. | -| Package-owned theme facade | `src/theme.ts` | Re-exports from `src/preset/theme`. | +| Package-owned token facade | `src/tokens.ts`, `src/preset/tokens.ts` | This is the public `@hashintel/ds-components/tokens` surface and its internal source of truth. | | Token and color generators | `scripts/**` | Reads `scripts/figma-variables.json` and writes generated preset files under `src/preset/theme/**`. | -| Token demo stories | `src/stories/tokens/**` | Token reference and migration/demo stories live here now. | +| Token demo stories | `src/tokens/**` | Token reference and migration/demo stories live here now. | +| Intro docs | `src/stories/Intro.mdx` | Shared intro documentation for the Ladle surface. | | Local demo config | `panda.local.config.ts` | Shared Panda config for local demo surfaces such as Ladle. | | Ladle harness | `.ladle/**` | Used for the token/demo surface and Playwright snapshots. | | Snapshot tests | `tests/**` | Snapshot harness for the Ladle surface. | @@ -41,13 +42,19 @@ When you need token lookup helpers or public token types, use the generated runt import { token, type Token } from "@hashintel/ds-helpers/tokens"; ``` +When you need the package-owned Panda token objects that feed the preset, use: + +```ts +import { semanticTokens, tokens } from "@hashintel/ds-components/tokens"; +``` + When a consumer needs the Panda preset, use: ```ts import { preset, scopedThemeConfig } from "@hashintel/ds-components/preset"; ``` -Do not resurrect `@hashintel/ds-theme`; `ds-components` now owns the public preset and theme entrypoints directly. +Do not resurrect `@hashintel/ds-theme`; `ds-components` now owns the public preset entrypoint directly. ## Component Workflow @@ -171,7 +178,7 @@ Color tokens should come from the semantic palette already defined in the preset - `yarn dev` is the primary Ladle-based loop and the best default when changing token stories or the demo harness. It uses the shared Vite/PostCSS config for Panda style extraction while Ladle is running. - `yarn dev:lib` is the lightest loop when you only need the publishable package build watcher. -- `src/stories/tokens/**` owns the token reference stories that used to live under `ds-helpers`. +- `src/tokens/**` owns the token reference stories that used to live under `ds-helpers`. - `tests/snapshots.spec.ts` exercises the Ladle surface and stores the images in `tests/__snapshots__/`. ## Common Pitfalls diff --git a/libs/@hashintel/ds-components/README.md b/libs/@hashintel/ds-components/README.md index 010c2e932a6..89cf513ac11 100644 --- a/libs/@hashintel/ds-components/README.md +++ b/libs/@hashintel/ds-components/README.md @@ -1,6 +1,6 @@ # @hashintel/ds-components -React components for HASH's refractive design system, built with TypeScript, Ark UI, and PandaCSS. +React components for HASH's design system, built with TypeScript, Ark UI, and PandaCSS. ## Ownership Model @@ -8,17 +8,17 @@ As of the FE-612 ownership restructure: - `@hashintel/ds-components` owns the Panda preset source, token/codegen scripts, and demo surfaces. - `@hashintel/ds-helpers` is the generated Panda `styled-system` artifact. -- The old `@hashintel/ds-theme` surface has been folded into `@hashintel/ds-components/preset` and `@hashintel/ds-components/theme`. +- The old `@hashintel/ds-theme` surface has been folded into `@hashintel/ds-components/preset`. For new internal work, treat `ds-components` as the source of truth. ## Public Entry Points -| Entry point | Purpose | -| --- | --- | -| `@hashintel/ds-components` | Published component entrypoints from `src/components/*.tsx` | +| Entry point | Purpose | +| --------------------------------- | ------------------------------------------------------------------------------ | +| `@hashintel/ds-components` | Published component entrypoints from `src/components/*.tsx` | | `@hashintel/ds-components/preset` | Panda preset helpers such as `preset`, `createPreset`, and `scopedThemeConfig` | -| `@hashintel/ds-components/theme` | Package-owned theme facade re-exporting from `src/preset/theme` | +| `@hashintel/ds-components/tokens` | Package-owned Panda token objects such as `tokens` and `semanticTokens` | Component implementation still uses the generated Panda runtime from `@hashintel/ds-helpers`: @@ -27,20 +27,23 @@ import { css, cva, cx } from "@hashintel/ds-helpers/css"; import { Box, Flex, Stack } from "@hashintel/ds-helpers/jsx"; ``` -Token lookup helpers and token types should also come from `@hashintel/ds-helpers/tokens`. +Token lookup helpers and token types should still come from `@hashintel/ds-helpers/tokens`. +Use `@hashintel/ds-components/tokens` when you need the package-owned Panda token objects themselves. ## Package Layout -| Area | Location | -| --- | --- | -| Components | `src/components/**` | -| Panda preset source | `src/preset.ts`, `src/preset/**` | -| Token and color generators | `scripts/**` | -| Stories | `src/components/*/*.stories.tsx` | -| Token demo stories | `src/stories/**` | -| Local demo config | `panda.local.config.ts` | -| Ladle harness | `.ladle/**` | -| Snapshot tests | `tests/**` | +| Area | Location | +| -------------------------- | -------------------------------- | +| Components | `src/components/**` | +| Panda preset source | `src/preset.ts`, `src/preset/**` | +| Package token facade | `src/tokens.ts` | +| Token and color generators | `scripts/**` | +| Stories | `src/components/*/*.stories.tsx` | +| Token demo stories | `src/tokens/**` | +| Intro docs | `src/stories/Intro.mdx` | +| Local demo config | `panda.local.config.ts` | +| Ladle harness | `.ladle/**` | +| Snapshot tests | `tests/**` | ## Common Commands diff --git a/libs/@hashintel/ds-components/_docs/research/park-ui-porting-guide.md b/libs/@hashintel/ds-components/_docs/research/park-ui-porting-guide.md index f7ca30fe7f5..fdcbb6da29a 100644 --- a/libs/@hashintel/ds-components/_docs/research/park-ui-porting-guide.md +++ b/libs/@hashintel/ds-components/_docs/research/park-ui-porting-guide.md @@ -8,7 +8,7 @@ This guide was originally written before the FE-612 ownership restructure. Apply - preset and token source changes belong in `@hashintel/ds-components/src/preset/**` - token and color generation lives in `@hashintel/ds-components/scripts/**` -- token demo stories live in `@hashintel/ds-components/src/stories/tokens/**` +- token demo stories live in `@hashintel/ds-components/src/tokens/**` - `@hashintel/ds-helpers` is the generated Panda runtime only ## Reference Repositories @@ -119,7 +119,7 @@ Established mapping from Radix neutral → HASH gray: | 11 | 60 | Low-contrast text | | 12 | 90 | High-contrast text | -Visual comparison story: `libs/@hashintel/ds-components/src/stories/tokens/tokens.color-migration.story.tsx` +Visual comparison story: `libs/@hashintel/ds-components/src/tokens/tokens.color-migration.story.tsx` ### Alpha → Solid Mapping @@ -301,7 +301,7 @@ bg: 'bg.accent.bold.default' | File | Purpose | |------|---------| -| `libs/@hashintel/ds-components/src/stories/tokens/tokens.color-migration.story.tsx` | Visual comparison tool for scale mapping | +| `libs/@hashintel/ds-components/src/tokens/tokens.color-migration.story.tsx` | Visual comparison tool for scale mapping | | `libs/@hashintel/ds-components/_ai/park-ui-porting-guide.md` | This document | ## Next Steps diff --git a/libs/@hashintel/ds-components/eslint.config.js b/libs/@hashintel/ds-components/eslint.config.js index 94a6663ad71..cb4b0b3ee11 100644 --- a/libs/@hashintel/ds-components/eslint.config.js +++ b/libs/@hashintel/ds-components/eslint.config.js @@ -1,8 +1,10 @@ -// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format -import storybook from "eslint-plugin-storybook"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; import { createBase, disableRules } from "@local/eslint/deprecated"; +const disableTypeCheckedRules = + typescriptEslint.configs["disable-type-checked"]?.rules ?? {}; + export default [ { ignores: [ @@ -16,7 +18,6 @@ export default [ ], }, ...createBase(import.meta.dirname), - ...storybook.configs["flat/recommended"], ...disableRules([]), { rules: { @@ -44,13 +45,32 @@ export default [ }, }, { - files: ["src/**/*.story.ts{x,}", "src/stories/**/*.{ts,tsx,mdx}"], + // Story files are intentionally excluded from `tsconfig.json` (they live + // in `tsconfig.beta.json`). Running type-aware lint rules on them forces + // ESLint's project service into a slow default-project fallback per file + // *and* type-instantiates the very heavy generated `styled-system` types + // for every single story. Disable type-aware linting entirely for the + // story surface — they're demo code, not shipped to consumers. + files: [ + "src/**/*.story.ts{x,}", + "src/beta/**/*.stories.ts{x,}", + "src/tokens/**/*.{ts,tsx,mdx}", + ], + languageOptions: { + parserOptions: { + // Drop the type-aware project lookup for story files. Without this, + // every story still costs a full project-service resolve even when + // every type-aware rule is off. + projectService: false, + project: false, + }, + }, rules: { - "@typescript-eslint/no-unnecessary-condition": "off", + ...disableTypeCheckedRules, + "@typescript-eslint/no-shadow": "off", "id-length": "off", "import/no-extraneous-dependencies": ["error", { devDependencies: true }], - "storybook/default-exports": "off", - "storybook/no-redundant-story-name": "off", + "react/no-array-index-key": "off", }, }, { diff --git a/libs/@hashintel/ds-components/package.json b/libs/@hashintel/ds-components/package.json index 6f04e3b321b..77c9ef12357 100644 --- a/libs/@hashintel/ds-components/package.json +++ b/libs/@hashintel/ds-components/package.json @@ -26,9 +26,9 @@ "types": "./dist/preset.d.ts", "import": "./dist/preset.js" }, - "./theme": { - "types": "./dist/theme.d.ts", - "import": "./dist/theme.js" + "./tokens": { + "types": "./dist/tokens.d.ts", + "import": "./dist/tokens.js" }, "./panda.buildinfo.json": "./dist/panda.buildinfo.json", "./*": { @@ -47,27 +47,28 @@ "build": "npm-run-all build:lib build:buildinfo", "build:buildinfo": "panda ship --config panda.config.ts --outfile dist/panda.buildinfo.json", "build:ladle": "ladle build", - "build:lib": "tsdown", + "build:lib": "tsup --config tsup.config.ts", "codegen": "yarn run codegen:colors && yarn run codegen:tokens && yarn run codegen:panda", "codegen:colors": "tsx scripts/generate-colors-radix.ts", "codegen:panda": "panda codegen --clean", "codegen:tokens": "tsx scripts/generate-tokens.ts", "dev": "yarn dev:ladle", "dev:ladle": "ladle serve", - "dev:lib": "tsdown --watch", + "dev:lib": "tsup --config tsup.config.ts --watch", "fix": "npm-run-all --continue-on-error \"fix:*\"", - "fix:eslint": "eslint --fix .", + "fix:eslint": "eslint --cache --fix .", "fix:format": "biome format --write", "lint": "npm-run-all --continue-on-error \"lint:*\"", - "lint:eslint": "eslint --report-unused-disable-directives .", + "lint:eslint": "eslint --cache --report-unused-disable-directives .", "lint:tsc": "tsc --noEmit", + "lint:tsc:beta": "tsc --noEmit -p tsconfig.beta.json", "prepare": "yarn run codegen", "prepublishOnly": "turbo run build", "preview:ladle": "yarn build:ladle && ladle preview", "test:snapshots": "yarn build:ladle && playwright test", "test:snapshots:update": "yarn build:ladle && playwright test --update-snapshots", - "test:unit": "vitest run --exclude tests/snapshots.spec.ts", - "test:unit:watch": "vitest --exclude tests/snapshots.spec.ts" + "test:unit": "yarn build:buildinfo && vitest run --exclude tests/snapshots.spec.ts", + "test:unit:watch": "yarn build:buildinfo && vitest --exclude tests/snapshots.spec.ts" }, "dependencies": { "@ark-ui/react": "5.26.2", @@ -90,6 +91,7 @@ "@playwright/test": "1.58.2", "@radix-ui/colors": "3.0.0", "@svgr/rollup": "8.1.0", + "@tanstack/react-virtual": "3.13.24", "@tsconfig/strictest": "^2.0.8", "@types/node": "22.18.13", "@types/react": "19.2.7", @@ -100,12 +102,12 @@ "case-anything": "3.1.0", "colorjs.io": "0.6.1", "eslint": "9.39.4", - "eslint-plugin-storybook": "10.3.1", + "lorem-ipsum": "2.0.8", "lucide-react": "0.544.0", "npm-run-all": "4.1.5", "react": "19.2.3", "react-dom": "19.2.3", - "tsdown": "^0.21.4", + "tsup": "^8.5.1", "tsx": "4.20.6", "typescript": "5.9.3", "vite": "7.3.2", diff --git a/libs/@hashintel/ds-components/panda.config.ts b/libs/@hashintel/ds-components/panda.config.ts index 530fc37d920..c9cce304369 100644 --- a/libs/@hashintel/ds-components/panda.config.ts +++ b/libs/@hashintel/ds-components/panda.config.ts @@ -5,7 +5,7 @@ import { preset } from "./src/preset"; export const coreConfig: Config = { importMap: "@hashintel/ds-helpers", outdir: "../ds-helpers/styled-system", - include: ["./src/components/**/*.{ts,tsx}"], + include: ["./src/components/**/*.{ts,tsx}", "./src/beta/**/*.{ts,tsx}"], jsxFramework: "react", outExtension: "mjs", preflight: false, diff --git a/libs/@hashintel/ds-components/panda.local.config.ts b/libs/@hashintel/ds-components/panda.local.config.ts index 988d98eb743..f1de7a05407 100644 --- a/libs/@hashintel/ds-components/panda.local.config.ts +++ b/libs/@hashintel/ds-components/panda.local.config.ts @@ -6,8 +6,8 @@ export default defineConfig({ ...coreConfig, outdir: "../ds-helpers/styled-system", include: [ - "./src/components/**/*.{ts,tsx}", - "./src/stories/**/*.{ts,tsx}", + ...(coreConfig.include ?? []), + "./src/tokens/**/*.{ts,tsx}", "./.ladle/**/*.{ts,tsx}", ], staticCss: { diff --git a/libs/@hashintel/ds-components/scripts/generate-colors-radix.ts b/libs/@hashintel/ds-components/scripts/generate-colors-radix.ts index 0e2e90c5681..d21200003a8 100644 --- a/libs/@hashintel/ds-components/scripts/generate-colors-radix.ts +++ b/libs/@hashintel/ds-components/scripts/generate-colors-radix.ts @@ -12,9 +12,10 @@ import fs from "node:fs"; import { join } from "node:path"; import Color from "colorjs.io"; import * as radixColors from "@radix-ui/colors"; -import { withSemantics, type PaletteKind } from "../src/preset/theme/utils"; +import { withSemantics, type PaletteKind } from "../src/preset/tokens/utils"; -const OUTPUT_DIR = "src/preset/theme/colors"; +const OUTPUT_DIR = "src/preset/tokens/gen/colors"; +const BARREL_FILE_PATH = "src/preset/tokens/gen/colors.gen.ts"; /** * Colors to include in generation. Add/remove as needed. @@ -482,7 +483,7 @@ export const staticColors = { black, white }; * Aliases are composed in main.ts, not generated here. */ function writeBarrelFile(colorNames: string[]): void { - const filePath = join(process.cwd(), "src/preset/theme/colors.gen.ts"); + const filePath = join(process.cwd(), BARREL_FILE_PATH); const imports = colorNames .map((name) => `import { ${name} } from "./colors/${name}.gen";`) diff --git a/libs/@hashintel/ds-components/scripts/generate-tokens.ts b/libs/@hashintel/ds-components/scripts/generate-tokens.ts index 41c3e074f3b..dfb51eab5ea 100644 --- a/libs/@hashintel/ds-components/scripts/generate-tokens.ts +++ b/libs/@hashintel/ds-components/scripts/generate-tokens.ts @@ -11,7 +11,7 @@ import { transformLineHeightReference, } from "./transforms"; -const OUTPUT_DIR = "src/preset/theme/tokens"; +const OUTPUT_DIR = "src/preset/tokens/gen"; // ============================================================================ // SPACING TOKENS @@ -159,21 +159,6 @@ export const lineHeights = defineTokens.lineHeights(${formatTokensForOutput(line console.log("📄 Created typography.gen.ts"); } -// ============================================================================ -// BARREL FILE -// ============================================================================ - -function generateBarrelFile(): void { - const filePath = join(process.cwd(), "src/preset/theme/tokens.gen.ts"); - - const content = `export { spacing } from "./tokens/spacing.gen"; -export { fonts, fontWeights, fontSizes, lineHeights } from "./tokens/typography.gen"; -`; - - fs.writeFileSync(filePath, content, "utf8"); - console.log("📄 Created tokens.gen.ts (barrel file)"); -} - // ============================================================================ // TOP-LEVEL SCHEMA - Validates expected structure of Figma export // ============================================================================ @@ -208,9 +193,6 @@ function main(): void { console.log("\n📦 Typography tokens:"); generateTypographyTokens(); - console.log("\n📦 Barrel file:"); - generateBarrelFile(); - console.log("\n✅ Token generation complete!"); } diff --git a/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.recipe.ts b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.recipe.ts new file mode 100644 index 00000000000..f8a028e44f1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.recipe.ts @@ -0,0 +1,36 @@ +import { cva } from "@hashintel/ds-helpers/css"; + +export const absoluteCenter = cva({ + base: { + position: "absolute", + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + defaultVariants: { + axis: "both", + }, + variants: { + axis: { + horizontal: { + insetStart: "[50%]", + translate: "[-50%]", + _rtl: { + translate: "[50%]", + }, + }, + vertical: { + top: "[50%]", + translate: "[0 -50%]", + }, + both: { + insetStart: "[50%]", + top: "[50%]", + translate: "[-50% -50%]", + _rtl: { + translate: "[50% -50%]", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.stories.ts b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.stories.ts new file mode 100644 index 00000000000..6db5b0b9568 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.stories.ts @@ -0,0 +1,3 @@ +export default { title: "Primitives/AbsoluteCenter" }; + +export { App as basic } from "./basic.story"; diff --git a/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.tsx b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.tsx new file mode 100644 index 00000000000..78e83b51d0c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/absolute-center/absolute-center.tsx @@ -0,0 +1,8 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import type { ComponentProps } from "react"; + +import { absoluteCenter } from "./absolute-center.recipe"; + +export type AbsoluteCenterProps = ComponentProps; +export const AbsoluteCenter = styled(ark.div, absoluteCenter); diff --git a/libs/@hashintel/ds-components/src/beta/absolute-center/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/absolute-center/basic.story.tsx new file mode 100644 index 00000000000..f00921d9ea6 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/absolute-center/basic.story.tsx @@ -0,0 +1,15 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; + +import { AbsoluteCenter } from "./absolute-center"; + +export const App = () => { + return ( + + + + Centered Content + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/accordion/accordion.recipe.ts b/libs/@hashintel/ds-components/src/beta/accordion/accordion.recipe.ts new file mode 100644 index 00000000000..686f05b6b7a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/accordion/accordion.recipe.ts @@ -0,0 +1,90 @@ +import { accordionAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const accordion = defineSlotRecipe({ + className: "accordion", + slots: accordionAnatomy.extendWith("itemBody").keys(), + base: { + root: { + width: "full", + "--accordion-radius": "radii.l2", + }, + item: { + overflowAnchor: "none", + }, + itemTrigger: { + alignItems: "center", + borderRadius: "var(--accordion-radius)", + color: "fg.default", + cursor: "pointer", + display: "flex", + fontWeight: "semibold", + gap: "3", + justifyContent: "space-between", + textAlign: "start", + textStyle: "lg", + width: "full", + _focusVisible: { + outline: "2px solid", + outlineColor: "colorPalette.focusRing", + }, + _disabled: { + layerStyle: "disabled", + }, + }, + itemIndicator: { + transition: "rotate 0.2s", + transformOrigin: "center", + color: "fg.subtle", + _open: { + rotate: "180deg", + }, + _icon: { + width: "1.2em", + height: "1.2em", + }, + }, + itemBody: { + pb: "calc(var(--accordion-padding-y) * 2)", + color: "fg.muted", + }, + itemContent: { + overflow: "hidden", + borderRadius: "var(--accordion-radius)", + _open: { + animationName: "expand-height, fade-in", + animationDuration: "normal", + }, + _closed: { + animationName: "collapse-height, fade-out", + animationDuration: "normal", + }, + }, + }, + defaultVariants: { + size: "md", + variant: "outline", + }, + variants: { + variant: { + outline: { + item: { + borderBottomWidth: "1px", + }, + }, + plain: {}, + }, + size: { + md: { + root: { + "--accordion-padding-x": "spacing.4", + "--accordion-padding-y": "spacing.2.5", + }, + itemTrigger: { + textStyle: "md", + py: "var(--accordion-padding-y)", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/accordion/accordion.stories.ts b/libs/@hashintel/ds-components/src/beta/accordion/accordion.stories.ts new file mode 100644 index 00000000000..7846fee6a0a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/accordion/accordion.stories.ts @@ -0,0 +1,3 @@ +export default { title: "Primitives/Accordion" }; + +export { App as basic } from "./basic.story"; diff --git a/libs/@hashintel/ds-components/src/beta/accordion/accordion.tsx b/libs/@hashintel/ds-components/src/beta/accordion/accordion.tsx new file mode 100644 index 00000000000..6aceb4ffaaf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/accordion/accordion.tsx @@ -0,0 +1,29 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { Accordion } from "@ark-ui/react/accordion"; +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { accordion } from "@hashintel/ds-helpers/recipes"; +import { ChevronDownIcon } from "lucide-react"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(accordion); + +export type RootProps = ComponentProps; +export const Root = withProvider(Accordion.Root, "root"); +export const RootProvider = withProvider(Accordion.RootProvider, "root"); +export const Item = withContext(Accordion.Item, "item"); +export const ItemContent = withContext(Accordion.ItemContent, "itemContent"); +export const ItemIndicator = withContext( + Accordion.ItemIndicator, + "itemIndicator", + { + defaultProps: { children: }, + }, +); +export const ItemTrigger = withContext(Accordion.ItemTrigger, "itemTrigger"); +export const ItemBody = withContext(ark.div, "itemBody"); + +export { AccordionContext as Context } from "@ark-ui/react/accordion"; diff --git a/libs/@hashintel/ds-components/src/beta/accordion/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/accordion/basic.story.tsx new file mode 100644 index 00000000000..7fcf408f085 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/accordion/basic.story.tsx @@ -0,0 +1,34 @@ +import * as Accordion from "./accordion"; + +const faqItems = [ + { + question: "What is Park UI?", + answer: "A beautiful component library built with Ark UI and Panda CSS.", + }, + { + question: "How do I get started?", + answer: "Visit our documentation for installation and usage guides.", + }, + { + question: "Is it free to use?", + answer: "Yes! Park UI is completely free and open source.", + }, +]; + +export const App = () => { + return ( + + {faqItems.map((item) => ( + + + {item.question} + + + + {item.answer} + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/alert.recipe.ts b/libs/@hashintel/ds-components/src/beta/alert/alert.recipe.ts new file mode 100644 index 00000000000..a7df5c44b05 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/alert.recipe.ts @@ -0,0 +1,112 @@ +import { defineSlotRecipe } from "@pandacss/dev"; + +export const alert = defineSlotRecipe({ + className: "alert", + slots: ["root", "content", "description", "indicator", "title"], + base: { + root: { + alignItems: "flex-start", + borderRadius: "l3", + display: "flex", + position: "relative", + width: "full", + }, + content: { + display: "flex", + flex: "1", + flexDirection: "column", + gap: "1", + }, + description: { + display: "inline", + }, + indicator: { + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + flexShrink: "0", + }, + title: { + fontWeight: "semibold", + }, + }, + defaultVariants: { + size: "md", + status: "info", + variant: "subtle", + }, + variants: { + size: { + md: { + root: { + gap: "3", + p: "4", + textStyle: "sm", + }, + indicator: { + _icon: { + width: "5", + height: "5", + }, + }, + }, + lg: { + root: { + gap: "4", + p: "4", + textStyle: "md", + }, + indicator: { + _icon: { + width: "6", + height: "6", + }, + }, + }, + }, + variant: { + solid: { + root: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + }, + surface: { + root: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + }, + subtle: { + root: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + }, + outline: { + root: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + }, + }, + }, + status: { + info: { + root: { colorPalette: "blue" }, + }, + warning: { + root: { colorPalette: "orange" }, + }, + success: { + root: { colorPalette: "green" }, + }, + error: { + root: { colorPalette: "red" }, + }, + neutral: {}, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/alert/alert.stories.ts b/libs/@hashintel/ds-components/src/beta/alert/alert.stories.ts new file mode 100644 index 00000000000..884eb7b0603 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/alert.stories.ts @@ -0,0 +1,8 @@ +export default { title: "Primitives/Alert" }; + +export { App as basic } from "./basic.story"; +export { App as closable } from "./closable.story"; +export { App as description } from "./description.story"; +export { App as sizes } from "./sizes.story"; +export { App as status } from "./status.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/alert/alert.tsx b/libs/@hashintel/ds-components/src/beta/alert/alert.tsx new file mode 100644 index 00000000000..00d8a26b2e1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/alert.tsx @@ -0,0 +1,38 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { alert } from "@hashintel/ds-helpers/recipes"; +import { InfoIcon } from "lucide-react"; +import { type ComponentProps, forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(alert); + +export type RootProps = ComponentProps; +export const Root = withProvider(ark.div, "root"); +export const Title = withContext(ark.h3, "title"); +export const Description = withContext(ark.div, "description"); +export const Content = withContext(ark.div, "content"); + +type IndicatorProps = ComponentProps; +const StyledIndicator = withContext(ark.span, "indicator"); + +export const Indicator = forwardRef( + ({ children, ...props }, ref) => { + return ( + + {children ?? } + + ); + }, +); + +export const Alert = { + Root, + Title, + Description, + Content, + Indicator, +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/basic.story.tsx new file mode 100644 index 00000000000..5e75e2fc2c7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/basic.story.tsx @@ -0,0 +1,10 @@ +import { Alert } from "./alert"; + +export const App = () => { + return ( + + + This is a title + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/closable.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/closable.story.tsx new file mode 100644 index 00000000000..a57f0b510f7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/closable.story.tsx @@ -0,0 +1,21 @@ +import { CloseButton } from "../close-button/close-button"; +import { Alert } from "./alert"; + +export const App = () => { + return ( + + + + This is a title + This is a description + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/closed.story.tsx new file mode 100644 index 00000000000..1c3c6136c23 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/closed.story.tsx @@ -0,0 +1,28 @@ +import { forwardRef, type ReactElement, type ReactNode } from "react"; + +import * as StyledAlert from "./alert"; + +export interface AlertProps extends Omit { + startElement?: ReactNode; + endElement?: ReactNode; + title?: ReactNode; + icon?: ReactElement; +} + +export const Alert = forwardRef((props, ref) => { + const { title, children, icon, startElement, endElement, ...rest } = props; + return ( + + {startElement ?? {icon}} + {children ? ( + + {title} + {children} + + ) : ( + {title} + )} + {endElement} + + ); +}); diff --git a/libs/@hashintel/ds-components/src/beta/alert/description.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/description.story.tsx new file mode 100644 index 00000000000..a391f0a7832 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/description.story.tsx @@ -0,0 +1,13 @@ +import { Alert } from "./alert"; + +export const App = () => { + return ( + + + + This is a title + This is a description + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/sizes.story.tsx new file mode 100644 index 00000000000..9ff2e15a279 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/sizes.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Alert } from "./alert"; + +export const App = () => { + const sizes = ["md", "lg"] as const; + + return ( + + {sizes.map((size) => ( + + + + This is a title + This is a description + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/status.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/status.story.tsx new file mode 100644 index 00000000000..fdf35733f4d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/status.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Alert } from "./alert"; + +export const App = () => { + const statuses = ["neutral", "info", "warning", "error", "success"] as const; + + return ( + + {statuses.map((status) => ( + + + + This is a title + This is a description + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/alert/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/alert/variants.story.tsx new file mode 100644 index 00000000000..5011b2c1bf0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/alert/variants.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Alert } from "./alert"; + +export const App = () => { + const variants = ["solid", "surface", "subtle", "outline"] as const; + + return ( + + {variants.map((variant) => ( + + + + This is a title + This is a description + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/angle-slider/angle-slider.recipe.ts b/libs/@hashintel/ds-components/src/beta/angle-slider/angle-slider.recipe.ts new file mode 100644 index 00000000000..878972bc26c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/angle-slider/angle-slider.recipe.ts @@ -0,0 +1,8 @@ +import { angleSliderAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const angleSlider = defineSlotRecipe({ + className: "angleSlider", + slots: angleSliderAnatomy.keys(), + base: {}, +}); diff --git a/libs/@hashintel/ds-components/src/beta/avatar/avatar.recipe.ts b/libs/@hashintel/ds-components/src/beta/avatar/avatar.recipe.ts new file mode 100644 index 00000000000..159f6ab31d9 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/avatar.recipe.ts @@ -0,0 +1,152 @@ +import { avatarAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const avatar = defineSlotRecipe({ + className: "avatar", + slots: avatarAnatomy.keys(), + base: { + root: { + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + fontWeight: "medium", + position: "relative", + verticalAlign: "top", + flexShrink: "0", + userSelect: "none", + width: "var(--avatar-size)", + height: "var(--avatar-size)", + fontSize: "var(--avatar-font-size)", + borderRadius: "var(--avatar-radius)", + }, + fallback: { + lineHeight: "1", + textTransform: "uppercase", + fontWeight: "medium", + fontSize: "var(--avatar-font-size)", + borderRadius: "var(--avatar-radius)", + }, + image: { + width: "100%", + height: "100%", + objectFit: "cover", + borderRadius: "var(--avatar-radius)", + }, + }, + defaultVariants: { + size: "md", + shape: "full", + variant: "subtle", + }, + variants: { + size: { + full: { + root: { + "--avatar-size": "100%", + "--avatar-font-size": "100%", + }, + }, + "2xs": { + root: { + "--avatar-font-size": "fontSizes.2xs", + "--avatar-size": "sizes.6", + }, + fallback: { + _icon: { width: "3", height: "3" }, + }, + }, + xs: { + root: { + "--avatar-font-size": "fontSizes.xs", + "--avatar-size": "sizes.8", + }, + fallback: { + _icon: { width: "4", height: "4" }, + }, + }, + sm: { + root: { + "--avatar-font-size": "fontSizes.sm", + "--avatar-size": "sizes.9", + }, + fallback: { + _icon: { width: "4.5", height: "4.5" }, + }, + }, + md: { + root: { + "--avatar-font-size": "fontSizes.md", + "--avatar-size": "sizes.10", + }, + fallback: { + _icon: { width: "5", height: "5" }, + }, + }, + lg: { + root: { + "--avatar-font-size": "fontSizes.md", + "--avatar-size": "sizes.11", + }, + fallback: { + _icon: { width: "5.5", height: "5.5" }, + }, + }, + xl: { + root: { + "--avatar-font-size": "fontSizes.lg", + "--avatar-size": "sizes.12", + }, + fallback: { + _icon: { width: "6", height: "6" }, + }, + }, + "2xl": { + root: { + "--avatar-font-size": "fontSizes.xl", + "--avatar-size": "sizes.16", + }, + fallback: { + _icon: { width: "8", height: "8" }, + }, + }, + }, + variant: { + solid: { + root: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + }, + surface: { + root: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + }, + subtle: { + root: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + }, + outline: { + root: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + }, + }, + }, + shape: { + square: {}, + rounded: { + root: { "--avatar-radius": "radii.l3" }, + }, + full: { + root: { "--avatar-radius": "radii.full" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/avatar/avatar.stories.ts b/libs/@hashintel/ds-components/src/beta/avatar/avatar.stories.ts new file mode 100644 index 00000000000..3c9a6e039cf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/avatar.stories.ts @@ -0,0 +1,12 @@ +export default { title: "Primitives/Avatar" }; + +export { App as badge } from "./badge.story"; +export { App as basic } from "./basic.story"; +export { App as colors } from "./colors.story"; +export { App as fallback } from "./fallback.story"; +export { App as group } from "./group.story"; +export { App as persona } from "./persona.story"; +export { App as ring } from "./ring.story"; +export { App as shapes } from "./shapes.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/avatar.tsx b/libs/@hashintel/ds-components/src/beta/avatar/avatar.tsx new file mode 100644 index 00000000000..cc98cf0173a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/avatar.tsx @@ -0,0 +1,55 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies, @typescript-eslint/no-use-before-define, @typescript-eslint/prefer-nullish-coalescing */ + +import { Avatar } from "@ark-ui/react/avatar"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { avatar } from "@hashintel/ds-helpers/recipes"; +import { UserIcon } from "lucide-react"; +import { type ComponentProps, forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(avatar); + +export type RootProps = ComponentProps; +export const Root = withProvider(Avatar.Root, "root"); +export const RootProvider = withProvider(Avatar.RootProvider, "root"); +export const Image = withContext(Avatar.Image, "image", { + defaultProps: { + draggable: "false", + referrerPolicy: "no-referrer", + }, +}); + +export { AvatarContext as Context } from "@ark-ui/react/avatar"; + +export interface FallbackProps extends ComponentProps { + /** + * The name to derive the initials from. + * If not provided, the fallback will display a generic icon. + */ + name?: string | undefined; +} + +const StyledFallback = withContext(Avatar.Fallback, "fallback"); + +export const Fallback = forwardRef( + (props, ref) => { + const { name, children, asChild, ...rest } = props; + + const fallbackContent = + children || asChild ? children : name ? getInitials(name) : ; + + return ( + + {fallbackContent} + + ); + }, +); + +const getInitials = (name: string) => { + const names = name.trim().split(" "); + const firstName = names[0] || ""; + const lastName = names.length > 1 ? names[names.length - 1] : ""; + return firstName && lastName ? `${firstName[0]}${lastName[0]}` : firstName[0]; +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/badge.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/badge.story.tsx new file mode 100644 index 00000000000..07c9fddc770 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/badge.story.tsx @@ -0,0 +1,19 @@ +import { Circle, Float } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +export const App = () => { + return ( + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/basic.story.tsx new file mode 100644 index 00000000000..8c14604fa68 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/basic.story.tsx @@ -0,0 +1,10 @@ +import * as Avatar from "./avatar"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/closed.story.tsx new file mode 100644 index 00000000000..bb846333e21 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/closed.story.tsx @@ -0,0 +1,28 @@ +import { forwardRef, type ImgHTMLAttributes } from "react"; + +import * as StyledAvatar from "./avatar"; + +type ImageProps = ImgHTMLAttributes; + +export interface AvatarProps extends StyledAvatar.RootProps { + name?: string; + src?: string; + srcSet?: string; + loading?: ImageProps["loading"]; + icon?: React.ReactElement; + fallback?: React.ReactNode; +} + +export const Avatar = forwardRef((props, ref) => { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = + props; + return ( + + + {fallback ?? icon} + + + {children} + + ); +}); diff --git a/libs/@hashintel/ds-components/src/beta/avatar/colors.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/colors.story.tsx new file mode 100644 index 00000000000..9e3c0803934 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/colors.story.tsx @@ -0,0 +1,33 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +const colorPalette = [ + "red", + "blue", + "green", + "yellow", + "purple", + "orange", +] as const; + +const pickPalette = (name: string) => { + const index = name.charCodeAt(0) % colorPalette.length; + return colorPalette[index]; +}; + +export const App = () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/fallback.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/fallback.story.tsx new file mode 100644 index 00000000000..b96668deabc --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/fallback.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +export const App = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/group.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/group.story.tsx new file mode 100644 index 00000000000..1860912d623 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/group.story.tsx @@ -0,0 +1,72 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Group } from "../group/group"; +import * as Avatar from "./avatar"; + +const users = [ + { + name: "Christian Schröter", + src: "https://avatars.githubusercontent.com/u/1846056?v=4", + }, + { + name: "Segun Adebayo", + src: "https://avatars.githubusercontent.com/u/6916170?v=4", + }, + { + name: "Philipp Körner", + src: "https://avatars.githubusercontent.com/u/153984143?v=4", + }, +]; + +export const App = () => { + return ( + + + {users.map((user) => ( + + + + + ))} + + +3 + + + + + {users.map((user, index) => ( + + + + + ))} + + +3 + + + + + {users.map((user) => ( + + + + + ))} + + +3 + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/persona.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/persona.story.tsx new file mode 100644 index 00000000000..65d936443a9 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/persona.story.tsx @@ -0,0 +1,21 @@ +import { Box, HStack } from "@hashintel/ds-helpers/jsx"; + +import { Text } from "../text/text"; +import * as Avatar from "./avatar"; + +export const App = () => { + return ( + + + + + + + Christian Busch + + christian@park-ui.com + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/ring.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/ring.story.tsx new file mode 100644 index 00000000000..c5867d5f9ad --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/ring.story.tsx @@ -0,0 +1,14 @@ +import * as Avatar from "./avatar"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/shapes.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/shapes.story.tsx new file mode 100644 index 00000000000..cff7215a7b4 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/shapes.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +export const App = () => { + const shapes = ["square", "rounded", "full"] as const; + + return ( + + {shapes.map((shape) => ( + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/sizes.story.tsx new file mode 100644 index 00000000000..b19204e8f1e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/sizes.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg", "xl", "2xl"] as const; + + return ( + + {sizes.map((size) => ( + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/avatar/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/avatar/variants.story.tsx new file mode 100644 index 00000000000..417b36fa4fe --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/avatar/variants.story.tsx @@ -0,0 +1,17 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import * as Avatar from "./avatar"; + +export const App = () => { + const variants = ["solid", "surface", "subtle", "outline"] as const; + + return ( + + {variants.map((variant) => ( + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/badge/badge.recipe.ts b/libs/@hashintel/ds-components/src/beta/badge/badge.recipe.ts new file mode 100644 index 00000000000..5bd112e8e32 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/badge.recipe.ts @@ -0,0 +1,79 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const badge = defineRecipe({ + className: "badge", + base: { + display: "inline-flex", + alignItems: "center", + borderRadius: "l2", + lineHeight: "1", + fontWeight: "medium", + fontVariantNumeric: "tabular-nums", + whiteSpace: "nowrap", + userSelect: "none", + }, + defaultVariants: { + variant: "subtle", + size: "md", + }, + variants: { + variant: { + solid: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + surface: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + subtle: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + outline: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + }, + }, + size: { + sm: { + fontSize: "xs", + px: "1.5", + h: "4.5", + gap: "0.5", + _icon: { boxSize: "2.5" }, + }, + md: { + fontSize: "xs", + px: "2", + h: "5", + gap: "1", + _icon: { boxSize: "3" }, + }, + lg: { + fontSize: "xs", + px: "2.5", + h: "5.5", + gap: "1", + _icon: { boxSize: "3.5" }, + }, + xl: { + fontSize: "sm", + px: "2.5", + h: "6", + gap: "1.5", + _icon: { boxSize: "4" }, + }, + "2xl": { + fontSize: "md", + px: "3", + h: "7", + gap: "1.5", + _icon: { boxSize: "4.5" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/badge/badge.stories.ts b/libs/@hashintel/ds-components/src/beta/badge/badge.stories.ts new file mode 100644 index 00000000000..6c65363fe49 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/badge.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Badge" }; + +export { App as basic } from "./basic.story"; +export { App as icon } from "./icon.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/badge/badge.tsx b/libs/@hashintel/ds-components/src/beta/badge/badge.tsx new file mode 100644 index 00000000000..e97089fad76 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/badge.tsx @@ -0,0 +1,7 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { badge } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +export type BadgeProps = ComponentProps; +export const Badge = styled(ark.div, badge); diff --git a/libs/@hashintel/ds-components/src/beta/badge/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/badge/basic.story.tsx new file mode 100644 index 00000000000..de4a592d9af --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/basic.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Badge } from "./badge"; + +export const App = () => { + return ( + + Default + Success + Removed + New + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/badge/icon.story.tsx b/libs/@hashintel/ds-components/src/beta/badge/icon.story.tsx new file mode 100644 index 00000000000..d1922071722 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/icon.story.tsx @@ -0,0 +1,17 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; +import { StarIcon } from "lucide-react"; + +import { Badge } from "./badge"; + +export const App = () => { + return ( + + + New + + + New + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/badge/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/badge/sizes.story.tsx new file mode 100644 index 00000000000..9cf86db5d4e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/sizes.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; +import { StarIcon } from "lucide-react"; + +import { Badge } from "./badge"; + +export const App = () => { + const sizes = ["sm", "md", "lg", "xl"] as const; + return ( + + {sizes.map((size) => ( + + + Badge + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/badge/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/badge/variants.story.tsx new file mode 100644 index 00000000000..2ee2b584f62 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/badge/variants.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Badge } from "./badge"; + +export const App = () => { + return ( + + Solid + Surface + Subtle + Outline + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/basic.story.tsx new file mode 100644 index 00000000000..8aa798ff557 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/basic.story.tsx @@ -0,0 +1,19 @@ +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + return ( + + + + Docs + + + + Components + + + Breadcrumbs + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.recipe.ts b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.recipe.ts new file mode 100644 index 00000000000..abb8b8f5213 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.recipe.ts @@ -0,0 +1,77 @@ +import { defineSlotRecipe } from "@pandacss/dev"; + +export const breadcrumb = defineSlotRecipe({ + className: "breadcrumb", + slots: ["root", "list", "link", "item", "separator", "ellipsis"], + base: { + list: { + alignItems: "center", + display: "flex", + listStyle: "none", + wordBreak: "break-word", + }, + link: { + alignItems: "center", + borderRadius: "l1", + display: "inline-flex", + focusRing: "outside", + gap: "2", + outline: "0", + textDecoration: "none", + transition: "color", + _icon: { boxSize: "1em" }, + }, + item: { + display: "inline-flex", + alignItems: "center", + color: "fg.muted", + _last: { + color: "fg.default", + }, + }, + separator: { + color: "fg.subtle", + _icon: { boxSize: "1em" }, + _rtl: { rotate: "180deg" }, + }, + ellipsis: { + alignItems: "center", + color: "fg.muted", + display: "inline-flex", + justifyContent: "center", + _icon: { boxSize: "1em" }, + }, + }, + + variants: { + variant: { + underline: { + link: { + textDecoration: "underline", + textDecorationThickness: "0.1em", + textUnderlineOffset: "0.125em", + textDecorationColor: "fg.subtle", + _hover: { textDecorationColor: "fg.default" }, + }, + }, + plain: { + link: { + color: "fg.muted", + _hover: { color: "fg.default" }, + _currentPage: { color: "fg.default" }, + }, + }, + }, + size: { + xs: { list: { gap: "1", textStyle: "xs" } }, + sm: { list: { gap: "1", textStyle: "sm" } }, + md: { list: { gap: "1.5", textStyle: "md" } }, + lg: { list: { gap: "2", textStyle: "lg" } }, + }, + }, + + defaultVariants: { + variant: "plain", + size: "md", + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.stories.ts b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.stories.ts new file mode 100644 index 00000000000..549ac999f1a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.stories.ts @@ -0,0 +1,10 @@ +export default { title: "Primitives/Breadcrumb" }; + +export { App as basic } from "./basic.story"; +export { App as ellipsis } from "./ellipsis.story"; +export { App as icon } from "./icon.story"; +export { App as interactive } from "./interactive.story"; +export { App as menu } from "./menu.story"; +export { App as separator } from "./separator.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.tsx new file mode 100644 index 00000000000..3b210d0f6da --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/breadcrumb.tsx @@ -0,0 +1,34 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { breadcrumb } from "@hashintel/ds-helpers/recipes"; +import { ChevronRightIcon } from "lucide-react"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(breadcrumb); + +export type RootProps = ComponentProps; + +export const Root = withProvider(ark.nav, "root", { + defaultProps: { "aria-label": "breadcrumb" }, +}); +export const List = withContext(ark.ol, "list"); +export const Item = withContext(ark.li, "item"); +export const Link = withContext(ark.a, "link"); +export const Ellipsis = withContext(ark.li, "ellipsis", { + defaultProps: { + role: "presentation", + "aria-hidden": true, + children: "...", + }, +}); + +export const Separator = withContext(ark.li, "separator", { + defaultProps: { + "aria-hidden": true, + children: , + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/closed.story.tsx new file mode 100644 index 00000000000..56905513493 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/closed.story.tsx @@ -0,0 +1,42 @@ +import { forwardRef, Fragment, type ReactNode } from "react"; + +import * as StyledBreadcrumb from "./breadcrumb"; + +export interface BreadcrumbProps extends StyledBreadcrumb.RootProps { + items: Array<{ title: ReactNode; url?: string }>; +} + +export const Breadcrumb = forwardRef( + (props, ref) => { + const { items, ...rootProps } = props; + + return ( + + + {items.map((item, index) => { + const last = index === items.length - 1; + + return last ? ( + + {item.title} + + ) : ( + + + {item.url ? ( + + {item.title} + + ) : ( + item.title + )} + + + + ); + })} + + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/ellipsis.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/ellipsis.story.tsx new file mode 100644 index 00000000000..21e249aa238 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/ellipsis.story.tsx @@ -0,0 +1,21 @@ +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + return ( + + + + Docs + + + + Components + + + + + Breadcrumbs + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/icon.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/icon.story.tsx new file mode 100644 index 00000000000..86c3ddab145 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/icon.story.tsx @@ -0,0 +1,27 @@ +import { ComponentIcon, FileIcon } from "lucide-react"; + +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + return ( + + + + + + Docs + + + + + + + Components + + + + Breadcrumbs + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/interactive.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/interactive.story.tsx new file mode 100644 index 00000000000..46aa2ed533b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/interactive.story.tsx @@ -0,0 +1,21 @@ +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + return ( + + + + Docs + + + + Components + + + + Breadmcrumbs + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/menu.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/menu.story.tsx new file mode 100644 index 00000000000..ebb1d0156bb --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/menu.story.tsx @@ -0,0 +1,63 @@ +import { Portal } from "@ark-ui/react/portal"; +import { EllipsisIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import * as Menu from "../menu/menu"; +import * as Breadcrumb from "./breadcrumb"; + +interface BreadcrumbMenuItemProps { + children: React.ReactNode; + items: Array<{ label: string; value: string }>; +} + +const BreadcrumbMenu = (props: BreadcrumbMenuItemProps) => { + const { children, items } = props; + return ( + + {children} + + + + {items.map((item) => ( + + {item.label} + + ))} + + + + + ); +}; + +export const App = () => { + return ( + + + + Docs + + + + Components + + + + + + + + + + + Breadcrumb + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/separator.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/separator.story.tsx new file mode 100644 index 00000000000..099a6a1dc95 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/separator.story.tsx @@ -0,0 +1,23 @@ +import { DotIcon } from "lucide-react"; + +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + return ( + + + + Docs + + / + + Components + + + + + Breadcrumbs + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/sizes.story.tsx new file mode 100644 index 00000000000..e64fbeb0a93 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/sizes.story.tsx @@ -0,0 +1,26 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg"] as const; + return ( + + {sizes.map((size) => ( + + + + Docs + + + + Components + + + Breadcrumbs + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/breadcrumb/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/breadcrumb/variants.story.tsx new file mode 100644 index 00000000000..0f49515981c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/breadcrumb/variants.story.tsx @@ -0,0 +1,26 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Breadcrumb from "./breadcrumb"; + +export const App = () => { + const variants = ["plain", "underline"] as const; + return ( + + {variants.map((variant) => ( + + + + Docs + + + + Components + + + Breadcrumbs + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/as-link.story.tsx b/libs/@hashintel/ds-components/src/beta/button/as-link.story.tsx new file mode 100644 index 00000000000..e1df31e695a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/as-link.story.tsx @@ -0,0 +1,11 @@ +import { Button } from "./button"; + +export const App = () => { + return ( + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/attached.story.tsx b/libs/@hashintel/ds-components/src/beta/button/attached.story.tsx new file mode 100644 index 00000000000..01512fd69a0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/attached.story.tsx @@ -0,0 +1,15 @@ +import { ChevronDownIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Button, ButtonGroup } from "./button"; + +export const App = () => { + return ( + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/button/basic.story.tsx new file mode 100644 index 00000000000..0fba8b36bd9 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/basic.story.tsx @@ -0,0 +1,5 @@ +import { Button } from "./button"; + +export const App = () => { + return ; +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/button.recipe.ts b/libs/@hashintel/ds-components/src/beta/button/button.recipe.ts new file mode 100644 index 00000000000..24571915df5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/button.recipe.ts @@ -0,0 +1,152 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const button = defineRecipe({ + className: "button", + jsx: ["Button", "IconButton", "CloseButton", "ButtonGroup"], + base: { + alignItems: "center", + appearance: "none", + borderRadius: "l2", + cursor: "pointer", + display: "inline-flex", + flexShrink: "0", + fontWeight: "semibold", + gap: "2", + isolation: "isolate", + justifyContent: "center", + outline: "0", + position: "relative", + transition: "colors", + transitionProperty: "background-color, border-color, color, box-shadow", + userSelect: "none", + verticalAlign: "middle", + whiteSpace: "nowrap", + _icon: { + flexShrink: "0", + }, + _disabled: { + layerStyle: "disabled", + }, + focusVisibleRing: "outside", + }, + defaultVariants: { + variant: "solid", + size: "md", + }, + variants: { + variant: { + solid: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + _hover: { + bg: "colorPalette.solid.bg.hover", + }, + }, + surface: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + _hover: { + borderColor: "colorPalette.surface.border.hover", + }, + _active: { + bg: "colorPalette.surface.bg.active", + }, + _on: { + bg: "colorPalette.surface.bg.active", + }, + }, + subtle: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + _hover: { + bg: "colorPalette.subtle.bg.hover", + }, + _active: { + bg: "colorPalette.subtle.bg.active", + }, + _on: { + bg: "colorPalette.subtle.bg.active", + }, + }, + outline: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + _hover: { + bg: "colorPalette.outline.bg.hover", + }, + _active: { + bg: "colorPalette.outline.bg.active", + }, + _on: { + bg: "colorPalette.outline.bg.active", + }, + }, + plain: { + color: "colorPalette.plain.fg", + _hover: { + bg: "colorPalette.plain.bg.hover", + }, + _active: { + bg: "colorPalette.plain.bg.active", + }, + _on: { + bg: "colorPalette.plain.bg.active", + }, + }, + }, + size: { + "2xs": { + h: "6", + minW: "6", + textStyle: "xs", + px: "2", + _icon: { boxSize: "3.5" }, + }, + xs: { + h: "8", + minW: "8", + textStyle: "sm", + px: "2.5", + _icon: { boxSize: "4" }, + }, + sm: { + h: "9", + minW: "9", + textStyle: "sm", + px: "3", + _icon: { boxSize: "4" }, + }, + md: { + h: "10", + minW: "10", + textStyle: "sm", + px: "3.5", + _icon: { boxSize: "5" }, + }, + lg: { + h: "11", + minW: "11", + textStyle: "md", + px: "4", + _icon: { boxSize: "5" }, + }, + xl: { + h: "12", + minW: "12", + textStyle: "md", + px: "4.5", + _icon: { boxSize: "5.5" }, + }, + "2xl": { + h: "16", + minW: "16", + textStyle: "xl", + px: "6", + _icon: { boxSize: "6" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/button/button.stories.ts b/libs/@hashintel/ds-components/src/beta/button/button.stories.ts new file mode 100644 index 00000000000..dfd88f6710d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/button.stories.ts @@ -0,0 +1,13 @@ +export default { title: "Primitives/Button" }; + +export { App as asLink } from "./as-link.story"; +export { App as attached } from "./attached.story"; +export { App as basic } from "./basic.story"; +export { App as colors } from "./colors.story"; +export { App as disabled } from "./disabled.story"; +export { App as group } from "./group.story"; +export { App as icon } from "./icon.story"; +export { App as loading } from "./loading.story"; +export { App as ref } from "./ref.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/button/button.tsx b/libs/@hashintel/ds-components/src/beta/button/button.tsx new file mode 100644 index 00000000000..9f68412726e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/button.tsx @@ -0,0 +1,102 @@ +"use client"; + +/* eslint-disable @typescript-eslint/no-use-before-define, @typescript-eslint/prefer-nullish-coalescing */ + +import { ark } from "@ark-ui/react/factory"; +import { createContext, mergeProps } from "@ark-ui/react/utils"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { button, type ButtonVariantProps } from "@hashintel/ds-helpers/recipes"; +import { type ComponentProps, forwardRef, useMemo } from "react"; + +import { Group, type GroupProps } from "../group/group"; +import { Loader } from "../loader/loader"; + +interface ButtonLoadingProps { + /** + * If `true`, the button will show a loading spinner. + * @default false + */ + loading?: boolean | undefined; + /** + * The text to show while loading. + */ + loadingText?: React.ReactNode | undefined; + /** + * The spinner to show while loading. + */ + spinner?: React.ReactNode | undefined; + /** + * The placement of the spinner + * @default "start" + */ + spinnerPlacement?: "start" | "end" | undefined; +} + +type BaseButtonProps = ComponentProps; +const BaseButton = styled(ark.button, button); + +export interface ButtonProps extends BaseButtonProps, ButtonLoadingProps {} + +export const Button = forwardRef( + (props, ref) => { + const propsContext = useButtonPropsContext(); + const buttonProps = useMemo( + () => mergeProps(propsContext, props), + [propsContext, props], + ); + + const { + loading, + loadingText, + children, + spinner, + spinnerPlacement, + ...rest + } = buttonProps; + return ( + + {!props.asChild && loading ? ( + + {children} + + ) : ( + children + )} + + ); + }, +); + +export interface ButtonGroupProps extends GroupProps, ButtonVariantProps {} + +export const ButtonGroup = forwardRef( + (props, ref) => { + const [variantProps, otherProps] = useMemo( + () => button.splitVariantProps(props), + [props], + ); + return ( + + + + ); + }, +); + +const [ButtonPropsProvider, useButtonPropsContext] = + createContext({ + name: "ButtonPropsContext", + hookName: "useButtonPropsContext", + providerName: "", + strict: false, + }); diff --git a/libs/@hashintel/ds-components/src/beta/button/colors.story.tsx b/libs/@hashintel/ds-components/src/beta/button/colors.story.tsx new file mode 100644 index 00000000000..cd5eaef8b43 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/colors.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "./button"; + +export const App = () => { + return ( + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/disabled.story.tsx b/libs/@hashintel/ds-components/src/beta/button/disabled.story.tsx new file mode 100644 index 00000000000..96d4d81ae18 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/disabled.story.tsx @@ -0,0 +1,5 @@ +import { Button } from "./button"; + +export const App = () => { + return ; +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/group.story.tsx b/libs/@hashintel/ds-components/src/beta/button/group.story.tsx new file mode 100644 index 00000000000..2a2630fb882 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/group.story.tsx @@ -0,0 +1,10 @@ +import { Button, ButtonGroup } from "./button"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/icon.story.tsx b/libs/@hashintel/ds-components/src/beta/button/icon.story.tsx new file mode 100644 index 00000000000..451d90e8ae6 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/icon.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; +import { PhoneIcon, SendIcon } from "lucide-react"; + +import { Button } from "./button"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/loading.story.tsx b/libs/@hashintel/ds-components/src/beta/button/loading.story.tsx new file mode 100644 index 00000000000..b8cd4021392 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/loading.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "./button"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/ref.story.tsx b/libs/@hashintel/ds-components/src/beta/button/ref.story.tsx new file mode 100644 index 00000000000..cfe627ecfc1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/ref.story.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { useRef } from "react"; + +import { Button } from "./button"; + +export const App = () => { + const ref = useRef(null); + return ; +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/button/sizes.story.tsx new file mode 100644 index 00000000000..100348b0b14 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/sizes.story.tsx @@ -0,0 +1,18 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; +import { CircleDotIcon } from "lucide-react"; + +import { Button } from "./button"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg", "xl", "2xl"] as const; + + return ( + + {sizes.map((size) => ( + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/button/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/button/variants.story.tsx new file mode 100644 index 00000000000..63d35a873cc --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/button/variants.story.tsx @@ -0,0 +1,15 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "./button"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/avatar.story.tsx b/libs/@hashintel/ds-components/src/beta/card/avatar.story.tsx new file mode 100644 index 00000000000..363a519d2ad --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/avatar.story.tsx @@ -0,0 +1,46 @@ +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { CheckIcon, XIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Button } from "../button/button"; +import { Text } from "../text/text"; +import * as Card from "./card"; + +export const App = () => { + return ( + + + + + + + + + + Nate Foss + + + @natefoss + + + + + + + Nate Foss has requested to join your team. You can approve or decline + their request. + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/card/basic.story.tsx new file mode 100644 index 00000000000..11935f55ec7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/basic.story.tsx @@ -0,0 +1,24 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Card from "./card"; + +export const App = () => { + return ( + + + Title + Description + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/card.recipe.ts b/libs/@hashintel/ds-components/src/beta/card/card.recipe.ts new file mode 100644 index 00000000000..c7f660f0f58 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/card.recipe.ts @@ -0,0 +1,68 @@ +import { defineSlotRecipe } from "@pandacss/dev"; + +export const card = defineSlotRecipe({ + className: "card", + slots: ["root", "header", "body", "footer", "title", "description"], + base: { + root: { + borderRadius: "l3", + display: "flex", + flexDirection: "column", + overflow: "hidden", + position: "relative", + }, + header: { + display: "flex", + flexDirection: "column", + gap: "1", + p: "6", + }, + body: { + display: "flex", + flex: "1", + flexDirection: "column", + pb: "6", + px: "6", + }, + footer: { + display: "flex", + justifyContent: "flex-end", + gap: "3", + pb: "6", + pt: "2", + px: "6", + }, + title: { + textStyle: "lg", + fontWeight: "semibold", + }, + description: { + color: "fg.muted", + textStyle: "sm", + }, + }, + defaultVariants: { + variant: "outline", + }, + variants: { + variant: { + elevated: { + root: { + bg: "gray.surface.bg", + boxShadow: "lg", + }, + }, + outline: { + root: { + bg: "gray.surface.bg", + borderWidth: "1px", + }, + }, + subtle: { + root: { + bg: "gray.subtle.bg", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/card/card.stories.ts b/libs/@hashintel/ds-components/src/beta/card/card.stories.ts new file mode 100644 index 00000000000..de79646eb86 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/card.stories.ts @@ -0,0 +1,8 @@ +export default { title: "Primitives/Card" }; + +export { App as avatar } from "./avatar.story"; +export { App as basic } from "./basic.story"; +export { App as form } from "./form.story"; +export { App as horizontal } from "./horizontal.story"; +export { App as image } from "./image.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/card/card.tsx b/libs/@hashintel/ds-components/src/beta/card/card.tsx new file mode 100644 index 00000000000..d706ff8c833 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/card.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { card } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(card); + +export type RootProps = ComponentProps; +export const Root = withProvider(ark.div, "root"); +export const Header = withContext(ark.div, "header"); +export const Body = withContext(ark.div, "body"); +export const Footer = withContext(ark.div, "footer"); +export const Title = withContext(ark.h3, "title"); +export const Description = withContext(ark.div, "description"); diff --git a/libs/@hashintel/ds-components/src/beta/card/form.story.tsx b/libs/@hashintel/ds-components/src/beta/card/form.story.tsx new file mode 100644 index 00000000000..ca66f6d5745 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/form.story.tsx @@ -0,0 +1,54 @@ +import { Divider, HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { GithubIcon, GitlabIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import { Text } from "../text/text"; +import * as Card from "./card"; + +export const App = () => { + return ( + + + Sign Up + + Create an account and discover the worlds' best UI component + framework. + + + + + + + + + + + + or sign up with + + + + + E-Mail + + + + Password + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/horizontal.story.tsx b/libs/@hashintel/ds-components/src/beta/card/horizontal.story.tsx new file mode 100644 index 00000000000..4d9e8705590 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/horizontal.story.tsx @@ -0,0 +1,36 @@ +import { Box, Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Badge } from "../badge/badge"; +import { Button } from "../button/button"; +import { Image } from "../image/image"; +import * as Card from "./card"; + +export const App = () => { + return ( + + Caffe Latte + + + The perfect latte + + Caffè latte is a coffee beverage of Italian origin made with + espresso and steamed milk. + + + + + Hot + Caffeine + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/image.story.tsx b/libs/@hashintel/ds-components/src/beta/card/image.story.tsx new file mode 100644 index 00000000000..830a1b28dcf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/image.story.tsx @@ -0,0 +1,28 @@ +import { Button } from "../button/button"; +import { Image } from "../image/image"; +import * as Card from "./card"; + +export const App = () => { + return ( + + Green double couch with wooden legs + + Living room Sofa + + This sofa is perfect for modern tropical spaces, baroque inspired + spaces. + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/card/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/card/variants.story.tsx new file mode 100644 index 00000000000..0cfb884d841 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/card/variants.story.tsx @@ -0,0 +1,56 @@ +import { Box, Grid } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Card from "./card"; + +export const App = () => { + return ( + + + + Title + Description + + + + + + + + + + + + Title + Description + + + + + + + + + + + + Title + Description + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/auto-play.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/auto-play.story.tsx new file mode 100644 index 00000000000..1a4d00bba76 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/auto-play.story.tsx @@ -0,0 +1,43 @@ +import { Center } from "@hashintel/ds-helpers/jsx"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Text } from "../text/text"; +import * as Carousel from "./carousel"; + +export const App = () => { + const slides = 5; + + return ( + + + {Array.from({ length: slides }, (_, index) => ( + +
+ + {index + 1} + +
+
+ ))} +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/basic.story.tsx new file mode 100644 index 00000000000..12e02c4f0f7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/basic.story.tsx @@ -0,0 +1,43 @@ +import { Center } from "@hashintel/ds-helpers/jsx"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Text } from "../text/text"; +import * as Carousel from "./carousel"; + +export const App = () => { + const slides = 5; + + return ( + + + {Array.from({ length: slides }, (_, index) => ( + +
+ + {index + 1} + +
+
+ ))} +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/carousel.recipe.ts b/libs/@hashintel/ds-components/src/beta/carousel/carousel.recipe.ts new file mode 100644 index 00000000000..fb7c1351759 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/carousel.recipe.ts @@ -0,0 +1,75 @@ +import { carouselAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const carousel = defineSlotRecipe({ + className: "carousel", + slots: carouselAnatomy.keys(), + base: { + root: { + position: "relative", + display: "flex", + flexDirection: "column", + gap: "2", + _vertical: { + flexDirection: "row", + }, + }, + control: { + alignItems: "center", + justifyContent: "space-between", + borderRadius: "l2", + display: "flex", + _vertical: { + flexDirection: "column", + }, + }, + itemGroup: { + flex: "1", + }, + indicatorGroup: { + display: "flex", + _vertical: { + flexDirection: "column", + }, + }, + indicator: { + borderRadius: "full", + background: "gray.subtle.bg", + cursor: "pointer", + _current: { + background: "colorPalette.solid.bg", + }, + focusVisibleRing: "outside", + }, + }, + defaultVariants: { + size: "md", + }, + variants: { + inline: { + true: { + control: { + background: { _light: "white.a11", _dark: "black.a11" }, + bottom: "3", + left: "50%", + p: "1", + position: "absolute", + transform: "translateX(-50%)", + }, + }, + }, + size: { + md: { + control: { + gap: "3", + }, + indicatorGroup: { + gap: "3", + }, + indicator: { + boxSize: "2.5", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/carousel/carousel.stories.ts b/libs/@hashintel/ds-components/src/beta/carousel/carousel.stories.ts new file mode 100644 index 00000000000..e01eb384b9f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/carousel.stories.ts @@ -0,0 +1,8 @@ +export default { title: "Primitives/Carousel" }; + +export { App as autoPlay } from "./auto-play.story"; +export { App as basic } from "./basic.story"; +export { App as images } from "./images.story"; +export { App as multiple } from "./multiple.story"; +export { App as scrollTo } from "./scroll-to.story"; +export { App as vertical } from "./vertical.story"; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/carousel.tsx b/libs/@hashintel/ds-components/src/beta/carousel/carousel.tsx new file mode 100644 index 00000000000..10270ea2aae --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/carousel.tsx @@ -0,0 +1,48 @@ +"use client"; + +/* eslint-disable @typescript-eslint/no-shadow, react/no-array-index-key */ + +import { Carousel, useCarouselContext } from "@ark-ui/react/carousel"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { carousel } from "@hashintel/ds-helpers/recipes"; +import { type ComponentProps, forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(carousel); + +export type RootProps = ComponentProps; +export const Root = withProvider(Carousel.Root, "root", { + forwardProps: ["page"], + defaultProps: { spacing: "16px" }, +}); +export const RootProvider = withProvider(Carousel.RootProvider, "root"); +export const AutoplayTrigger = withContext( + Carousel.AutoplayTrigger, + "autoplayTrigger", +); +export const Control = withContext(Carousel.Control, "control"); +export const Indicator = withContext(Carousel.Indicator, "indicator"); +export const Item = withContext(Carousel.Item, "item"); +export const ItemGroup = withContext(Carousel.ItemGroup, "itemGroup"); +export const NextTrigger = withContext(Carousel.NextTrigger, "nextTrigger"); +export const PrevTrigger = withContext(Carousel.PrevTrigger, "prevTrigger"); + +const StyledIndicatorGroup = withContext( + Carousel.IndicatorGroup, + "indicatorGroup", +); +export const IndicatorGroup = forwardRef< + HTMLDivElement, + ComponentProps +>((props, ref) => { + const carousel = useCarouselContext(); + + return ( + + {carousel.pageSnapPoints.map((_, index) => ( + + ))} + + ); +}); + +export { CarouselContext as Context } from "@ark-ui/react/carousel"; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/images.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/images.story.tsx new file mode 100644 index 00000000000..31b76864959 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/images.story.tsx @@ -0,0 +1,40 @@ +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Image } from "../image/image"; +import * as Carousel from "./carousel"; + +const images = [ + "https://tinyurl.com/5b6ka8jd", + "https://tinyurl.com/7rmccdn5", + "https://tinyurl.com/59jxz9uu", + "https://tinyurl.com/6jurv23t", + "https://tinyurl.com/yp4rfum7", +]; + +export const App = () => { + return ( + + + {images.map((image, index) => ( + + + + ))} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/multiple.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/multiple.story.tsx new file mode 100644 index 00000000000..e120a1d5139 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/multiple.story.tsx @@ -0,0 +1,43 @@ +import { Center } from "@hashintel/ds-helpers/jsx"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Text } from "../text/text"; +import * as Carousel from "./carousel"; + +export const App = () => { + const slides = 10; + + return ( + + + {Array.from({ length: slides }, (_, index) => ( + +
+ + {index + 1} + +
+
+ ))} +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/scroll-to.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/scroll-to.story.tsx new file mode 100644 index 00000000000..f853430cc77 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/scroll-to.story.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useCarouselContext } from "@ark-ui/react/carousel"; +import { Center } from "@hashintel/ds-helpers/jsx"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import { IconButton } from "../icon-button/icon-button"; +import { Text } from "../text/text"; +import * as Carousel from "./carousel"; + +const ScrollToTrigger = () => { + const carousel = useCarouselContext(); + + return ( + + ); +}; + +export const App = () => { + const slides = 5; + + return ( + + + + {Array.from({ length: slides }, (_, index) => ( + +
+ + {index + 1} + +
+
+ ))} +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/carousel/vertical.story.tsx b/libs/@hashintel/ds-components/src/beta/carousel/vertical.story.tsx new file mode 100644 index 00000000000..b3bb5af972f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/carousel/vertical.story.tsx @@ -0,0 +1,48 @@ +import { Center } from "@hashintel/ds-helpers/jsx"; +import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; + +import { IconButton } from "../icon-button/icon-button"; +import { Text } from "../text/text"; +import * as Carousel from "./carousel"; + +export const App = () => { + const slides = 5; + + return ( + + + {Array.from({ length: slides }, (_, index) => ( + +
+ + {index + 1} + +
+
+ ))} +
+ + + + + + + + + + + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/basic.story.tsx new file mode 100644 index 00000000000..7e0e285e7b8 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/basic.story.tsx @@ -0,0 +1,13 @@ +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + + + + Label + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.recipe.ts b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.recipe.ts new file mode 100644 index 00000000000..82dc4ab132f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.recipe.ts @@ -0,0 +1,108 @@ +import { checkboxAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const checkbox = defineSlotRecipe({ + slots: checkboxAnatomy.keys(), + className: "checkbox", + base: { + root: { + display: "inline-flex", + gap: "2", + alignItems: "center", + verticalAlign: "top", + position: "relative", + _disabled: { + layerStyle: "disabled", + }, + }, + control: { + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + flexShrink: "0", + borderWidth: "1px", + borderColor: "transparent", + borderRadius: "l1", + cursor: "pointer", + focusVisibleRing: "outside", + + _icon: { + boxSize: "full", + }, + }, + label: { + fontWeight: "medium", + userSelect: "none", + }, + }, + + variants: { + size: { + sm: { + root: { gap: "2" }, + label: { textStyle: "sm" }, + control: { boxSize: "4.5", _icon: { boxSize: "3" } }, + }, + md: { + root: { gap: "3" }, + label: { textStyle: "md" }, + control: { boxSize: "5", _icon: { boxSize: "3.5" } }, + }, + lg: { + root: { gap: "3" }, + label: { textStyle: "lg" }, + control: { boxSize: "5.5", _icon: { boxSize: "4" } }, + }, + }, + + variant: { + solid: { + control: { + borderColor: "border", + _checked: { + bg: "colorPalette.solid.bg", + borderColor: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + _invalid: { + background: "error", + }, + }, + }, + surface: { + control: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + }, + subtle: { + control: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + }, + outline: { + control: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + _checked: { + borderColor: "colorPalette.solid.bg", + }, + }, + }, + plain: { + control: { + color: "colorPalette.plain.fg", + }, + }, + }, + }, + + defaultVariants: { + variant: "solid", + size: "md", + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.stories.ts b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.stories.ts new file mode 100644 index 00000000000..cb2d5849095 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.stories.ts @@ -0,0 +1,13 @@ +export default { title: "Primitives/Checkbox" }; + +export { App as basic } from "./basic.story"; +export { App as colors } from "./colors.story"; +export { App as controlled } from "./controlled.story"; +export { App as description } from "./description.story"; +export { App as icon } from "./icon.story"; +export { App as indeterminate } from "./indeterminate.story"; +export { App as label } from "./label.story"; +export { App as link } from "./link.story"; +export { App as sizes } from "./sizes.story"; +export { App as states } from "./states.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.tsx new file mode 100644 index 00000000000..b258eb88473 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/checkbox.tsx @@ -0,0 +1,58 @@ +"use client"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Checkbox, useCheckboxContext } from "@ark-ui/react/checkbox"; +import { createStyleContext, styled } from "@hashintel/ds-helpers/jsx"; +import { checkbox } from "@hashintel/ds-helpers/recipes"; +import type { HTMLStyledProps } from "@hashintel/ds-helpers/types"; +import { type ComponentProps, forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(checkbox); + +export type RootProps = ComponentProps; +export type HiddenInputProps = ComponentProps; + +export const Root = withProvider(Checkbox.Root, "root"); +export const RootProvider = withProvider(Checkbox.RootProvider, "root"); +export const Control = withContext(Checkbox.Control, "control"); +export const Group = withProvider(Checkbox.Group, "group"); +export const Label = withContext(Checkbox.Label, "label"); +export const HiddenInput = Checkbox.HiddenInput; + +export { + type CheckboxCheckedState as CheckedState, + CheckboxGroupProvider as GroupProvider, +} from "@ark-ui/react/checkbox"; + +// styled.svg is typed too broadly to satisfy strict TS in this context +// biome-ignore lint/suspicious/noExplicitAny: union type too complex for TS +const StyledSvg = styled.svg as React.ComponentType; + +export const Indicator = forwardRef>( + (props, ref) => { + const { indeterminate, checked } = useCheckboxContext(); + + return ( + + + Checkmark + {indeterminate ? ( + + ) : checked ? ( + + ) : null} + + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/closed.story.tsx new file mode 100644 index 00000000000..959f38fe8b0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/closed.story.tsx @@ -0,0 +1,30 @@ +import { VisuallyHidden } from "@hashintel/ds-helpers/jsx"; +import { forwardRef } from "react"; + +import * as StyledCheckbox from "./checkbox"; + +export type { CheckboxCheckedState } from "@ark-ui/react/checkbox"; + +export interface CheckboxProps extends StyledCheckbox.RootProps { + inputProps?: StyledCheckbox.HiddenInputProps; +} + +export const Checkbox = forwardRef( + (props, ref) => { + const { children, inputProps, ...rootProps } = props; + return ( + + + + + + {children && {children}} + {props["aria-label"] && ( + + {props["aria-label"]} + + )} + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/colors.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/colors.story.tsx new file mode 100644 index 00000000000..adbf805714a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/colors.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + const colors = ["blue", "green", "amber", "red"] as const; + + return ( + + {colors.map((color) => ( + + + + + + Label + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/controlled.story.tsx new file mode 100644 index 00000000000..5dfb990ff69 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/controlled.story.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useState } from "react"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + const [checked, setChecked] = useState(false); + + return ( + setChecked(e.checked)} + > + + + + + Label + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/description.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/description.story.tsx new file mode 100644 index 00000000000..335602296aa --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/description.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Text } from "../text/text"; +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + + + + + I agree to the terms and conditions + + By clicking this, you agree to our Terms and Privacy Policy. + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/icon.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/icon.story.tsx new file mode 100644 index 00000000000..60cf8d3b6c9 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/icon.story.tsx @@ -0,0 +1,17 @@ +import { PlusIcon } from "lucide-react"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + + + + + + Label + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/indeterminate.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/indeterminate.story.tsx new file mode 100644 index 00000000000..febadbd6899 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/indeterminate.story.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { Stack } from "@hashintel/ds-helpers/jsx"; +import { useState } from "react"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + const initialValues = [ + { label: "Monday", checked: false, value: "monday" }, + { label: "Tuesday", checked: false, value: "tuesday" }, + { label: "Wednesday", checked: false, value: "wednesday" }, + { label: "Thursday", checked: false, value: "thursday" }, + ]; + const [values, setValues] = useState(initialValues); + + const allChecked = values.every((value) => value.checked); + const indeterminate = values.some((value) => value.checked) && !allChecked; + + return ( + + { + setValues((current) => + current.map((value) => ({ ...value, checked: !!e.checked })), + ); + }} + > + + + + + Weekdays + + {values.map((item, index) => ( + + setValues((current) => { + const newValues = [...current]; + if (!newValues[index]) { + return newValues; + } + newValues[index] = { ...newValues[index], checked: !!e.checked }; + return newValues; + }) + } + > + + + + + {item.label} + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/label.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/label.story.tsx new file mode 100644 index 00000000000..de74a9c35a5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/label.story.tsx @@ -0,0 +1,13 @@ +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + Label + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/link.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/link.story.tsx new file mode 100644 index 00000000000..ba3eee51ae5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/link.story.tsx @@ -0,0 +1,19 @@ +import { Link } from "../link/link"; +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + + + + + I agree to the{" "} + + terms and conditions + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/sizes.story.tsx new file mode 100644 index 00000000000..944afb243f5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/sizes.story.tsx @@ -0,0 +1,20 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + const sizes = ["sm", "md", "lg"] as const; + return ( + + {sizes.map((size) => ( + + + + + + Label + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/states.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/states.story.tsx new file mode 100644 index 00000000000..4050fe5ae81 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/states.story.tsx @@ -0,0 +1,41 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + return ( + + + + + + + Disabled + + + + + + + + Disabled + + + + + + + + Readonly + + + + + + + + Invalid + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/checkbox/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/checkbox/variants.story.tsx new file mode 100644 index 00000000000..1b452fcbd15 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/checkbox/variants.story.tsx @@ -0,0 +1,20 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Checkbox from "./checkbox"; + +export const App = () => { + const variants = ["solid", "surface", "subtle", "outline", "plain"] as const; + return ( + + {variants.map((variant) => ( + + + + + + Label + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/clipboard/basic.story.tsx new file mode 100644 index 00000000000..24602880717 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/basic.story.tsx @@ -0,0 +1,14 @@ +import { IconButton } from "../icon-button/icon-button"; +import * as Clipboard from "./clipboard"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/button.story.tsx b/libs/@hashintel/ds-components/src/beta/clipboard/button.story.tsx new file mode 100644 index 00000000000..5eb81637412 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/button.story.tsx @@ -0,0 +1,15 @@ +import { Button } from "../button/button"; +import * as Clipboard from "./clipboard"; + +export const App = () => { + return ( + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.recipe.ts b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.recipe.ts new file mode 100644 index 00000000000..744835ebb25 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.recipe.ts @@ -0,0 +1,21 @@ +import { clipboardAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const clipboard = defineSlotRecipe({ + className: "clipboard", + slots: clipboardAnatomy.keys(), + base: { + root: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + gap: "1.5", + }, + label: { + fontWeight: "medium", + textStyle: "sm", + color: "fg.default", + gap: "0.5", + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.stories.ts b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.stories.ts new file mode 100644 index 00000000000..3ded9e6a1e6 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Clipboard" }; + +export { App as basic } from "./basic.story"; +export { App as button } from "./button.story"; +export { App as input } from "./input.story"; +export { App as timeout } from "./timeout.story"; diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.tsx b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.tsx new file mode 100644 index 00000000000..5fb0bfe8425 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/clipboard.tsx @@ -0,0 +1,45 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { Clipboard } from "@ark-ui/react/clipboard"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { clipboard } from "@hashintel/ds-helpers/recipes"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import { type ComponentProps, forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(clipboard); + +export type RootProps = ComponentProps; +export const Root = withProvider(Clipboard.Root, "root"); +export const RootProvider = withProvider(Clipboard.RootProvider, "root"); +export const Control = withContext(Clipboard.Control, "control"); +export const Input = withContext(Clipboard.Input, "input"); +export const Label = withContext(Clipboard.Label, "label"); +export const Trigger = withContext(Clipboard.Trigger, "trigger"); + +export { ClipboardContext as Context } from "@ark-ui/react/clipboard"; + +type IndicatorProps = ComponentProps; + +const StyledIndicator = withContext(Clipboard.Indicator, "indicator"); + +export const Indicator = forwardRef( + (props, ref) => { + return ( + } {...props}> + + + ); + }, +); + +export const CopyText = forwardRef( + (props, ref) => { + return ( + + Copy + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/input.story.tsx b/libs/@hashintel/ds-components/src/beta/clipboard/input.story.tsx new file mode 100644 index 00000000000..55d8b078304 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/input.story.tsx @@ -0,0 +1,27 @@ +import { IconButton } from "../icon-button/icon-button"; +import { Input } from "../input/input"; +import { InputGroup } from "../input-group/input-group"; +import * as Clipboard from "./clipboard"; + +const ClipboardIconButton = () => { + return ( + + + + + + ); +}; + +export const App = () => { + return ( + + Document Link + }> + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/clipboard/timeout.story.tsx b/libs/@hashintel/ds-components/src/beta/clipboard/timeout.story.tsx new file mode 100644 index 00000000000..1530872d499 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/clipboard/timeout.story.tsx @@ -0,0 +1,14 @@ +import { IconButton } from "../icon-button/icon-button"; +import * as Clipboard from "./clipboard"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/close-button/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/close-button/basic.story.tsx new file mode 100644 index 00000000000..789ec3e564e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/basic.story.tsx @@ -0,0 +1,5 @@ +import { CloseButton } from "./close-button"; + +export const App = () => { + return ; +}; diff --git a/libs/@hashintel/ds-components/src/beta/close-button/close-button.stories.ts b/libs/@hashintel/ds-components/src/beta/close-button/close-button.stories.ts new file mode 100644 index 00000000000..7736a1a0213 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/close-button.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/CloseButton" }; + +export { App as basic } from "./basic.story"; +export { App as customIcon } from "./custom-icon.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/close-button/close-button.tsx b/libs/@hashintel/ds-components/src/beta/close-button/close-button.tsx new file mode 100644 index 00000000000..707dcff2c39 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/close-button.tsx @@ -0,0 +1,28 @@ +/* eslint-disable import/no-extraneous-dependencies, @typescript-eslint/no-explicit-any */ + +import { XIcon } from "lucide-react"; +import { forwardRef } from "react"; + +import { IconButton, type IconButtonProps } from "../icon-button/icon-button"; + +export type CloseButtonProps = IconButtonProps; + +// IconButton produces a union type too complex for TS in this context +// biome-ignore lint/suspicious/noExplicitAny: union type too complex for TS +const TypedIconButton = IconButton as React.ComponentType; + +export const CloseButton = forwardRef( + (props, ref) => { + return ( + + {props.children ?? } + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/close-button/custom-icon.story.tsx b/libs/@hashintel/ds-components/src/beta/close-button/custom-icon.story.tsx new file mode 100644 index 00000000000..b02f0480313 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/custom-icon.story.tsx @@ -0,0 +1,11 @@ +import { XCircleIcon } from "lucide-react"; + +import { CloseButton } from "./close-button"; + +export const App = () => { + return ( + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/close-button/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/close-button/sizes.story.tsx new file mode 100644 index 00000000000..9f4bdd4c4a4 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/sizes.story.tsx @@ -0,0 +1,15 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { CloseButton } from "./close-button"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/close-button/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/close-button/variants.story.tsx new file mode 100644 index 00000000000..53b860def04 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/close-button/variants.story.tsx @@ -0,0 +1,15 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { CloseButton } from "./close-button"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/code/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/code/basic.story.tsx new file mode 100644 index 00000000000..aeda7ebb332 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/basic.story.tsx @@ -0,0 +1,5 @@ +import { Code } from "./code"; + +export const App = () => { + return {`console.log("Hello, world!")`}; +}; diff --git a/libs/@hashintel/ds-components/src/beta/code/code.recipe.ts b/libs/@hashintel/ds-components/src/beta/code/code.recipe.ts new file mode 100644 index 00000000000..e9dc22b644f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/code.recipe.ts @@ -0,0 +1,51 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const code = defineRecipe({ + className: "code", + base: { + alignItems: "center", + borderRadius: "l2", + display: "inline-flex", + fontVariantNumeric: "tabular-nums", + fontWeight: "medium", + fontFamily: "code", + gap: "1", + lineHeight: "1", + }, + defaultVariants: { + size: "md", + variant: "subtle", + }, + variants: { + variant: { + solid: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + surface: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + subtle: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + outline: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + }, + plain: { + color: "colorPalette.plain.fg", + }, + }, + size: { + sm: { textStyle: "xs", height: "4.5", minWidth: "4.5", px: "1" }, + md: { textStyle: "sm", height: "5", minWidth: "5", px: "1" }, + lg: { textStyle: "sm", height: "5.5", minWidth: "5.5", px: "1" }, + xl: { textStyle: "md", height: "6", minWidth: "6", px: "1" }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/code/code.stories.ts b/libs/@hashintel/ds-components/src/beta/code/code.stories.ts new file mode 100644 index 00000000000..be66a9d749f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/code.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Code" }; + +export { App as basic } from "./basic.story"; +export { App as colors } from "./colors.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/code/code.tsx b/libs/@hashintel/ds-components/src/beta/code/code.tsx new file mode 100644 index 00000000000..4bf4aef336a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/code.tsx @@ -0,0 +1,7 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { code } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +export type CodeProps = ComponentProps; +export const Code = styled(ark.code, code); diff --git a/libs/@hashintel/ds-components/src/beta/code/colors.story.tsx b/libs/@hashintel/ds-components/src/beta/code/colors.story.tsx new file mode 100644 index 00000000000..a64f2e6aaef --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/colors.story.tsx @@ -0,0 +1,27 @@ +import { Stack, Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Code } from "./code"; + +export const App = () => { + const colors = ["blue", "green", "amber", "red"] as const; + return ( + + {colors.map((colorPalette) => ( + + + console.log() + + + console.log() + + + console.log() + + + console.log() + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/code/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/code/sizes.story.tsx new file mode 100644 index 00000000000..e30dac0ce62 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/sizes.story.tsx @@ -0,0 +1,16 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Code } from "./code"; + +export const App = () => { + const sizes = ["sm", "md", "lg", "xl"] as const; + return ( + + {sizes.map((size) => ( + + console.log() + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/code/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/code/variants.story.tsx new file mode 100644 index 00000000000..77c3092d494 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/code/variants.story.tsx @@ -0,0 +1,16 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Code } from "./code"; + +export const App = () => { + const variants = ["solid", "surface", "outline", "subtle", "plain"] as const; + return ( + + {variants.map((variant) => ( + + console.log() + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/collapsible/basic.story.tsx new file mode 100644 index 00000000000..05e97178238 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/basic.story.tsx @@ -0,0 +1,21 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Collapsible from "./collapsible"; + +export const App = () => { + return ( + + + + + + + Park UI beautifully-designed, accessible components system and code + distribution platform. Built with Panda CSS and supporting a wide + range of JS frameworks + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.recipe.ts b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.recipe.ts new file mode 100644 index 00000000000..5a741a93528 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.recipe.ts @@ -0,0 +1,20 @@ +import { collapsibleAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const collapsible = defineSlotRecipe({ + className: "collapsible", + slots: collapsibleAnatomy.keys(), + base: { + content: { + overflow: "hidden", + _open: { + animationName: "expand-height, fade-in", + animationDuration: "slow", + }, + _closed: { + animationName: "collapse-height, fade-out", + animationDuration: "normal", + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.stories.ts b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.stories.ts new file mode 100644 index 00000000000..aa85cc9b55d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.stories.ts @@ -0,0 +1,5 @@ +export default { title: "Primitives/Collapsible" }; + +export { App as basic } from "./basic.story"; +export { App as lazyMount } from "./lazy-mount.story"; +export { App as unmountOnExit } from "./unmount-on-exit.story"; diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.tsx b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.tsx new file mode 100644 index 00000000000..9720b299aae --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/collapsible.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { Collapsible } from "@ark-ui/react/collapsible"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { collapsible } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(collapsible); + +export type RootProps = ComponentProps; +export const Root = withProvider(Collapsible.Root, "root"); +export const RootProvider = withProvider(Collapsible.RootProvider, "root"); +export const Content = withContext(Collapsible.Content, "content"); +export const Indicator = withContext(Collapsible.Indicator, "indicator"); +export const Trigger = withContext(Collapsible.Trigger, "trigger"); + +export { CollapsibleContext as Context } from "@ark-ui/react/collapsible"; diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/lazy-mount.story.tsx b/libs/@hashintel/ds-components/src/beta/collapsible/lazy-mount.story.tsx new file mode 100644 index 00000000000..de4e0bd9478 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/lazy-mount.story.tsx @@ -0,0 +1,21 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Collapsible from "./collapsible"; + +export const App = () => { + return ( + + + + + + + If you inspect the DOM, you'll notice that the content is unmounted + when collapsed. This is useful for performance reasons when you have a + lot of collapsible content. + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/collapsible/unmount-on-exit.story.tsx b/libs/@hashintel/ds-components/src/beta/collapsible/unmount-on-exit.story.tsx new file mode 100644 index 00000000000..630b0bf4a70 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/collapsible/unmount-on-exit.story.tsx @@ -0,0 +1,22 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Collapsible from "./collapsible"; + +export const App = () => { + return ( + + + + + + + If you inspect the DOM, you'll notice that the content will be + initially mounted and unmounted when collapsed. This is useful for + improving performance and preventing background processes (like timers + or API calls) from running when the content is hidden. + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/color-picker/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/color-picker/basic.story.tsx new file mode 100644 index 00000000000..ee048e3e3c8 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/color-picker/basic.story.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { parseColor } from "@ark-ui/react/color-picker"; + +import * as ColorPicker from "./color-picker"; + +export const App = () => { + return ( + + Color + + + + + + + + + + + + + Toggle ColorFormat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pick color + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.recipe.ts b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.recipe.ts new file mode 100644 index 00000000000..7626b832330 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.recipe.ts @@ -0,0 +1,89 @@ +import { colorPickerAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const colorPicker = defineSlotRecipe({ + className: "color-picker", + slots: colorPickerAnatomy.keys(), + base: { + root: { + display: "flex", + flexDirection: "column", + gap: "1.5", + }, + label: { + color: "fg.default", + fontWeight: "medium", + textStyle: "sm", + }, + control: { + display: "flex", + flexDirection: "row", + gap: "2", + }, + content: { + background: "gray.surface.bg", + borderRadius: "l3", + boxShadow: "lg", + display: "flex", + flexDirection: "column", + maxWidth: "sm", + p: "4", + zIndex: "dropdown", + _open: { + animation: "fadeIn 0.25s ease-out", + }, + _closed: { + animation: "fadeOut 0.2s ease-out", + }, + _hidden: { + display: "none", + }, + }, + area: { + height: "36", + borderRadius: "l2", + overflow: "hidden", + }, + areaThumb: { + borderRadius: "full", + height: "2.5", + width: "2.5", + boxShadow: "white 0px 0px 0px 2px, black 0px 0px 2px 1px", + outline: "none", + }, + areaBackground: { + height: "full", + }, + channelSlider: { + borderRadius: "l2", + }, + channelSliderTrack: { + height: "3", + borderRadius: "l2", + }, + swatchGroup: { + display: "grid", + gridTemplateColumns: "repeat(7, 1fr)", + gap: "2", + background: "gray.surface.bg", + }, + swatch: { + height: "6", + width: "6", + borderRadius: "l2", + boxShadow: + "0 0 0 1px var(--colors-border-emphasized), 0 0 0 2px var(--colors-bg-default) inset", + }, + channelSliderThumb: { + borderRadius: "full", + height: "2.5", + width: "2.5", + boxShadow: "white 0px 0px 0px 2px, black 0px 0px 2px 1px", + transform: "translate(-50%, -50%)", + outline: "none", + }, + transparencyGrid: { + borderRadius: "l2", + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.stories.ts b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.stories.ts new file mode 100644 index 00000000000..c1db8654a19 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.stories.ts @@ -0,0 +1,3 @@ +export default { title: "Primitives/ColorPicker" }; + +export { App as basic } from "./basic.story"; diff --git a/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.tsx b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.tsx new file mode 100644 index 00000000000..4481e2fbc78 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/color-picker/color-picker.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { ColorPicker } from "@ark-ui/react/color-picker"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { colorPicker } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(colorPicker); + +export type RootProps = ComponentProps; +export const Root = withProvider(ColorPicker.Root, "root"); +export const RootProvider = withProvider(ColorPicker.RootProvider, "root"); +export const Area = withContext(ColorPicker.Area, "area"); +export const AreaBackground = withContext( + ColorPicker.AreaBackground, + "areaBackground", +); +export const AreaThumb = withContext(ColorPicker.AreaThumb, "areaThumb"); +export const ChannelInput = withContext( + ColorPicker.ChannelInput, + "channelInput", +); +export const ChannelSlider = withContext( + ColorPicker.ChannelSlider, + "channelSlider", +); +export const ChannelSliderLabel = withContext( + ColorPicker.ChannelSliderLabel, + "channelSliderLabel", +); +export const ChannelSliderThumb = withContext( + ColorPicker.ChannelSliderThumb, + "channelSliderThumb", +); +export const ChannelSliderTrack = withContext( + ColorPicker.ChannelSliderTrack, + "channelSliderTrack", +); +export const ChannelSliderValueText = withContext( + ColorPicker.ChannelSliderValueText, + "channelSliderValueText", +); +export const Content = withContext(ColorPicker.Content, "content"); +export const Control = withContext(ColorPicker.Control, "control"); +export const EyeDropperTrigger = withContext( + ColorPicker.EyeDropperTrigger, + "eyeDropperTrigger", +); +export const FormatSelect = withContext( + ColorPicker.FormatSelect, + "formatSelect", +); +export const FormatTrigger = withContext( + ColorPicker.FormatTrigger, + "formatTrigger", +); +export const HiddenInput = ColorPicker.HiddenInput; +export const Label = withContext(ColorPicker.Label, "label"); +export const Positioner = withContext(ColorPicker.Positioner, "positioner"); +export const Swatch = withContext(ColorPicker.Swatch, "swatch"); +export const SwatchGroup = withContext(ColorPicker.SwatchGroup, "swatchGroup"); +export const SwatchIndicator = withContext( + ColorPicker.SwatchIndicator, + "swatchIndicator", +); +export const SwatchTrigger = withContext( + ColorPicker.SwatchTrigger, + "swatchTrigger", +); +export const TransparencyGrid = withContext( + ColorPicker.TransparencyGrid, + "transparencyGrid", +); +export const Trigger = withContext(ColorPicker.Trigger, "trigger"); +export const ValueSwatch = ColorPicker.ValueSwatch; +export const ValueText = withContext(ColorPicker.ValueText, "valueText"); +export const View = withContext(ColorPicker.View, "view"); + +export { ColorPickerContext as Context } from "@ark-ui/react/color-picker"; diff --git a/libs/@hashintel/ds-components/src/beta/combobox/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/combobox/basic.story.tsx new file mode 100644 index 00000000000..91bc4fb9089 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/basic.story.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useListCollection } from "@ark-ui/react/collection"; +import { useFilter } from "@ark-ui/react/locale"; +import { Portal } from "@ark-ui/react/portal"; + +import * as Combobox from "./combobox"; + +const frameworks = [ + { label: "React", value: "react" }, + { label: "Solid", value: "solid" }, + { label: "Vue", value: "vue" }, + { label: "Angular", value: "angular" }, + { label: "Svelte", value: "svelte" }, + { label: "Preact", value: "preact" }, + { label: "Qwik", value: "qwik" }, + { label: "Lit", value: "lit" }, + { label: "Alpine.js", value: "alpinejs" }, + { label: "Ember", value: "ember" }, + { label: "Next.js", value: "nextjs" }, +]; + +export const App = () => { + const { contains } = useFilter({ sensitivity: "base" }); + + const { collection, filter } = useListCollection({ + initialItems: frameworks, + filter: contains, + }); + + return ( + filter(e.inputValue)} + > + Framework + + + + + + + + + + + No items found + {collection.items.map((item) => ( + + {item.label} + + + ))} + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/combobox/combobox.recipe.ts b/libs/@hashintel/ds-components/src/beta/combobox/combobox.recipe.ts new file mode 100644 index 00000000000..82dec950341 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/combobox.recipe.ts @@ -0,0 +1,191 @@ +import { comboboxAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +import { input } from "../input/input.recipe"; + +export const combobox = defineSlotRecipe({ + className: "combobox", + slots: comboboxAnatomy.extendWith("indicatorGroup").keys(), + base: { + root: { + display: "flex", + flexDirection: "column", + gap: "1.5", + width: "full", + }, + label: { + textStyle: "label", + }, + input: { + ...input.base, + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + control: { + position: "relative", + }, + content: { + background: "gray.surface.bg", + borderRadius: "l2", + boxShadow: "md", + display: "flex", + flexDirection: "column", + maxH: "min(var(--available-height), {sizes.96})", + minWidth: "max(var(--reference-width), {sizes.40})", + outline: "0", + overflowY: "auto", + zIndex: "dropdown", + _open: { + animationStyle: "slide-fade-in", + animationDuration: "slow", + }, + _closed: { + animationStyle: "slide-fade-out", + animationDuration: "fastest", + }, + "&[data-empty]:not(:has([data-scope=combobox][data-part=empty]))": { + opacity: 0, + }, + }, + item: { + alignItems: "center", + borderRadius: "l1", + cursor: "pointer", + display: "flex", + justifyContent: "space-between", + _hover: { + background: "gray.surface.bg.hover", + }, + _highlighted: { + background: "gray.surface.bg.hover", + }, + _selected: {}, + _disabled: { + layerStyle: "disabled", + }, + }, + itemGroup: { + display: "flex", + flexDirection: "column", + }, + itemGroupLabel: { + alignItems: "flex-start", + color: "fg.subtle", + display: "flex", + flexDirection: "column", + fontWeight: "medium", + gap: "1px", + justifyContent: "center", + _after: { + content: '""', + width: "100%", + height: "1px", + bg: "border", + }, + }, + itemIndicator: { + color: "colorPalette.plain.fg", + }, + indicatorGroup: { + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "1", + pos: "absolute", + insetEnd: "0", + top: "0", + bottom: "0", + }, + trigger: { + color: "fg.subtle", + }, + clearTrigger: { + color: "fg.muted", + }, + empty: { + display: "flex", + alignItems: "center", + color: "fg.subtle", + }, + }, + defaultVariants: { + size: "md", + variant: "outline", + }, + variants: { + variant: { + outline: { + input: input.variants.variant.outline, + }, + surface: { + input: input.variants.variant.surface, + }, + subtle: { + input: input.variants.variant.subtle, + }, + }, + size: { + xs: { + input: { + ...input.variants.size.xs, + pe: "12", + }, + content: { p: "1", gap: "0.5", textStyle: "sm" }, + item: { px: "1", minH: "8", gap: "2", _icon: { boxSize: "3.5" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "1", height: "8" }, + indicatorGroup: { px: "2", _icon: { boxSize: "3.5" } }, + empty: { px: "1", minH: "8" }, + }, + sm: { + input: { + ...input.variants.size.sm, + pe: "14", + }, + content: { p: "1", gap: "0.5", textStyle: "sm" }, + item: { px: "1.5", minH: "9", gap: "2", _icon: { boxSize: "4" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "1.5", height: "9" }, + indicatorGroup: { px: "2.5", _icon: { boxSize: "4" } }, + empty: { px: "1.5", minH: "9" }, + }, + md: { + input: { + ...input.variants.size.md, + pe: "14", + }, + content: { p: "1", gap: "0.5", textStyle: "md" }, + indicatorGroup: { px: "3", _icon: { boxSize: "4" } }, + item: { px: "2", minH: "10", gap: "2", _icon: { boxSize: "4" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "2", height: "10" }, + empty: { px: "2", minH: "10" }, + }, + lg: { + input: { + ...input.variants.size.lg, + pe: "16", + }, + content: { p: "1", gap: "0.5", textStyle: "md" }, + item: { px: "2.5", minH: "11", gap: "2", _icon: { boxSize: "4.5" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "2.5", height: "11" }, + indicatorGroup: { px: "3.5", _icon: { boxSize: "4.5" } }, + empty: { px: "2.5", minH: "11" }, + }, + xl: { + input: { + ...input.variants.size.xl, + pe: "16", + }, + content: { p: "1", gap: "1", textStyle: "lg" }, + item: { px: "3", minH: "12", gap: "3", _icon: { boxSize: "5" } }, + itemGroup: { gap: "1" }, + itemGroupLabel: { px: "3", height: "12" }, + indicatorGroup: { px: "4", _icon: { boxSize: "5" } }, + empty: { px: "3", minH: "12" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/combobox/combobox.stories.ts b/libs/@hashintel/ds-components/src/beta/combobox/combobox.stories.ts new file mode 100644 index 00000000000..c55b3d3f48a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/combobox.stories.ts @@ -0,0 +1,5 @@ +export default { title: "Primitives/Combobox" }; + +export { App as basic } from "./basic.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/combobox/combobox.tsx b/libs/@hashintel/ds-components/src/beta/combobox/combobox.tsx new file mode 100644 index 00000000000..a0a8952ba3a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/combobox.tsx @@ -0,0 +1,72 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { Combobox, useComboboxItemContext } from "@ark-ui/react/combobox"; +import { ark } from "@ark-ui/react/factory"; +import { + createStyleContext, + type HTMLStyledProps, +} from "@hashintel/ds-helpers/jsx"; +import { + combobox, + type ComboboxVariantProps, +} from "@hashintel/ds-helpers/recipes"; +import { CheckIcon, ChevronsUpDownIcon, XIcon } from "lucide-react"; +import { forwardRef } from "react"; + +const { withProvider, withContext } = createStyleContext(combobox); + +export type RootProps = HTMLStyledProps<"div"> & ComboboxVariantProps; + +export const Root = withProvider(Combobox.Root, "root", { + defaultProps: { positioning: { sameWidth: false } }, +}) as Combobox.RootComponent; + +export const RootProvider = withProvider( + Combobox.RootProvider, + "root", +) as Combobox.RootProviderComponent; + +export const ClearTrigger = withContext(Combobox.ClearTrigger, "clearTrigger", { + defaultProps: { children: }, +}); +export const Content = withContext(Combobox.Content, "content"); +export const Control = withContext(Combobox.Control, "control"); +export const Empty = withContext(Combobox.Empty, "empty"); +export const IndicatorGroup = withContext(ark.div, "indicatorGroup"); +export const Input = withContext(Combobox.Input, "input"); +export const Item = withContext(Combobox.Item, "item"); +export const ItemGroup = withContext(Combobox.ItemGroup, "itemGroup"); +export const ItemGroupLabel = withContext( + Combobox.ItemGroupLabel, + "itemGroupLabel", +); +export const ItemText = withContext(Combobox.ItemText, "itemText"); +export const Label = withContext(Combobox.Label, "label"); +export const List = withContext(Combobox.List, "list"); +export const Positioner = withContext(Combobox.Positioner, "positioner"); +export const Trigger = withContext(Combobox.Trigger, "trigger", { + defaultProps: { children: }, +}); + +export { ComboboxContext as Context } from "@ark-ui/react/combobox"; + +const StyledItemIndicator = withContext( + Combobox.ItemIndicator, + "itemIndicator", +); + +export const ItemIndicator = forwardRef>( + (props, ref) => { + const item = useComboboxItemContext(); + + return item.selected ? ( + + + + ) : ( + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/combobox/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/combobox/sizes.story.tsx new file mode 100644 index 00000000000..b3ca24a0fd5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/sizes.story.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useListCollection } from "@ark-ui/react/collection"; +import { useFilter } from "@ark-ui/react/locale"; +import { Portal } from "@ark-ui/react/portal"; +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Combobox from "./combobox"; + +const frameworks = [ + { label: "React", value: "react" }, + { label: "Solid", value: "solid" }, + { label: "Vue", value: "vue" }, + { label: "Angular", value: "angular" }, + { label: "Svelte", value: "svelte" }, + { label: "Preact", value: "preact" }, + { label: "Qwik", value: "qwik" }, + { label: "Lit", value: "lit" }, + { label: "Alpine.js", value: "alpinejs" }, + { label: "Ember", value: "ember" }, + { label: "Next.js", value: "nextjs" }, +]; + +export const App = () => { + const { contains } = useFilter({ sensitivity: "base" }); + + const { collection, filter } = useListCollection({ + initialItems: frameworks, + filter: contains, + }); + + const sizes = ["xs", "sm", "md", "lg", "xl"] as const; + + return ( + + {sizes.map((size) => ( + filter(e.inputValue)} + key={size} + size={size} + > + Framework + + + + + + + + + + + No items found + {collection.items.map((item) => ( + + {item.label} + + + ))} + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/combobox/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/combobox/variants.story.tsx new file mode 100644 index 00000000000..378e1369c53 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/combobox/variants.story.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useListCollection } from "@ark-ui/react/collection"; +import { useFilter } from "@ark-ui/react/locale"; +import { Portal } from "@ark-ui/react/portal"; +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Combobox from "./combobox"; + +const frameworks = [ + { label: "React", value: "react" }, + { label: "Solid", value: "solid" }, + { label: "Vue", value: "vue" }, + { label: "Angular", value: "angular" }, + { label: "Svelte", value: "svelte" }, + { label: "Preact", value: "preact" }, + { label: "Qwik", value: "qwik" }, + { label: "Lit", value: "lit" }, + { label: "Alpine.js", value: "alpinejs" }, + { label: "Ember", value: "ember" }, + { label: "Next.js", value: "nextjs" }, +]; + +export const App = () => { + const { contains } = useFilter({ sensitivity: "base" }); + + const { collection, filter } = useListCollection({ + initialItems: frameworks, + filter: contains, + }); + + const variants = ["outline", "subtle", "surface"] as const; + + return ( + + {variants.map((variant) => ( + filter(e.inputValue)} + > + Framework + + + + + + + + + + + No items found + {collection.items.map((item) => ( + + {item.label} + + + ))} + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/date-picker/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/date-picker/basic.story.tsx new file mode 100644 index 00000000000..b6381288e31 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/date-picker/basic.story.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; + +import * as DatePicker from "./date-picker"; + +export const App = () => { + return ( + + Select Date + + + 📅 + Clear + + + + + + + {(datePicker) => ( + <> + + + + + + + + + + + {datePicker.weekDays.map((weekDay, id) => ( + + {weekDay.short} + + ))} + + + + {datePicker.weeks.map((week, id) => ( + + {week.map((day, id) => ( + + + {day.day} + + + ))} + + ))} + + + + )} + + + + + {(datePicker) => ( + <> + + + + + + + + + + {datePicker + .getMonthsGrid({ columns: 4, format: "short" }) + .map((months, id: number) => ( + + {months.map((month, id: number) => ( + + + {month.label} + + + ))} + + ))} + + + + )} + + + + + {(datePicker) => ( + <> + + + + + + + + + + {datePicker + .getYearsGrid({ columns: 4 }) + .map((years, id: number) => ( + + {years.map((year, id: number) => ( + + + {year.label} + + + ))} + + ))} + + + + )} + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.recipe.ts b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.recipe.ts new file mode 100644 index 00000000000..d798141f676 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.recipe.ts @@ -0,0 +1,91 @@ +import { datePickerAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const datePicker = defineSlotRecipe({ + className: "date-picker", + slots: datePickerAnatomy.keys(), + base: { + root: { + display: "flex", + flexDirection: "column", + gap: "1.5", + }, + content: { + background: "gray.surface.bg", + borderRadius: "l3", + boxShadow: "lg", + display: "flex", + flexDirection: "column", + gap: "3", + p: "4", + width: "344px", + zIndex: "dropdown", + _open: { + animation: "fadeIn 0.25s ease-out", + }, + _closed: { + animation: "fadeOut 0.2s ease-out", + }, + _hidden: { + display: "none", + }, + }, + control: { + display: "flex", + flexDirection: "row", + gap: "2", + }, + label: { + color: "fg.default", + fontWeight: "medium", + textStyle: "sm", + }, + tableHeader: { + color: "fg.muted", + fontWeight: "semibold", + height: "10", + textStyle: "sm", + }, + viewControl: { + display: "flex", + gap: "2", + justifyContent: "space-between", + }, + table: { + width: "full", + borderCollapse: "separate", + borderSpacing: "1", + m: "-1", + }, + tableCell: { + textAlign: "center", + }, + tableCellTrigger: { + width: "100%", + _today: { + _before: { + content: "'−'", + color: "colorPalette.solid", + position: "absolute", + marginTop: "6", + }, + }, + "&[data-in-range]": { + background: "gray.subtle.bg", + }, + _selected: { + _before: { + color: "colorPalette.contrast", + }, + }, + }, + view: { + display: "flex", + flexDirection: "column", + gap: "3", + _hidden: { + display: "none", + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.stories.ts b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.stories.ts new file mode 100644 index 00000000000..b65b92a737b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.stories.ts @@ -0,0 +1,3 @@ +export default { title: "Primitives/DatePicker" }; + +export { App as basic } from "./basic.story"; diff --git a/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.tsx b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.tsx new file mode 100644 index 00000000000..553a85ac286 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/date-picker/date-picker.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { DatePicker } from "@ark-ui/react/date-picker"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { datePicker } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(datePicker); + +export type RootProps = ComponentProps; +export const Root = withProvider(DatePicker.Root, "root"); +export const RootProvider = withProvider(DatePicker.RootProvider, "root"); +export const ClearTrigger = withContext( + DatePicker.ClearTrigger, + "clearTrigger", +); +export const Content = withContext(DatePicker.Content, "content"); +export const Control = withContext(DatePicker.Control, "control"); +export const Input = withContext(DatePicker.Input, "input"); +export const Label = withContext(DatePicker.Label, "label"); +export const MonthSelect = withContext(DatePicker.MonthSelect, "monthSelect"); +export const NextTrigger = withContext(DatePicker.NextTrigger, "nextTrigger"); +export const Positioner = withContext(DatePicker.Positioner, "positioner"); +export const PresetTrigger = withContext( + DatePicker.PresetTrigger, + "presetTrigger", +); +export const PrevTrigger = withContext(DatePicker.PrevTrigger, "prevTrigger"); +export const RangeText = withContext(DatePicker.RangeText, "rangeText"); +export const Table = withContext(DatePicker.Table, "table"); +export const TableBody = withContext(DatePicker.TableBody, "tableBody"); +export const TableCell = withContext(DatePicker.TableCell, "tableCell"); +export const TableCellTrigger = withContext( + DatePicker.TableCellTrigger, + "tableCellTrigger", +); +export const TableHead = withContext(DatePicker.TableHead, "tableHead"); +export const TableHeader = withContext(DatePicker.TableHeader, "tableHeader"); +export const TableRow = withContext(DatePicker.TableRow, "tableRow"); +export const Trigger = withContext(DatePicker.Trigger, "trigger"); +export const View = withContext(DatePicker.View, "view"); +export const ViewControl = withContext(DatePicker.ViewControl, "viewControl"); +export const ViewTrigger = withContext(DatePicker.ViewTrigger, "viewTrigger"); +export const YearSelect = withContext(DatePicker.YearSelect, "yearSelect"); + +export { DatePickerContext as Context } from "@ark-ui/react/date-picker"; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/alert.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/alert.story.tsx new file mode 100644 index 00000000000..7008a5a9211 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/alert.story.tsx @@ -0,0 +1,42 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Are you sure? + + This action cannot be undone. This will permanently delete your + account and remove your data from our systems. + + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/basic.story.tsx new file mode 100644 index 00000000000..c29a5cbabe5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/basic.story.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/controlled.story.tsx new file mode 100644 index 00000000000..e8666392fdb --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/controlled.story.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; +import { useState } from "react"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + const [open, setOpen] = useState(false); + + return ( + setOpen(e.open)}> + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/cover.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/cover.story.tsx new file mode 100644 index 00000000000..9c651f913d1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/cover.story.tsx @@ -0,0 +1,38 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/dialog.recipe.ts b/libs/@hashintel/ds-components/src/beta/dialog/dialog.recipe.ts new file mode 100644 index 00000000000..39f9e1d8860 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/dialog.recipe.ts @@ -0,0 +1,210 @@ +import { dialogAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const dialog = defineSlotRecipe({ + className: "dialog", + slots: dialogAnatomy.extendWith("header", "body", "footer").keys(), + base: { + backdrop: { + background: "black.a7", + height: "100dvh", + left: "0", + position: "fixed", + top: "0", + width: "100dvw", + zIndex: "var(--z-index)", + _open: { + animationName: "fade-in", + animationTimingFunction: "emphasized-in", + animationDuration: "normal", + }, + _closed: { + animationName: "fade-out", + animationTimingFunction: "emphasized-out", + animationDuration: "fast", + }, + }, + positioner: { + "--dialog-z-index": "zIndex.modal", + display: "flex", + height: "100dvh", + justifyContent: "center", + left: 0, + overscrollBehaviorY: "none", + position: "fixed", + top: 0, + width: "100dvw", + zIndex: "calc(var(--dialog-z-index) + var(--layer-index, 0))", + }, + + title: { + fontWeight: "semibold", + textStyle: "lg", + }, + description: { + color: "fg.muted", + textStyle: "sm", + }, + closeTrigger: { + pos: "absolute", + top: "3", + insetEnd: "3", + }, + content: { + "--dialog-z-index": "zIndex.modal", + bg: "gray.surface.bg", + borderRadius: "l3", + boxShadow: "lg", + display: "flex", + flexDirection: "column", + my: "var(--dialog-margin, var(--dialog-base-margin))", + outline: 0, + position: "relative", + textStyle: "sm", + width: "100%", + zIndex: "calc(var(--dialog-z-index) + var(--layer-index, 0))", + py: { base: "4", md: "6" }, + gap: { base: "4", md: "6" }, + _open: { + animationDuration: "slowest", + }, + _closed: { + animationDuration: "normal", + }, + }, + header: { + display: "flex", + flexDirection: "column", + gap: "0.5", + px: { base: "4", md: "6" }, + flex: "0", + }, + body: { + display: "flex", + flex: "1", + flexDirection: "column", + alignItems: "flex-start", + px: { base: "4", md: "6" }, + }, + footer: { + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + flex: "0", + gap: "3", + px: { base: "4", md: "6" }, + }, + }, + defaultVariants: { + size: "md", + scrollBehavior: "outside", + placement: "center", + motionPreset: "scale", + }, + variants: { + motionPreset: { + scale: { + content: { + _open: { animationName: "scale-in, fade-in" }, + _closed: { animationName: "scale-out, fade-out" }, + }, + }, + "slide-in-bottom": { + content: { + _open: { animationName: "slide-from-bottom, fade-in" }, + _closed: { animationName: "slide-to-bottom, fade-out" }, + }, + }, + "slide-in-top": { + content: { + _open: { animationName: "slide-from-top, fade-in" }, + _closed: { animationName: "slide-to-top, fade-out" }, + }, + }, + "slide-in-left": { + content: { + _open: { animationName: "slide-from-left, fade-in" }, + _closed: { animationName: "slide-to-left, fade-out" }, + }, + }, + "slide-in-right": { + content: { + _open: { animationName: "slide-from-right, fade-in" }, + _closed: { animationName: "slide-to-right, fade-out" }, + }, + }, + none: {}, + }, + size: { + xs: { content: { maxW: "xs" } }, + sm: { content: { maxW: "sm" } }, + md: { content: { maxW: "md" } }, + lg: { content: { maxW: "lg" } }, + xl: { content: { maxW: "xl" } }, + cover: { + positioner: { padding: "8" }, + content: { + width: "100%", + height: "100%", + "--dialog-margin": "0", + }, + }, + full: { + content: { + maxW: "100dvw", + minH: "100dvh", + "--dialog-margin": "0", + borderRadius: "0", + }, + }, + }, + placement: { + center: { + positioner: { + alignItems: "center", + }, + content: { + "--dialog-base-margin": "auto", + mx: "auto", + }, + }, + top: { + positioner: { + alignItems: "flex-start", + }, + content: { + "--dialog-base-margin": "spacing.16", + mx: "auto", + }, + }, + bottom: { + positioner: { + alignItems: "flex-end", + }, + content: { + "--dialog-base-margin": "spacing.16", + mx: "auto", + }, + }, + }, + scrollBehavior: { + inside: { + positioner: { + overflow: "hidden", + }, + content: { + maxH: "calc(100% - 7.5rem)", + }, + body: { + overflow: "auto", + }, + }, + outside: { + positioner: { + overflow: "auto", + pointerEvents: "auto", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/dialog/dialog.stories.ts b/libs/@hashintel/ds-components/src/beta/dialog/dialog.stories.ts new file mode 100644 index 00000000000..3cb00e63386 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/dialog.stories.ts @@ -0,0 +1,16 @@ +export default { title: "Primitives/Dialog" }; + +export { App as alert } from "./alert.story"; +export { App as basic } from "./basic.story"; +export { App as controlled } from "./controlled.story"; +export { App as cover } from "./cover.story"; +export { App as fullscreen } from "./fullscreen.story"; +export { App as initialFocus } from "./initial-focus.story"; +export { App as insideScroll } from "./inside-scroll.story"; +export { App as motionPresets } from "./motion-presets.story"; +export { App as nested } from "./nested.story"; +export { App as nonModal } from "./non-modal.story"; +export { App as outsideScroll } from "./outside-scroll.story"; +export { App as placements } from "./placements.story"; +export { App as responsive } from "./responsive.story"; +export { App as sizes } from "./sizes.story"; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/dialog.tsx b/libs/@hashintel/ds-components/src/beta/dialog/dialog.tsx new file mode 100644 index 00000000000..a83473e84b7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/dialog.tsx @@ -0,0 +1,53 @@ +"use client"; + +/* eslint-disable @typescript-eslint/no-shadow */ + +import { Dialog, useDialogContext } from "@ark-ui/react/dialog"; +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext, styled } from "@hashintel/ds-helpers/jsx"; +import { dialog } from "@hashintel/ds-helpers/recipes"; +import { type ComponentProps, forwardRef } from "react"; + +const { withRootProvider, withContext } = createStyleContext(dialog); + +export type RootProps = ComponentProps; +export const Root = withRootProvider(Dialog.Root, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const RootProvider = withRootProvider(Dialog.RootProvider, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const Backdrop = withContext(Dialog.Backdrop, "backdrop"); +export const CloseTrigger = withContext(Dialog.CloseTrigger, "closeTrigger"); +export const Content = withContext(Dialog.Content, "content"); +export const Description = withContext(Dialog.Description, "description"); +export const Positioner = withContext(Dialog.Positioner, "positioner"); +export const Title = withContext(Dialog.Title, "title"); +export const Trigger = withContext(Dialog.Trigger, "trigger"); +export const Body = withContext(ark.div, "body"); +export const Header = withContext(ark.div, "header"); +export const Footer = withContext(ark.div, "footer"); + +const StyledButton = styled(ark.button); + +export type ActionTriggerProps = ComponentProps; + +export const ActionTrigger: React.ForwardRefExoticComponent< + ActionTriggerProps & React.RefAttributes +> = forwardRef( + ({ onClick, ...props }, ref) => { + const dialog = useDialogContext(); + return ( + { + onClick?.(event); + dialog.setOpen(false); + }} + /> + ); + }, +); + +export { DialogContext as Context } from "@ark-ui/react/dialog"; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/fullscreen.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/fullscreen.story.tsx new file mode 100644 index 00000000000..fa476a4b264 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/fullscreen.story.tsx @@ -0,0 +1,38 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/initial-focus.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/initial-focus.story.tsx new file mode 100644 index 00000000000..cc657c36978 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/initial-focus.story.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; +import { useRef } from "react"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import * as Dialog from "./dialog"; + +export const App = () => { + const ref = useRef(null); + + return ( + ref.current}> + + + + + + + + + Title + Description + + + + First Name + + + + Last Name + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/inside-scroll.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/inside-scroll.story.tsx new file mode 100644 index 00000000000..d81e77e3ebb --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/inside-scroll.story.tsx @@ -0,0 +1,47 @@ +import { Portal } from "@ark-ui/react/portal"; +import { loremIpsum } from "lorem-ipsum"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/motion-presets.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/motion-presets.story.tsx new file mode 100644 index 00000000000..b742ae3317b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/motion-presets.story.tsx @@ -0,0 +1,45 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + const presets = ["scale", "slide-in-bottom", "slide-in-top", "none"] as const; + + return ( + + {presets.map((preset) => ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/nested.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/nested.story.tsx new file mode 100644 index 00000000000..ee34d65e0b5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/nested.story.tsx @@ -0,0 +1,52 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/non-modal.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/non-modal.story.tsx new file mode 100644 index 00000000000..bf2b69e10cf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/non-modal.story.tsx @@ -0,0 +1,37 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/outside-scroll.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/outside-scroll.story.tsx new file mode 100644 index 00000000000..79d79947997 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/outside-scroll.story.tsx @@ -0,0 +1,47 @@ +import { Portal } from "@ark-ui/react/portal"; +import { loremIpsum } from "lorem-ipsum"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/placements.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/placements.story.tsx new file mode 100644 index 00000000000..762d0784c7f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/placements.story.tsx @@ -0,0 +1,45 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + const placements = ["top", "center", "bottom"] as const; + + return ( + + {placements.map((placement) => ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/responsive.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/responsive.story.tsx new file mode 100644 index 00000000000..64f855f4daf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/responsive.story.tsx @@ -0,0 +1,38 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/dialog/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/dialog/sizes.story.tsx new file mode 100644 index 00000000000..cc51c3e1083 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/dialog/sizes.story.tsx @@ -0,0 +1,45 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "./dialog"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg", "xl"] as const; + + return ( + + {sizes.map((size) => ( + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/display-value/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/display-value/basic.story.tsx new file mode 100644 index 00000000000..488f264709b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/display-value/basic.story.tsx @@ -0,0 +1,5 @@ +import { DisplayValue } from "./display-value"; + +export const App = () => { + return ; +}; diff --git a/libs/@hashintel/ds-components/src/beta/display-value/display-value.stories.ts b/libs/@hashintel/ds-components/src/beta/display-value/display-value.stories.ts new file mode 100644 index 00000000000..b72383d70ad --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/display-value/display-value.stories.ts @@ -0,0 +1,5 @@ +export default { title: "Primitives/DisplayValue" }; + +export { App as basic } from "./basic.story"; +export { App as withFormatting } from "./with-formatting.story"; +export { App as withNoValue } from "./with-no-value.story"; diff --git a/libs/@hashintel/ds-components/src/beta/display-value/display-value.tsx b/libs/@hashintel/ds-components/src/beta/display-value/display-value.tsx new file mode 100644 index 00000000000..ada440b5091 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/display-value/display-value.tsx @@ -0,0 +1,49 @@ +"use client"; + +/* eslint-disable @typescript-eslint/no-use-before-define */ + +import { VisuallyHidden } from "@hashintel/ds-helpers/jsx"; + +import { Span } from "../span/span"; + +export interface DisplayValueProps { + /** The value to display */ + value?: T | null | undefined; + /** Optional function to format the value before displaying */ + formatValue?: (value: NonNullable) => string | null | undefined; +} + +export const DisplayValue = (props: DisplayValueProps) => { + const { value, formatValue } = props; + + const formattedValue = isNotEmpty(value) + ? (formatValue?.(value) ?? String(value)) + : null; + + if (formattedValue) { + return formattedValue; + } + + return ( + <> + + — + + No value available + + ); +}; + +const isString = (value: unknown): value is string => typeof value === "string"; + +const isNotEmpty = ( + value: T | null | undefined, +): value is NonNullable => { + if (value == null) { + return false; + } + if (isString(value) || Array.isArray(value)) { + return value.length > 0; + } + return true; +}; diff --git a/libs/@hashintel/ds-components/src/beta/display-value/with-formatting.story.tsx b/libs/@hashintel/ds-components/src/beta/display-value/with-formatting.story.tsx new file mode 100644 index 00000000000..0a86100581c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/display-value/with-formatting.story.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Span } from "../span/span"; +import { DisplayValue } from "./display-value"; + +export const App = () => { + return ( + + + date.toDateString()} + /> + + + arr.join(", ")} + /> + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/display-value/with-no-value.story.tsx b/libs/@hashintel/ds-components/src/beta/display-value/with-no-value.story.tsx new file mode 100644 index 00000000000..ab57cbff9a2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/display-value/with-no-value.story.tsx @@ -0,0 +1,14 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { DisplayValue } from "./display-value"; + +export const App = () => { + return ( + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/drawer/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/drawer/basic.story.tsx new file mode 100644 index 00000000000..0df220f038b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/basic.story.tsx @@ -0,0 +1,34 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Drawer from "./drawer"; + +export const App = () => { + return ( + + + + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/drawer/drawer.recipe.ts b/libs/@hashintel/ds-components/src/beta/drawer/drawer.recipe.ts new file mode 100644 index 00000000000..7a58ee32078 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/drawer.recipe.ts @@ -0,0 +1,202 @@ +import { dialogAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const drawer = defineSlotRecipe({ + className: "drawer", + slots: dialogAnatomy.extendWith("header", "body", "footer").keys(), + base: { + backdrop: { + background: "black.a7", + position: "fixed", + insetInlineStart: "0", + top: "0", + width: "100vw", + height: "100dvh", + zIndex: "overlay", + _open: { + animationName: "fade-in", + animationTimingFunction: "emphasized-in", + animationDuration: "slow", + }, + _closed: { + animationName: "fade-out", + animationTimingFunction: "emphasized-out", + animationDuration: "normal", + }, + }, + positioner: { + display: "flex", + width: "100vw", + height: "100dvh", + position: "fixed", + insetInlineStart: "0", + top: "0", + zIndex: "modal", + overscrollBehaviorY: "none", + }, + content: { + display: "flex", + flexDirection: "column", + position: "relative", + width: "100%", + outline: 0, + zIndex: "modal", + maxH: "100dvh", + color: "inherit", + bg: "gray.surface.bg", + boxShadow: "lg", + _open: { + animationDuration: "slowest", + animationTimingFunction: "cubic-bezier(0.05, 0.7, 0.1, 1.0)", + }, + _closed: { + animationDuration: "normal", + animationTimingFunction: "cubic-bezier(0.3, 0.0, 0.8, 0.15)", + }, + }, + header: { + display: "flex", + flexDirection: "column", + gap: "1", + pt: { base: "4", md: "6" }, + pb: "4", + px: { base: "4", md: "6" }, + flex: "0", + }, + body: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + flex: "1", + overflow: "auto", + p: { base: "4", md: "6" }, + }, + footer: { + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + flex: "0", + gap: "3", + py: "4", + px: { base: "4", md: "6" }, + }, + title: { + color: "fg.default", + fontWeight: "semibold", + textStyle: "xl", + }, + description: { + color: "fg.muted", + textStyle: "sm", + }, + closeTrigger: { + pos: "absolute", + top: "3", + insetEnd: "3", + }, + }, + defaultVariants: { + placement: "end", + size: "sm", + }, + variants: { + size: { + xs: { + content: { + maxW: "xs", + }, + }, + sm: { + content: { + maxW: "sm", + }, + }, + md: { + content: { + maxW: "md", + }, + }, + lg: { + content: { + maxW: "lg", + }, + }, + xl: { + content: { + maxW: "xl", + }, + }, + full: { + content: { + maxW: "100vw", + h: "100dvh", + }, + }, + }, + placement: { + start: { + positioner: { + justifyContent: "flex-start", + alignItems: "stretch", + }, + content: { + _open: { + animationName: { + base: "slide-from-left-full, fade-in", + _rtl: "slide-from-right-full, fade-in", + }, + }, + _closed: { + animationName: { + base: "slide-to-left-full, fade-out", + _rtl: "slide-to-right-full, fade-out", + }, + }, + }, + }, + end: { + positioner: { + justifyContent: "flex-end", + alignItems: "stretch", + }, + content: { + _open: { + animationName: { + base: "slide-from-right-full, fade-in", + _rtl: "slide-from-left-full, fade-in", + }, + }, + _closed: { + animationName: { + base: "slide-to-right-full, fade-out", + _rtl: "slide-to-left-full, fade-out", + }, + }, + }, + }, + top: { + positioner: { + justifyContent: "stretch", + alignItems: "flex-start", + }, + content: { + maxW: "100%", + _open: { animationName: "slide-from-top-full, fade-in" }, + _closed: { animationName: "slide-to-top-full, fade-out" }, + }, + }, + + bottom: { + positioner: { + justifyContent: "stretch", + alignItems: "flex-end", + }, + content: { + maxW: "100%", + _open: { animationName: "slide-from-bottom-full, fade-in" }, + _closed: { animationName: "slide-to-bottom-full, fade-out" }, + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/drawer/drawer.stories.ts b/libs/@hashintel/ds-components/src/beta/drawer/drawer.stories.ts new file mode 100644 index 00000000000..c8d31350079 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/drawer.stories.ts @@ -0,0 +1,5 @@ +export default { title: "Primitives/Drawer" }; + +export { App as basic } from "./basic.story"; +export { App as placements } from "./placements.story"; +export { App as sizes } from "./sizes.story"; diff --git a/libs/@hashintel/ds-components/src/beta/drawer/drawer.tsx b/libs/@hashintel/ds-components/src/beta/drawer/drawer.tsx new file mode 100644 index 00000000000..b627a871217 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/drawer.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Dialog } from "@ark-ui/react/dialog"; +import { ark } from "@ark-ui/react/factory"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { drawer } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withRootProvider, withContext } = createStyleContext(drawer); + +export type RootProps = ComponentProps; +export const Root = withRootProvider(Dialog.Root, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const RootProvider = withRootProvider(Dialog.Root, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const Backdrop = withContext(Dialog.Backdrop, "backdrop"); +export const Positioner = withContext(Dialog.Positioner, "positioner"); +export const CloseTrigger = withContext(Dialog.CloseTrigger, "closeTrigger"); +export const Content = withContext(Dialog.Content, "content"); +export const Description = withContext(Dialog.Description, "description"); +export const Title = withContext(Dialog.Title, "title"); +export const Trigger = withContext(Dialog.Trigger, "trigger"); + +export const Body = withContext(ark.div, "body"); +export const Header = withContext(ark.div, "header"); +export const Footer = withContext(ark.div, "footer"); + +export { DialogContext as Context } from "@ark-ui/react/dialog"; diff --git a/libs/@hashintel/ds-components/src/beta/drawer/placements.story.tsx b/libs/@hashintel/ds-components/src/beta/drawer/placements.story.tsx new file mode 100644 index 00000000000..1fc0dea0da5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/placements.story.tsx @@ -0,0 +1,39 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Drawer from "./drawer"; + +export const App = () => { + return ( + + {(["bottom", "top", "start", "end"] as const).map((placement) => ( + + + + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/drawer/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/drawer/sizes.story.tsx new file mode 100644 index 00000000000..05342fffb2a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/drawer/sizes.story.tsx @@ -0,0 +1,38 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Drawer from "./drawer"; + +export const App = () => { + return ( + + {(["xs", "sm", "md", "lg", "xl", "full"] as const).map((size) => ( + + + + + + + + + + + + + Title + Description + + {/* Content */} + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/editable/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/editable/basic.story.tsx new file mode 100644 index 00000000000..5b8e6c9c4c4 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/basic.story.tsx @@ -0,0 +1,10 @@ +import * as Editable from "./editable"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/editable/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/editable/controlled.story.tsx new file mode 100644 index 00000000000..6ff786d2db8 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/controlled.story.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { useState } from "react"; + +import * as Editable from "./editable"; + +export const App = () => { + const [name, setName] = useState(""); + return ( + setName(e.value)} + placeholder="Click to edit" + > + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/editable/controls.story.tsx b/libs/@hashintel/ds-components/src/beta/editable/controls.story.tsx new file mode 100644 index 00000000000..f0eb89e036b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/controls.story.tsx @@ -0,0 +1,33 @@ +import { CheckIcon, EditIcon, XIcon } from "lucide-react"; + +import { ButtonGroup } from "../button/button"; +import { IconButton } from "../icon-button/icon-button"; +import * as Editable from "./editable"; + +export const App = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/editable/double-click.story.tsx b/libs/@hashintel/ds-components/src/beta/editable/double-click.story.tsx new file mode 100644 index 00000000000..39755465c6a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/double-click.story.tsx @@ -0,0 +1,13 @@ +import * as Editable from "./editable"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/editable/editable.recipe.ts b/libs/@hashintel/ds-components/src/beta/editable/editable.recipe.ts new file mode 100644 index 00000000000..d1137cc9b8a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/editable.recipe.ts @@ -0,0 +1,74 @@ +import { editableAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const editable = defineSlotRecipe({ + slots: editableAnatomy.keys(), + className: "editable", + base: { + root: { + alignItems: "center", + display: "inline-flex", + gap: "1.5", + position: "relative", + width: "full", + }, + preview: { + alignItems: "center", + borderRadius: "l2", + cursor: "default", + display: "inline-flex", + transitionDuration: "normal", + transitionProperty: "common", + _disabled: { + userSelect: "none", + }, + _hover: { + bg: "gray.plain.bg.hover", + }, + }, + input: { + borderRadius: "l2", + + focusRingWidth: "2px", + focusRing: "inside", + transitionDuration: "normal", + transitionProperty: "common", + width: "full", + _focusVisible: { + outlineOffset: "-1px", + }, + }, + control: { + alignItems: "center", + display: "inline-flex", + gap: "1.5", + }, + }, + defaultVariants: { + size: "md", + }, + variants: { + size: { + "2xs": { + preview: { textStyle: "xs", px: "2", py: "0.5" }, + input: { textStyle: "xs", px: "2", py: "0.5" }, + }, + xs: { + preview: { textStyle: "sm", px: "2.5", py: "1.5" }, + input: { textStyle: "sm", px: "2.5", py: "1.5" }, + }, + sm: { + preview: { textStyle: "sm", px: "3", py: "2" }, + input: { textStyle: "sm", px: "3", py: "2" }, + }, + md: { + preview: { textStyle: "sm", px: "3.5", py: "2.5" }, + input: { textStyle: "sm", px: "3.5", py: "2.5" }, + }, + lg: { + preview: { textStyle: "md", px: "4", py: "2.5" }, + input: { textStyle: "md", px: "4", py: "2.5" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/editable/editable.stories.ts b/libs/@hashintel/ds-components/src/beta/editable/editable.stories.ts new file mode 100644 index 00000000000..fa36a5ad012 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/editable.stories.ts @@ -0,0 +1,7 @@ +export default { title: "Primitives/Editable" }; + +export { App as basic } from "./basic.story"; +export { App as controlled } from "./controlled.story"; +export { App as controls } from "./controls.story"; +export { App as doubleClick } from "./double-click.story"; +export { App as sizes } from "./sizes.story"; diff --git a/libs/@hashintel/ds-components/src/beta/editable/editable.tsx b/libs/@hashintel/ds-components/src/beta/editable/editable.tsx new file mode 100644 index 00000000000..879f29106e2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/editable.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { Editable } from "@ark-ui/react/editable"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { editable } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(editable); + +export type RootProps = ComponentProps; +export const Root = withProvider(Editable.Root, "root"); +export const RootProvider = withProvider(Editable.RootProvider, "root"); +export const Area = withContext(Editable.Area, "area"); +export const CancelTrigger = withContext( + Editable.CancelTrigger, + "cancelTrigger", +); +export const Control = withContext(Editable.Control, "control"); +export const EditTrigger = withContext(Editable.EditTrigger, "editTrigger"); +export const Input = withContext(Editable.Input, "input"); +export const Label = withContext(Editable.Label, "label"); +export const Preview = withContext(Editable.Preview, "preview"); +export const SubmitTrigger = withContext( + Editable.SubmitTrigger, + "submitTrigger", +); + +export { EditableContext as Context } from "@ark-ui/react/editable"; diff --git a/libs/@hashintel/ds-components/src/beta/editable/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/editable/sizes.story.tsx new file mode 100644 index 00000000000..6b84992d3be --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/editable/sizes.story.tsx @@ -0,0 +1,17 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as Editable from "./editable"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg"] as const; + return ( + + {sizes.map((size) => ( + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/field/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/field/basic.story.tsx new file mode 100644 index 00000000000..12f9704f634 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/basic.story.tsx @@ -0,0 +1,11 @@ +import { Input } from "../input/input"; +import * as Field from "./field"; + +export const App = () => { + return ( + + Email + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/field/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/field/closed.story.tsx new file mode 100644 index 00000000000..c6969cbe50d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/closed.story.tsx @@ -0,0 +1,30 @@ +import { forwardRef, type ReactNode } from "react"; + +import * as StyledField from "./field"; + +export interface FieldProps extends Omit { + label?: ReactNode; + helperText?: ReactNode; + errorText?: ReactNode; + optionalText?: ReactNode; +} + +export const Field = forwardRef((props, ref) => { + const { label, children, helperText, errorText, optionalText, ...rest } = + props; + return ( + + {label && ( + + {label} + + + )} + {children} + {helperText && ( + {helperText} + )} + {errorText} + + ); +}); diff --git a/libs/@hashintel/ds-components/src/beta/field/disabled.story.tsx b/libs/@hashintel/ds-components/src/beta/field/disabled.story.tsx new file mode 100644 index 00000000000..33bdae5d548 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/disabled.story.tsx @@ -0,0 +1,12 @@ +import { Input } from "../input/input"; +import * as Field from "./field"; + +export const App = () => { + return ( + + Email + + This is a helper text + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/field/field.recipe.ts b/libs/@hashintel/ds-components/src/beta/field/field.recipe.ts new file mode 100644 index 00000000000..7f7f7e3e1c5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/field.recipe.ts @@ -0,0 +1,40 @@ +import { fieldAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const field = defineSlotRecipe({ + className: "field", + slots: fieldAnatomy.keys(), + base: { + root: { + display: "flex", + flexDirection: "column", + gap: "1.5", + }, + label: { + alignItems: "center", + color: "fg.default", + display: "flex", + gap: "0.5", + textAlign: "start", + userSelect: "none", + textStyle: "label", + _disabled: { + layerStyle: "disabled", + }, + }, + requiredIndicator: { + color: "colorPalette.solid", + }, + helperText: { + color: "fg.muted", + textStyle: "sm", + _disabled: { + layerStyle: "disabled", + }, + }, + errorText: { + color: "error", + textStyle: "sm", + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/field/field.stories.ts b/libs/@hashintel/ds-components/src/beta/field/field.stories.ts new file mode 100644 index 00000000000..4c07956cdbe --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/field.stories.ts @@ -0,0 +1,7 @@ +export default { title: "Primitives/Field" }; + +export { App as basic } from "./basic.story"; +export { App as disabled } from "./disabled.story"; +export { App as helperText } from "./helper-text.story"; +export { App as invalid } from "./invalid.story"; +export { App as required } from "./required.story"; diff --git a/libs/@hashintel/ds-components/src/beta/field/field.tsx b/libs/@hashintel/ds-components/src/beta/field/field.tsx new file mode 100644 index 00000000000..7a94cec90df --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/field.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Field } from "@ark-ui/react/field"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { field } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(field); + +export type RootProps = ComponentProps; +export const Root = withProvider(Field.Root, "root"); +export const RootProvider = withProvider(Field.RootProvider, "root"); +export const ErrorText = withContext(Field.ErrorText, "errorText"); +export const HelperText = withContext(Field.HelperText, "helperText"); +export const Label = withContext(Field.Label, "label"); +export const RequiredIndicator = withContext( + Field.RequiredIndicator, + "requiredIndicator", +); + +export { FieldContext as Context } from "@ark-ui/react/field"; diff --git a/libs/@hashintel/ds-components/src/beta/field/helper-text.story.tsx b/libs/@hashintel/ds-components/src/beta/field/helper-text.story.tsx new file mode 100644 index 00000000000..fe1673214d2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/helper-text.story.tsx @@ -0,0 +1,12 @@ +import { Input } from "../input/input"; +import * as Field from "./field"; + +export const App = () => { + return ( + + Email + + This is a helper text + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/field/invalid.story.tsx b/libs/@hashintel/ds-components/src/beta/field/invalid.story.tsx new file mode 100644 index 00000000000..1b732e89997 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/invalid.story.tsx @@ -0,0 +1,12 @@ +import { Input } from "../input/input"; +import * as Field from "./field"; + +export const App = () => { + return ( + + Email + + This is an error text + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/field/required.story.tsx b/libs/@hashintel/ds-components/src/beta/field/required.story.tsx new file mode 100644 index 00000000000..602f44118b2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/field/required.story.tsx @@ -0,0 +1,14 @@ +import { Input } from "../input/input"; +import * as Field from "./field"; + +export const App = () => { + return ( + + + Email + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/fieldset/basic.story.tsx new file mode 100644 index 00000000000..023d8d4a071 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/basic.story.tsx @@ -0,0 +1,26 @@ +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import * as Fieldset from "./fieldset"; + +export const App = () => { + return ( + + + Contact details + + Please provide your contact details below. + + + + + Name + + + + Email address + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/disabled.story.tsx b/libs/@hashintel/ds-components/src/beta/fieldset/disabled.story.tsx new file mode 100644 index 00000000000..a71e212eb10 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/disabled.story.tsx @@ -0,0 +1,26 @@ +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import * as Fieldset from "./fieldset"; + +export const App = () => { + return ( + + + Contact details + + Please provide your contact details below. + + + + + Name + + + + Email address + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.recipe.ts b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.recipe.ts new file mode 100644 index 00000000000..de00331cf81 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.recipe.ts @@ -0,0 +1,46 @@ +import { fieldsetAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const fieldset = defineSlotRecipe({ + className: "fieldset", + slots: fieldsetAnatomy.extendWith("content", "control").keys(), + base: { + root: { + display: "flex", + justifyContent: "space-between", + width: "full", + flexDirection: { base: "column", md: "row" }, + gap: { base: "5", md: "8" }, + }, + control: { + maxW: "xs", + display: "flex", + flexDirection: "column", + width: "full", + gap: "1", + }, + content: { + display: "flex", + flexDirection: "column", + width: "full", + maxW: "2xl", + gap: "4", + }, + legend: { + color: "fg.default", + fontWeight: "semibold", + }, + helperText: { + color: "fg.muted", + textStyle: "sm", + }, + errorText: { + display: "inline-flex", + alignItems: "center", + color: "error", + gap: "2", + fontWeight: "medium", + textStyle: "sm", + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.stories.ts b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.stories.ts new file mode 100644 index 00000000000..a3031518125 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.stories.ts @@ -0,0 +1,5 @@ +export default { title: "Primitives/Fieldset" }; + +export { App as basic } from "./basic.story"; +export { App as disabled } from "./disabled.story"; +export { App as invalid } from "./invalid.story"; diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.tsx b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.tsx new file mode 100644 index 00000000000..e57f9bb398d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/fieldset.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { ark } from "@ark-ui/react/factory"; +import { Fieldset } from "@ark-ui/react/fieldset"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { fieldset } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(fieldset); + +export type RootProps = ComponentProps; +export const Root = withProvider(Fieldset.Root, "root"); +export const RootProvider = withProvider(Fieldset.RootProvider, "root"); +export const Legend = withContext(Fieldset.Legend, "legend"); +export const HelperText = withContext(Fieldset.HelperText, "helperText"); +export const ErrorText = withContext(Fieldset.ErrorText, "errorText"); +export const Content = withContext(ark.div, "content"); +export const Control = withContext(ark.div, "control"); + +export { FieldsetContext as Context } from "@ark-ui/react/fieldset"; diff --git a/libs/@hashintel/ds-components/src/beta/fieldset/invalid.story.tsx b/libs/@hashintel/ds-components/src/beta/fieldset/invalid.story.tsx new file mode 100644 index 00000000000..1a2c1835296 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/fieldset/invalid.story.tsx @@ -0,0 +1,31 @@ +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import * as Fieldset from "./fieldset"; + +export const App = () => { + return ( + + + Shipping details + + Some fields are invalid. Please check them. + + + + + Name + + + + Email address + + The email has already been taken. + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/accepted-files.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/accepted-files.story.tsx new file mode 100644 index 00000000000..1643ee9c00d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/accepted-files.story.tsx @@ -0,0 +1,18 @@ +import { ImageUpIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/basic.story.tsx new file mode 100644 index 00000000000..826846967d5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/basic.story.tsx @@ -0,0 +1,18 @@ +import { UploadIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/directory.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/directory.story.tsx new file mode 100644 index 00000000000..ef26ea84f1a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/directory.story.tsx @@ -0,0 +1,18 @@ +import { FolderUpIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/dropzone.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/dropzone.story.tsx new file mode 100644 index 00000000000..e86f977982c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/dropzone.story.tsx @@ -0,0 +1,21 @@ +import { Box } from "@hashintel/ds-helpers/jsx"; +import { UploadIcon } from "lucide-react"; + +import { Icon } from "../icon/icon"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + Drag and drop files here + .png, .jpg up to 5MB + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.recipe.ts b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.recipe.ts new file mode 100644 index 00000000000..07015cf098f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.recipe.ts @@ -0,0 +1,85 @@ +import { fileUploadAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const fileUpload = defineSlotRecipe({ + className: "file-upload", + slots: fileUploadAnatomy.keys(), + base: { + root: { + alignItems: "flex-start", + display: "flex", + flexDirection: "column", + gap: "1.5", + width: "full", + }, + label: { + textStyle: "label", + }, + dropzone: { + alignItems: "center", + background: "gray.surface.bg", + borderRadius: "l3", + borderStyle: "dashed", + borderWidth: "2px", + display: "flex", + flexDirection: "column", + + focusVisibleRing: "outside", + justifyContent: "center", + transition: "backgrounds", + width: "full", + _dragging: { + background: "gray.surface.bg.hover", + borderStyle: "solid", + borderColor: "colorPalette.solid.bg", + }, + }, + item: { + alignItems: "start", + animationDuration: "normal", + animationName: "fade-in", + background: "gray.surface.bg", + borderRadius: "l3", + borderWidth: "1px", + display: "flex", + pos: "relative", + width: "full", + }, + itemGroup: { + display: "flex", + alignItems: "start", + flexDirection: "column", + width: "full", + }, + itemName: { + color: "fg.default", + fontWeight: "medium", + }, + itemSizeText: { + color: "fg.muted", + }, + itemDeleteTrigger: { + color: "fg.subtle", + }, + itemPreviewImage: { + aspectRatio: "1", + objectFit: "cover", + maxW: "20", + borderRadius: "l2", + }, + }, + defaultVariants: { + size: "md", + }, + variants: { + size: { + md: { + root: { gap: "4" }, + dropzone: { px: "6", py: "4", minHeight: "xs", gap: "0" }, + item: { p: "4", gap: "3", textStyle: "sm" }, + itemGroup: { gap: "3" }, + itemDeleteTrigger: { _icon: { boxSize: "4" } }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.stories.ts b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.stories.ts new file mode 100644 index 00000000000..e3c5bf4decb --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.stories.ts @@ -0,0 +1,10 @@ +export default { title: "Primitives/FileUpload" }; + +export { App as acceptedFiles } from "./accepted-files.story"; +export { App as basic } from "./basic.story"; +export { App as directory } from "./directory.story"; +export { App as dropzone } from "./dropzone.story"; +export { App as imagePreview } from "./image-preview.story"; +export { App as maxFiles } from "./max-files.story"; +export { App as mediaCapture } from "./media-capture.story"; +export { App as withInput } from "./with-input.story"; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.tsx new file mode 100644 index 00000000000..1afeb00ae23 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/file-upload.tsx @@ -0,0 +1,132 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies, @typescript-eslint/no-shadow, @typescript-eslint/no-empty-object-type */ + +import { FileUpload, useFileUploadContext } from "@ark-ui/react/file-upload"; +import { + createStyleContext, + type HTMLStyledProps, + Stack, +} from "@hashintel/ds-helpers/jsx"; +import { fileUpload } from "@hashintel/ds-helpers/recipes"; +import { FileIcon, XIcon } from "lucide-react"; +import { type ComponentProps, forwardRef, useMemo } from "react"; + +import { Span } from "../span/span"; + +const { withProvider, withContext } = createStyleContext(fileUpload); + +export type RootProps = ComponentProps; +export type ItemProps = ComponentProps; + +export const Root = withProvider(FileUpload.Root, "root"); +export const RootProvider = withProvider(FileUpload.RootProvider, "root"); +export const ClearTrigger = withContext( + FileUpload.ClearTrigger, + "clearTrigger", +); +export const Dropzone = withContext(FileUpload.Dropzone, "dropzone"); +export const HiddenInput = FileUpload.HiddenInput; +export const Item = withContext(FileUpload.Item, "item"); +export const ItemDeleteTrigger = withContext( + FileUpload.ItemDeleteTrigger, + "itemDeleteTrigger", + { + defaultProps: { children: }, + }, +); +export const ItemGroup = withContext(FileUpload.ItemGroup, "itemGroup"); +export const ItemName = withContext(FileUpload.ItemName, "itemName"); +export const ItemPreview = withContext(FileUpload.ItemPreview, "itemPreview", { + defaultProps: { + children: , + }, +}); +export const ItemPreviewImage = withContext( + FileUpload.ItemPreviewImage, + "itemPreviewImage", +); +export const ItemSizeText = withContext( + FileUpload.ItemSizeText, + "itemSizeText", +); +export const Label = withContext(FileUpload.Label, "label"); +export const Trigger = withContext(FileUpload.Trigger, "trigger"); + +export { FileUploadContext as Context } from "@ark-ui/react/file-upload"; + +interface ItemsBaseProps { + showSize?: boolean | undefined; + clearable?: boolean | undefined; + files?: File[] | undefined; +} + +interface ItemsProps extends Omit, ItemsBaseProps {} + +export const Items = (props: ItemsProps) => { + const { showSize, clearable, files, ...rest } = props; + const fileUpload = useFileUploadContext(); + const acceptedFiles = files ?? fileUpload.acceptedFiles; + + return acceptedFiles.map((file) => ( + + + + + {showSize && } + + + {clearable && } + + )); +}; + +interface FileUploadListProps extends ItemsBaseProps {} + +export const List = forwardRef( + (props, ref) => { + const { showSize, clearable, files, ...rest } = props; + + return ( + + + + ); + }, +); + +export interface FileTextProps extends HTMLStyledProps<"span"> { + fallback?: string | undefined; +} + +export const FileText = forwardRef( + (props, ref) => { + const { fallback = "Select file(s)", ...rest } = props; + + const fileUpload = useFileUploadContext(); + + const acceptedFiles = fileUpload.acceptedFiles; + + const fileText = useMemo(() => { + if (acceptedFiles.length === 1) { + return acceptedFiles[0]?.name; + } + if (acceptedFiles.length > 1) { + return `${acceptedFiles.length} files`; + } + return fallback; + }, [acceptedFiles, fallback]); + + return ( + + {fileText} + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/image-preview.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/image-preview.story.tsx new file mode 100644 index 00000000000..6414b7d9195 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/image-preview.story.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useFileUploadContext } from "@ark-ui/react/file-upload"; +import { ImageUpIcon, XIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import { IconButton } from "../icon-button/icon-button"; +import * as FileUpload from "./file-upload"; + +const FileUploadList = () => { + const fileUpload = useFileUploadContext(); + const files = fileUpload.acceptedFiles; + if (files.length === 0) { + return null; + } + + return ( + + {files.map((file) => ( + + + + + + + + + ))} + + ); +}; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/max-files.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/max-files.story.tsx new file mode 100644 index 00000000000..f3605114dab --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/max-files.story.tsx @@ -0,0 +1,18 @@ +import { FileUpIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/media-capture.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/media-capture.story.tsx new file mode 100644 index 00000000000..7d422e6d6df --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/media-capture.story.tsx @@ -0,0 +1,18 @@ +import { WebcamIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/file-upload/with-input.story.tsx b/libs/@hashintel/ds-components/src/beta/file-upload/with-input.story.tsx new file mode 100644 index 00000000000..7905b7d4ebd --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/file-upload/with-input.story.tsx @@ -0,0 +1,29 @@ +import { FileUpIcon } from "lucide-react"; + +import { CloseButton } from "../close-button/close-button"; +import { Input } from "../input/input"; +import { InputGroup } from "../input-group/input-group"; +import * as FileUpload from "./file-upload"; + +export const App = () => { + return ( + + + Upload file + } + endElement={ + + + + } + > + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/floating-panel/floating-panel.recipe.ts b/libs/@hashintel/ds-components/src/beta/floating-panel/floating-panel.recipe.ts new file mode 100644 index 00000000000..d9445fbeec5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/floating-panel/floating-panel.recipe.ts @@ -0,0 +1,8 @@ +import { floatingPanelAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const floatingPanel = defineSlotRecipe({ + className: "floating-panel", + slots: floatingPanelAnatomy.keys(), + base: {}, +}); diff --git a/libs/@hashintel/ds-components/src/beta/group/attached.story.tsx b/libs/@hashintel/ds-components/src/beta/group/attached.story.tsx new file mode 100644 index 00000000000..bbe3fa1fc1b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/attached.story.tsx @@ -0,0 +1,20 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Badge } from "../badge/badge"; +import { Button } from "../button/button"; +import { Group } from "./group"; + +export const App = () => { + return ( + + + + + + + Commit status + 90+ + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/group/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/group/basic.story.tsx new file mode 100644 index 00000000000..c760cbe267c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/basic.story.tsx @@ -0,0 +1,12 @@ +import { Button } from "../button/button"; +import { Group } from "./group"; + +export const App = () => { + return ( + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/group/group.recipe.ts b/libs/@hashintel/ds-components/src/beta/group/group.recipe.ts new file mode 100644 index 00000000000..e5938122d45 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/group.recipe.ts @@ -0,0 +1,77 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const group = defineRecipe({ + className: "group", + base: { + display: "inline-flex", + position: "relative", + gap: "2", + "& > *": { + _focusVisible: { + zIndex: 1, + }, + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + variants: { + orientation: { + horizontal: { + flexDirection: "row", + }, + vertical: { + flexDirection: "column", + }, + }, + attached: { + true: { + gap: "0", + }, + }, + grow: { + true: { + display: "flex", + "& > *": { + flex: 1, + }, + }, + }, + }, + compoundVariants: [ + { + orientation: "horizontal", + attached: true, + css: { + "& > *:first-child": { + borderEndRadius: "0", + marginEnd: "-1px", + }, + "& > *:last-child": { + borderStartRadius: "0", + }, + "& > *:not(:first-child):not(:last-child)": { + borderRadius: "0", + marginEnd: "-1px", + }, + }, + }, + { + orientation: "vertical", + attached: true, + css: { + "& > *:first-child": { + borderBottomRadius: "0", + marginBottom: "-1px", + }, + "& > *:last-child": { + borderTopRadius: "0", + }, + "& > *:not(:first-child):not(:last-child)": { + borderRadius: "0", + marginBottom: "-1px", + }, + }, + }, + ], +}); diff --git a/libs/@hashintel/ds-components/src/beta/group/group.stories.ts b/libs/@hashintel/ds-components/src/beta/group/group.stories.ts new file mode 100644 index 00000000000..632e2067037 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/group.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Group" }; + +export { App as attached } from "./attached.story"; +export { App as basic } from "./basic.story"; +export { App as grow } from "./grow.story"; +export { App as vertical } from "./vertical.story"; diff --git a/libs/@hashintel/ds-components/src/beta/group/group.tsx b/libs/@hashintel/ds-components/src/beta/group/group.tsx new file mode 100644 index 00000000000..9570a75e6c9 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/group.tsx @@ -0,0 +1,7 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { group } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +export type GroupProps = ComponentProps; +export const Group = styled(ark.div, group); diff --git a/libs/@hashintel/ds-components/src/beta/group/grow.story.tsx b/libs/@hashintel/ds-components/src/beta/group/grow.story.tsx new file mode 100644 index 00000000000..48ecfd8d2e4 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/grow.story.tsx @@ -0,0 +1,12 @@ +import { Button } from "../button/button"; +import { Group } from "./group"; + +export const App = () => { + return ( + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/group/vertical.story.tsx b/libs/@hashintel/ds-components/src/beta/group/vertical.story.tsx new file mode 100644 index 00000000000..e885d605ff5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/group/vertical.story.tsx @@ -0,0 +1,12 @@ +import { Button } from "../button/button"; +import { Group } from "./group"; + +export const App = () => { + return ( + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/heading/as.story.tsx b/libs/@hashintel/ds-components/src/beta/heading/as.story.tsx new file mode 100644 index 00000000000..2be82515c65 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/as.story.tsx @@ -0,0 +1,13 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Heading } from "./heading"; + +export const App = () => { + return ( + + Level 1 + Level 2 + Level 3 + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/heading/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/heading/basic.story.tsx new file mode 100644 index 00000000000..463388c5a9e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/basic.story.tsx @@ -0,0 +1,5 @@ +import { Heading } from "./heading"; + +export const App = () => { + return Sphinx of black quartz, judge my vow.; +}; diff --git a/libs/@hashintel/ds-components/src/beta/heading/heading.recipe.ts b/libs/@hashintel/ds-components/src/beta/heading/heading.recipe.ts new file mode 100644 index 00000000000..d7f7f600df1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/heading.recipe.ts @@ -0,0 +1,8 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const heading = defineRecipe({ + className: "heading", + base: { + fontWeight: "semibold", + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/heading/heading.stories.ts b/libs/@hashintel/ds-components/src/beta/heading/heading.stories.ts new file mode 100644 index 00000000000..1c8742ea861 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/heading.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Heading" }; + +export { App as as } from "./as.story"; +export { App as basic } from "./basic.story"; +export { App as sizes } from "./sizes.story"; +export { App as weights } from "./weights.story"; diff --git a/libs/@hashintel/ds-components/src/beta/heading/heading.tsx b/libs/@hashintel/ds-components/src/beta/heading/heading.tsx new file mode 100644 index 00000000000..b30b8cbbb00 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/heading.tsx @@ -0,0 +1,6 @@ +import { styled } from "@hashintel/ds-helpers/jsx"; +import { heading } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +export type HeadingProps = ComponentProps; +export const Heading = styled("h2", heading); diff --git a/libs/@hashintel/ds-components/src/beta/heading/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/heading/sizes.story.tsx new file mode 100644 index 00000000000..c315295a6ce --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/sizes.story.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Heading } from "./heading"; + +export const App = () => { + return ( + + Ag + Ag + Ag + Ag + Ag + Ag + Ag + Ag + Ag + Ag + Ag + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/heading/weights.story.tsx b/libs/@hashintel/ds-components/src/beta/heading/weights.story.tsx new file mode 100644 index 00000000000..44fc045b5ae --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/heading/weights.story.tsx @@ -0,0 +1,23 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Heading } from "./heading"; + +export const App = () => { + return ( + + + Sphinx of black quartz, judge my vow. + + + Sphinx of black quartz, judge my vow. + + + Sphinx of black quartz, judge my vow. + + + Sphinx of black quartz, judge my vow. + + Sphinx of black quartz, judge my vow. + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/basic.story.tsx new file mode 100644 index 00000000000..f8cfbbd7381 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/basic.story.tsx @@ -0,0 +1,52 @@ +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + return ( + + + + @grizzly_codes + + + + + + + + + + + + + + + + + @grizzly_codes + + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/controlled.story.tsx new file mode 100644 index 00000000000..8a63482c490 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/controlled.story.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; +import { useState } from "react"; + +import * as Avatar from "../avatar/avatar"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + const [open, setOpen] = useState(false); + + return ( + setOpen(e.open)}> + + + @grizzly_codes + + + + + + + + + + + + + + + + @grizzly_codes + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/delays.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/delays.story.tsx new file mode 100644 index 00000000000..bf8546215cd --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/delays.story.tsx @@ -0,0 +1,52 @@ +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + return ( + + + + @grizzly_codes + + + + + + + + + + + + + + + + + @grizzly_codes + + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/dialog.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/dialog.story.tsx new file mode 100644 index 00000000000..b1c9577924d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/dialog.story.tsx @@ -0,0 +1,81 @@ +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "../dialog/dialog"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + return ( + + + + + + + + + + + + + Popover in Dialog + + + + + + @grizzly_codes + + + + + + + + + + + + + + + + @grizzly_codes + + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/disabled.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/disabled.story.tsx new file mode 100644 index 00000000000..41f3013f3b2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/disabled.story.tsx @@ -0,0 +1,52 @@ +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + return ( + + + + @grizzly_codes + + + + + + + + + + + + + + + + + @grizzly_codes + + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.recipe.ts b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.recipe.ts new file mode 100644 index 00000000000..f6d2802daf7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.recipe.ts @@ -0,0 +1,41 @@ +import { hoverCardAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const hoverCard = defineSlotRecipe({ + className: "hover-card", + slots: hoverCardAnatomy.keys(), + base: { + content: { + "--hovercard-bg": "colors.gray.surface.bg", + + bg: "var(--hovercard-bg)", + borderRadius: "l3", + boxShadow: "lg", + display: "flex", + flexDirection: "column", + maxWidth: "80", + outline: "0", + padding: "4", + position: "relative", + textStyle: "sm", + transformOrigin: "var(--transform-origin)", + zIndex: "popover", + _open: { + animationStyle: "slide-fade-in", + animationDuration: "fast", + }, + _closed: { + animationStyle: "slide-fade-out", + animationDuration: "faster", + }, + }, + arrow: { + "--arrow-size": "sizes.3", + "--arrow-background": "var(--hovercard-bg)", + }, + arrowTip: { + borderTopWidth: "0.5px", + borderInlineStartWidth: "0.5px", + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.stories.ts b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.stories.ts new file mode 100644 index 00000000000..3895b3f5ff0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.stories.ts @@ -0,0 +1,8 @@ +export default { title: "Primitives/HoverCard" }; + +export { App as basic } from "./basic.story"; +export { App as controlled } from "./controlled.story"; +export { App as delays } from "./delays.story"; +export { App as dialog } from "./dialog.story"; +export { App as disabled } from "./disabled.story"; +export { App as placement } from "./placement.story"; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.tsx new file mode 100644 index 00000000000..546baeff90c --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/hover-card.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { HoverCard } from "@ark-ui/react/hover-card"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { hoverCard } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withRootProvider, withContext } = createStyleContext(hoverCard); + +export type RootProps = ComponentProps; +export const Root = withRootProvider(HoverCard.Root); +export const RootProvider = withRootProvider(HoverCard.RootProvider); +export const Arrow = withContext(HoverCard.Arrow, "arrow"); +export const ArrowTip = withContext(HoverCard.ArrowTip, "arrowTip"); +export const Content = withContext(HoverCard.Content, "content"); +export const Positioner = withContext(HoverCard.Positioner, "positioner"); +export const Trigger = withContext(HoverCard.Trigger, "trigger"); + +export { HoverCardContext as Context } from "@ark-ui/react/hover-card"; diff --git a/libs/@hashintel/ds-components/src/beta/hover-card/placement.story.tsx b/libs/@hashintel/ds-components/src/beta/hover-card/placement.story.tsx new file mode 100644 index 00000000000..074976e8645 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/hover-card/placement.story.tsx @@ -0,0 +1,52 @@ +import { Portal } from "@ark-ui/react/portal"; +import { HStack, Stack } from "@hashintel/ds-helpers/jsx"; +import { MapPinIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import { Icon } from "../icon/icon"; +import { Link } from "../link/link"; +import { Text } from "../text/text"; +import * as HoverCard from "./hover-card"; + +export const App = () => { + return ( + + + + @grizzly_codes + + + + + + + + + + + + + + + + + @grizzly_codes + + + Principal Software Engineer working at Pyck.ai + + + + + + + Joined Oktober 2025 + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/icon-button/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/icon-button/basic.story.tsx new file mode 100644 index 00000000000..044ce93bd75 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/icon-button/basic.story.tsx @@ -0,0 +1,11 @@ +import { SendIcon } from "lucide-react"; + +import { IconButton } from "./icon-button"; + +export const App = () => { + return ( + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/icon-button/colors.story.tsx b/libs/@hashintel/ds-components/src/beta/icon-button/colors.story.tsx new file mode 100644 index 00000000000..c289845d2b2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/icon-button/colors.story.tsx @@ -0,0 +1,23 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; +import { SearchIcon } from "lucide-react"; + +import { IconButton } from "./icon-button"; + +export const App = () => { + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.stories.ts b/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.stories.ts new file mode 100644 index 00000000000..49e6e7c6dad --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.stories.ts @@ -0,0 +1,7 @@ +export default { title: "Primitives/IconButton" }; + +export { App as basic } from "./basic.story"; +export { App as colors } from "./colors.story"; +export { App as rounded } from "./rounded.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.tsx b/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.tsx new file mode 100644 index 00000000000..36222fa5cd0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/icon-button/icon-button.tsx @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ + +import { forwardRef } from "react"; + +import { Button, type ButtonProps } from "../button/button"; + +export interface IconButtonProps extends ButtonProps {} + +export const IconButton = forwardRef( + (props, ref) => { + return + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/basic.story.tsx new file mode 100644 index 00000000000..65978b70b67 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/basic.story.tsx @@ -0,0 +1,5 @@ +import { Kbd } from "./kbd"; + +export const App = () => { + return Shift + Tab; +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/combinations.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/combinations.story.tsx new file mode 100644 index 00000000000..a8b50d6f4bf --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/combinations.story.tsx @@ -0,0 +1,11 @@ +import { HStack } from "@hashintel/ds-helpers/jsx"; + +import { Kbd } from "./kbd"; + +export const App = () => { + return ( + + ctrl+shift+del + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/inline.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/inline.story.tsx new file mode 100644 index 00000000000..9cb12844d78 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/inline.story.tsx @@ -0,0 +1,10 @@ +import { Text } from "../text/text"; +import { Kbd } from "./kbd"; + +export const App = () => { + return ( + + Press F12 to open DevTools + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/kbd.recipe.ts b/libs/@hashintel/ds-components/src/beta/kbd/kbd.recipe.ts new file mode 100644 index 00000000000..96188d14cb2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/kbd.recipe.ts @@ -0,0 +1,55 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const kbd = defineRecipe({ + className: "kbd", + base: { + display: "inline-flex", + alignItems: "center", + fontWeight: "medium", + fontFamily: "code", + flexShrink: "0", + whiteSpace: "nowrap", + wordSpacing: "-0.5em", + userSelect: "none", + borderRadius: "l2", + justifyContent: "center", + }, + + variants: { + variant: { + solid: { + bg: "colorPalette.solid.bg", + color: "colorPalette.solid.fg", + }, + surface: { + bg: "colorPalette.surface.bg", + borderWidth: "1px", + borderColor: "colorPalette.surface.border", + color: "colorPalette.surface.fg", + }, + outline: { + borderWidth: "1px", + borderColor: "colorPalette.outline.border", + color: "colorPalette.outline.fg", + }, + subtle: { + bg: "colorPalette.subtle.bg", + color: "colorPalette.subtle.fg", + }, + plain: { + color: "colorPalette.plain.fg", + }, + }, + size: { + sm: { textStyle: "xs", height: "4.5", minWidth: "4.5", px: "1" }, + md: { textStyle: "sm", height: "5", minWidth: "5", px: "1" }, + lg: { textStyle: "sm", height: "5.5", minWidth: "5.5", px: "1" }, + xl: { textStyle: "md", height: "6", minWidth: "6", px: "1" }, + }, + }, + + defaultVariants: { + size: "md", + variant: "subtle", + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/kbd/kbd.stories.ts b/libs/@hashintel/ds-components/src/beta/kbd/kbd.stories.ts new file mode 100644 index 00000000000..85e5ab99b0b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/kbd.stories.ts @@ -0,0 +1,8 @@ +export default { title: "Primitives/Kbd" }; + +export { App as basic } from "./basic.story"; +export { App as combinations } from "./combinations.story"; +export { App as inline } from "./inline.story"; +export { App as modifierKeys } from "./modifier-keys.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/kbd.tsx b/libs/@hashintel/ds-components/src/beta/kbd/kbd.tsx new file mode 100644 index 00000000000..05853e7a415 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/kbd.tsx @@ -0,0 +1,7 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { kbd } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "@hashintel/ds-helpers/types"; + +export type KbdProps = ComponentProps; +export const Kbd = styled(ark.kbd, kbd); diff --git a/libs/@hashintel/ds-components/src/beta/kbd/modifier-keys.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/modifier-keys.story.tsx new file mode 100644 index 00000000000..cd6ff8b3795 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/modifier-keys.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Kbd } from "./kbd"; + +export const App = () => { + return ( + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/sizes.story.tsx new file mode 100644 index 00000000000..22e3533dfa2 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/sizes.story.tsx @@ -0,0 +1,14 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Kbd } from "./kbd"; + +export const App = () => { + return ( + + Shift + Tab + Shift + Tab + Shift + Tab + Shift + Tab + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/kbd/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/kbd/variants.story.tsx new file mode 100644 index 00000000000..68397e97185 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/kbd/variants.story.tsx @@ -0,0 +1,15 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Kbd } from "./kbd"; + +export const App = () => { + return ( + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/link/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/link/basic.story.tsx new file mode 100644 index 00000000000..07544ba72f5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/basic.story.tsx @@ -0,0 +1,5 @@ +import { Link } from "./link"; + +export const App = () => { + return Visit Park UI; +}; diff --git a/libs/@hashintel/ds-components/src/beta/link/icon.story.tsx b/libs/@hashintel/ds-components/src/beta/link/icon.story.tsx new file mode 100644 index 00000000000..7fb33edac71 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/icon.story.tsx @@ -0,0 +1,11 @@ +import { ExternalLinkIcon } from "lucide-react"; + +import { Link } from "./link"; + +export const App = () => { + return ( + + Visit Park UI + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/link/inline.story.tsx b/libs/@hashintel/ds-components/src/beta/link/inline.story.tsx new file mode 100644 index 00000000000..c7d05c17c99 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/inline.story.tsx @@ -0,0 +1,10 @@ +import { Text } from "../text/text"; +import { Link } from "./link"; + +export const App = () => { + return ( + + Visit the Park UI website + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/link/link.recipe.ts b/libs/@hashintel/ds-components/src/beta/link/link.recipe.ts new file mode 100644 index 00000000000..b263c7b7ea1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/link.recipe.ts @@ -0,0 +1,42 @@ +import { defineRecipe } from "@pandacss/dev"; + +export const link = defineRecipe({ + className: "link", + base: { + alignItems: "center", + borderRadius: "l1", + cursor: "pointer", + display: "inline-flex", + focusVisibleRing: "outside", + fontWeight: "medium", + gap: "1.5", + outline: "none", + textDecorationLine: "underline", + textDecorationThickness: "0.1em", + textUnderlineOffset: "0.125em", + transitionDuration: "normal", + transitionProperty: "text-decoration-color", + _icon: { + boxSize: "1em", + }, + }, + defaultVariants: { + variant: "underline", + }, + variants: { + variant: { + underline: { + textDecorationColor: "colorPalette.surface.fg/60", + _hover: { + textDecorationColor: "colorPalette.surface.fg", + }, + }, + plain: { + textDecorationColor: "transparent", + _hover: { + textDecorationColor: "colorPalette.surface.fg", + }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/link/link.stories.ts b/libs/@hashintel/ds-components/src/beta/link/link.stories.ts new file mode 100644 index 00000000000..482559c1f94 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/link.stories.ts @@ -0,0 +1,6 @@ +export default { title: "Primitives/Link" }; + +export { App as basic } from "./basic.story"; +export { App as icon } from "./icon.story"; +export { App as inline } from "./inline.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/link/link.tsx b/libs/@hashintel/ds-components/src/beta/link/link.tsx new file mode 100644 index 00000000000..274621b7183 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/link.tsx @@ -0,0 +1,7 @@ +import { ark } from "@ark-ui/react/factory"; +import { styled } from "@hashintel/ds-helpers/jsx"; +import { link } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +export type LinkProps = ComponentProps; +export const Link = styled(ark.a, link); diff --git a/libs/@hashintel/ds-components/src/beta/link/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/link/variants.story.tsx new file mode 100644 index 00000000000..271b1a5cefa --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/link/variants.story.tsx @@ -0,0 +1,16 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import { Link } from "./link"; + +export const App = () => { + return ( + + + Visit Park UI + + + Visit Park UI + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/listbox/listbox.recipe.ts b/libs/@hashintel/ds-components/src/beta/listbox/listbox.recipe.ts new file mode 100644 index 00000000000..25df1d5ca05 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/listbox/listbox.recipe.ts @@ -0,0 +1,8 @@ +import { listboxAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const listbox = defineSlotRecipe({ + className: "listbox", + slots: listboxAnatomy.keys(), + base: {}, +}); diff --git a/libs/@hashintel/ds-components/src/beta/loader/loader.tsx b/libs/@hashintel/ds-components/src/beta/loader/loader.tsx new file mode 100644 index 00000000000..249ce287e97 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/loader/loader.tsx @@ -0,0 +1,76 @@ +"use client"; + +import type { HTMLStyledProps } from "@hashintel/ds-helpers/jsx"; +import { forwardRef } from "react"; + +import { AbsoluteCenter } from "../absolute-center/absolute-center"; +import { Span } from "../span/span"; +import { Spinner } from "../spinner/spinner"; + +export interface LoaderProps extends HTMLStyledProps<"span"> { + /** + * Whether the loader is visible + * @default true + */ + visible?: boolean | undefined; + /** + * The spinner to display when loading + */ + spinner?: React.ReactNode | undefined; + /** + * The placement of the spinner + * @default "start" + */ + spinnerPlacement?: "start" | "end" | undefined; + /** + * The text to display when loading + */ + text?: React.ReactNode | undefined; + + children?: React.ReactNode; +} + +export const Loader = forwardRef((props, ref) => { + const { + spinner = ( + // @ts-expect-error - "inherit" is not a valid color token + + ), + spinnerPlacement = "start", + children, + text, + visible = true, + ...rest + } = props; + + if (!visible) { + return children; + } + + if (text) { + return ( + + {spinnerPlacement === "start" && spinner} + {text} + {spinnerPlacement === "end" && spinner} + + ); + } + + if (spinner) { + return ( + + {spinner} + + {children} + + + ); + } + + return ( + + {children} + + ); +}); diff --git a/libs/@hashintel/ds-components/src/beta/menu/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/basic.story.tsx new file mode 100644 index 00000000000..50b6c81598a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/basic.story.tsx @@ -0,0 +1,23 @@ +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + return ( + + + + + + + New File + New Folder + Open + Save + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/checkbox.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/checkbox.story.tsx new file mode 100644 index 00000000000..189a005d015 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/checkbox.story.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useCheckboxGroup } from "@ark-ui/react/checkbox"; + +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + const items = [ + { title: "Enable Notifications", value: "notifications" }, + { title: "Dark Mode", value: "dark-mode" }, + { title: "Show Tooltips", value: "tooltips" }, + ]; + + const group = useCheckboxGroup({ defaultValue: ["notifications"] }); + + return ( + + + + + + + {items.map(({ title, value }) => ( + group.toggleValue(value)} + > + {title} + + + ))} + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/context.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/context.story.tsx new file mode 100644 index 00000000000..6100d797f46 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/context.story.tsx @@ -0,0 +1,36 @@ +import { Portal } from "@ark-ui/react/portal"; +import { Center } from "@hashintel/ds-helpers/jsx"; + +import * as Menu from "./menu"; + +export const App = () => { + return ( + + +
+ Right click in explorer +
+
+ + + + New File + New Folder + Copy Path + + Reveal in File Explorer + + Properties + + + +
+ ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/group.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/group.story.tsx new file mode 100644 index 00000000000..00028d9bc76 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/group.story.tsx @@ -0,0 +1,33 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + return ( + + + + + + + + + Sort by + Name + Date Modified + Size + Type + + + View Options + Details + Large Icons + List + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/menu.recipe.ts b/libs/@hashintel/ds-components/src/beta/menu/menu.recipe.ts new file mode 100644 index 00000000000..378e58193d7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/menu.recipe.ts @@ -0,0 +1,123 @@ +import { menuAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const menu = defineSlotRecipe({ + className: "menu", + slots: menuAnatomy.keys(), + base: { + content: { + "--menu-z-index": "zIndex.dropdown", + + bg: "gray.surface.bg", + borderRadius: "l3", + boxShadow: "md", + display: "flex", + flexDirection: "column", + maxH: "min(var(--available-height), {sizes.96})", + minWidth: "max(var(--reference-width), {sizes.40})", + outline: "0", + overflow: "hidden", + overflowY: "auto", + position: "relative", + zIndex: "calc(var(--menu-z-index) + var(--layer-index, 0))", + _open: { + animationStyle: "slide-fade-in", + animationDuration: "fast", + }, + _closed: { + animationStyle: "slide-fade-out", + animationDuration: "faster", + }, + }, + item: { + alignItems: "center", + borderRadius: "l2", + display: "flex", + flex: "0 0 auto", + outline: "0", + textAlign: "start", + textDecoration: "none", + userSelect: "none", + width: "100%", + _highlighted: { + bg: "gray.surface.bg.hover", + }, + _disabled: { + layerStyle: "disabled", + }, + }, + trigger: { + _focusVisible: { + focusVisibleRing: "outside", + }, + }, + itemGroupLabel: { + alignItems: "flex-start", + color: "fg.subtle", + display: "flex", + flexDirection: "column", + fontWeight: "medium", + gap: "1px", + justifyContent: "center", + _after: { + content: '""', + width: "100%", + height: "1px", + bg: "border", + }, + }, + itemIndicator: { + justifyContent: "flex-end", + display: "flex", + flex: "1", + _checked: { + _icon: { + color: "colorPalette.plain.fg", + }, + }, + }, + }, + defaultVariants: { + size: "md", + }, + + variants: { + size: { + xs: { + content: { p: "1", gap: "0.5", textStyle: "sm" }, + item: { px: "1", minH: "8", gap: "2", _icon: { boxSize: "3.5" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "1", height: "8" }, + separator: { mx: "-1", my: "0.5" }, + }, + sm: { + content: { p: "1", gap: "0.5", textStyle: "sm" }, + item: { px: "1.5", minH: "9", gap: "2", _icon: { boxSize: "4" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "1.5", height: "9" }, + separator: { mx: "-1.5", my: "0.5" }, + }, + md: { + content: { p: "1", gap: "0.5", textStyle: "md" }, + item: { px: "2", minH: "10", gap: "2", _icon: { boxSize: "4" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "2", height: "10" }, + separator: { mx: "-2", my: "0.5" }, + }, + lg: { + content: { p: "1", gap: "0.5", textStyle: "md" }, + item: { px: "2.5", minH: "11", gap: "2", _icon: { boxSize: "4.5" } }, + itemGroup: { gap: "0.5" }, + itemGroupLabel: { px: "2.5", height: "11" }, + separator: { mx: "-2.5", my: "0.5" }, + }, + xl: { + content: { p: "1", gap: "1", textStyle: "lg" }, + item: { px: "3", minH: "12", gap: "3", _icon: { boxSize: "5" } }, + itemGroup: { gap: "1" }, + itemGroupLabel: { px: "3", height: "12" }, + separator: { mx: "-3", my: "0" }, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/menu/menu.stories.ts b/libs/@hashintel/ds-components/src/beta/menu/menu.stories.ts new file mode 100644 index 00000000000..4ac85487681 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/menu.stories.ts @@ -0,0 +1,10 @@ +export default { title: "Primitives/Menu" }; + +export { App as basic } from "./basic.story"; +export { App as checkbox } from "./checkbox.story"; +export { App as context } from "./context.story"; +export { App as group } from "./group.story"; +export { App as radioGroup } from "./radio-group.story"; +export { App as sizes } from "./sizes.story"; +export { App as submenu } from "./submenu.story"; +export { App as withAvatar } from "./with-avatar.story"; diff --git a/libs/@hashintel/ds-components/src/beta/menu/menu.tsx b/libs/@hashintel/ds-components/src/beta/menu/menu.tsx new file mode 100644 index 00000000000..e0ce4ac4489 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/menu.tsx @@ -0,0 +1,67 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { Menu, useMenuItemContext } from "@ark-ui/react/menu"; +import { + createStyleContext, + type HTMLStyledProps, +} from "@hashintel/ds-helpers/jsx"; +import { menu } from "@hashintel/ds-helpers/recipes"; +import { CheckIcon, ChevronDownIcon } from "lucide-react"; +import { type ComponentProps, forwardRef } from "react"; + +const { withRootProvider, withContext } = createStyleContext(menu); + +export type RootProps = ComponentProps; +export const Root = withRootProvider(Menu.Root, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const RootProvider = withRootProvider(Menu.RootProvider, { + defaultProps: { unmountOnExit: true, lazyMount: true }, +}); +export const Arrow = withContext(Menu.Arrow, "arrow"); +export const ArrowTip = withContext(Menu.ArrowTip, "arrowTip"); +export const CheckboxItem = withContext(Menu.CheckboxItem, "item"); +export const Content = withContext(Menu.Content, "content"); +export const ContextTrigger = withContext( + Menu.ContextTrigger, + "contextTrigger", +); +export const Indicator = withContext(Menu.Indicator, "indicator", { + defaultProps: { children: }, +}); +export const Item = withContext(Menu.Item, "item"); +export const ItemGroup = withContext(Menu.ItemGroup, "itemGroup"); +export const ItemGroupLabel = withContext( + Menu.ItemGroupLabel, + "itemGroupLabel", +); +export const ItemText = withContext(Menu.ItemText, "itemText"); +export const Positioner = withContext(Menu.Positioner, "positioner"); +export const RadioItem = withContext(Menu.RadioItem, "item"); +export const RadioItemGroup = withContext(Menu.RadioItemGroup, "itemGroup"); +export const Separator = withContext(Menu.Separator, "separator"); +export const Trigger = withContext(Menu.Trigger, "trigger"); +export const TriggerItem = withContext(Menu.TriggerItem, "item"); + +export { + MenuContext as Context, + type MenuSelectionDetails as SelectionDetails, +} from "@ark-ui/react/menu"; + +const StyledItemIndicator = withContext(Menu.ItemIndicator, "itemIndicator"); + +export const ItemIndicator = forwardRef>( + (props, ref) => { + const item = useMenuItemContext(); + + return item.checked ? ( + + + + ) : ( + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/menu/radio-group.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/radio-group.story.tsx new file mode 100644 index 00000000000..6fd384a99aa --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/radio-group.story.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useState } from "react"; + +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + const items = [ + { label: "Name (A-Z)", value: "name" }, + { label: "Date Created", value: "date" }, + { label: "Size", value: "size" }, + ]; + + const [value, setValue] = useState("name"); + return ( + + + + + + + setValue(e.value)} + > + {items.map((item) => ( + + {item.label} + + + ))} + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/sizes.story.tsx new file mode 100644 index 00000000000..7c5ff94c280 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/sizes.story.tsx @@ -0,0 +1,33 @@ +import { Wrap } from "@hashintel/ds-helpers/jsx"; + +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + const sizes = ["xs", "sm", "md", "lg"] as const; + + return ( + + {sizes.map((size) => ( + + + + + + + New File + Open File + + Save + Save As + Recent Files + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/submenu.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/submenu.story.tsx new file mode 100644 index 00000000000..b633f33ef03 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/submenu.story.tsx @@ -0,0 +1,43 @@ +import { Portal } from "@ark-ui/react/portal"; +import { ChevronRightIcon } from "lucide-react"; + +import { Button } from "../button/button"; +import * as Menu from "./menu"; + +export const App = () => { + return ( + + + + + + + + Find Files + Search & Replace + Compare Files + + + Git + + + + + + Status + Commit Changes + View History + + + + + Open Terminal + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/menu/with-avatar.story.tsx b/libs/@hashintel/ds-components/src/beta/menu/with-avatar.story.tsx new file mode 100644 index 00000000000..0deb2ae7709 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/menu/with-avatar.story.tsx @@ -0,0 +1,34 @@ +import { LogOutIcon, Settings2Icon, UserIcon } from "lucide-react"; + +import * as Avatar from "../avatar/avatar"; +import * as Menu from "./menu"; + +export const App = () => { + return ( + + + + + + + + + + + + Account + + + + Settings + + + + + Logout + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/basic.story.tsx new file mode 100644 index 00000000000..5f824bb3d42 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/basic.story.tsx @@ -0,0 +1,10 @@ +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/closed.story.tsx new file mode 100644 index 00000000000..33e6bd04bb0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/closed.story.tsx @@ -0,0 +1,20 @@ +import { forwardRef, type InputHTMLAttributes, type RefObject } from "react"; + +import * as StyledNumberInput from "./number-input"; + +export interface NumberInputProps extends StyledNumberInput.RootProps { + rootRef?: RefObject; + inputProps?: InputHTMLAttributes; +} + +export const NumberInput = forwardRef( + (props, ref) => { + const { inputProps, rootRef, ...rest } = props; + return ( + + + + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/number-input/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/controlled.story.tsx new file mode 100644 index 00000000000..049598c9e36 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/controlled.story.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { useState } from "react"; + +import * as NumberInput from "./number-input"; + +export const App = () => { + const [value, setValue] = useState("10"); + return ( + setValue(e.value)}> + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/disabled.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/disabled.story.tsx new file mode 100644 index 00000000000..d97cad9cc4a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/disabled.story.tsx @@ -0,0 +1,10 @@ +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/element.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/element.story.tsx new file mode 100644 index 00000000000..cb82fd6e9a1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/element.story.tsx @@ -0,0 +1,15 @@ +import { DollarSignIcon } from "lucide-react"; + +import { InputGroup } from "../input-group/input-group"; +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + }> + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/formatting.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/formatting.story.tsx new file mode 100644 index 00000000000..c04c96e04d0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/formatting.story.tsx @@ -0,0 +1,45 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/helper-text.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/helper-text.story.tsx new file mode 100644 index 00000000000..c4279e0c820 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/helper-text.story.tsx @@ -0,0 +1,17 @@ +import * as Field from "../field/field"; +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + Quantity + + + + + + Please enter a number between 5 and 50 + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/invalid.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/invalid.story.tsx new file mode 100644 index 00000000000..32c2af7c4c1 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/invalid.story.tsx @@ -0,0 +1,15 @@ +import * as Field from "../field/field"; +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + Quantity + + + + + The entry is invalid + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/min-max.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/min-max.story.tsx new file mode 100644 index 00000000000..2ffbb8672ee --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/min-max.story.tsx @@ -0,0 +1,10 @@ +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/mouse-wheel.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/mouse-wheel.story.tsx new file mode 100644 index 00000000000..2068c9f2284 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/mouse-wheel.story.tsx @@ -0,0 +1,10 @@ +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/number-input.recipe.ts b/libs/@hashintel/ds-components/src/beta/number-input/number-input.recipe.ts new file mode 100644 index 00000000000..357c10d43d5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/number-input.recipe.ts @@ -0,0 +1,109 @@ +import { numberInputAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +import { input } from "../input/input.recipe"; + +const trigger = { + alignItems: "center", + color: "fg.muted", + cursor: "pointer", + display: "flex", + flex: "1", + justifyContent: "center", + lineHeight: "1", + transition: "common", + userSelect: "none", + _icon: { + boxSize: "1em", + }, + _hover: { + bg: "gray.surface.bg.hover", + }, + _active: { + bg: "gray.surface.bg.active", + }, +}; + +export const numberInput = defineSlotRecipe({ + className: "number-input", + slots: numberInputAnatomy.keys(), + base: { + root: { + isolation: "isolate", + position: "relative", + _disabled: { + layerStyle: "disabled", + }, + }, + control: { + borderStartWidth: "1px", + display: "flex", + divideY: "1px", + flexDirection: "column", + height: "calc(100% - 2px)", + insetEnd: "0px", + margin: "1px", + position: "absolute", + top: "0", + width: "var(--stepper-width)", + zIndex: "1", + }, + input: { + ...input.base, + verticalAlign: "top", + pe: "calc(var(--stepper-width) + 0.5rem)", + }, + label: { + color: "fg.default", + fontWeight: "medium", + }, + incrementTrigger: { + ...trigger, + borderTopRightRadius: "l2", + }, + decrementTrigger: { + ...trigger, + borderBottomRightRadius: "l2", + }, + }, + defaultVariants: { + size: "md", + variant: "outline", + }, + variants: { + size: { + sm: { + control: { + "--stepper-width": "sizes.4.5", + }, + input: input.variants.size.sm, + }, + md: { + control: { + "--stepper-width": "sizes.5", + }, + input: input.variants.size.md, + }, + lg: { + control: { + "--stepper-width": "sizes.5.5", + }, + input: input.variants.size.lg, + }, + xl: { + control: { + "--stepper-width": "sizes.6", + }, + input: input.variants.size.xl, + }, + }, + variant: { + outline: { + input: input.variants.variant.outline, + }, + surface: { + input: input.variants.variant.surface, + }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/number-input/number-input.stories.ts b/libs/@hashintel/ds-components/src/beta/number-input/number-input.stories.ts new file mode 100644 index 00000000000..1df6402f677 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/number-input.stories.ts @@ -0,0 +1,14 @@ +export default { title: "Primitives/NumberInput" }; + +export { App as basic } from "./basic.story"; +export { App as controlled } from "./controlled.story"; +export { App as disabled } from "./disabled.story"; +export { App as element } from "./element.story"; +export { App as formatting } from "./formatting.story"; +export { App as helperText } from "./helper-text.story"; +export { App as invalid } from "./invalid.story"; +export { App as minMax } from "./min-max.story"; +export { App as mouseWheel } from "./mouse-wheel.story"; +export { App as scrubber } from "./scrubber.story"; +export { App as sizes } from "./sizes.story"; +export { App as step } from "./step.story"; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/number-input.tsx b/libs/@hashintel/ds-components/src/beta/number-input/number-input.tsx new file mode 100644 index 00000000000..cf219cfc347 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/number-input.tsx @@ -0,0 +1,45 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies */ + +import { NumberInput } from "@ark-ui/react/number-input"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { numberInput } from "@hashintel/ds-helpers/recipes"; +import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(numberInput); + +export type RootProps = ComponentProps; +export const Root = withProvider(NumberInput.Root, "root"); +export const RootProvider = withProvider(NumberInput.RootProvider, "root"); +export const DecrementTrigger = withContext( + NumberInput.DecrementTrigger, + "decrementTrigger", + { + defaultProps: { children: }, + }, +); +export const IncrementTrigger = withContext( + NumberInput.IncrementTrigger, + "incrementTrigger", + { + defaultProps: { children: }, + }, +); +export const Input = withContext(NumberInput.Input, "input"); +export const Label = withContext(NumberInput.Label, "label"); +export const Scrubber = withContext(NumberInput.Scrubber, "scrubber"); +export const ValueText = withContext(NumberInput.ValueText, "valueText"); +export const Control = withContext(NumberInput.Control, "control", { + defaultProps: { + children: ( + <> + + + + ), + }, +}); + +export { NumberInputContext as Context } from "@ark-ui/react/number-input"; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/scrubber.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/scrubber.story.tsx new file mode 100644 index 00000000000..0af19f7eae0 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/scrubber.story.tsx @@ -0,0 +1,21 @@ +import { ArrowLeftRightIcon } from "lucide-react"; + +import { InputGroup } from "../input-group/input-group"; +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + + } + > + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/sizes.story.tsx new file mode 100644 index 00000000000..266dc2f4bcb --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/sizes.story.tsx @@ -0,0 +1,17 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as NumberInput from "./number-input"; + +export const App = () => { + const sizes = ["sm", "md", "lg", "xl"] as const; + return ( + + {sizes.map((size) => ( + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/number-input/step.story.tsx b/libs/@hashintel/ds-components/src/beta/number-input/step.story.tsx new file mode 100644 index 00000000000..cc5754e4baa --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/number-input/step.story.tsx @@ -0,0 +1,10 @@ +import * as NumberInput from "./number-input"; + +export const App = () => { + return ( + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pagination/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/pagination/basic.story.tsx new file mode 100644 index 00000000000..80ee44d28fd --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pagination/basic.story.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +import { ButtonGroup } from "../button/button"; +import { IconButton } from "../icon-button/icon-button"; +import * as Pagination from "./pagination"; + +export const App = () => { + return ( + + + + + + + + + page.selected ? ( + {page.value} + ) : ( + + {page.value} + + ) + } + /> + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pagination/pagination.recipe.ts b/libs/@hashintel/ds-components/src/beta/pagination/pagination.recipe.ts new file mode 100644 index 00000000000..1080b1e3ac3 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pagination/pagination.recipe.ts @@ -0,0 +1,8 @@ +import { paginationAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const pagination = defineSlotRecipe({ + className: "pagination", + slots: paginationAnatomy.keys(), + base: {}, +}); diff --git a/libs/@hashintel/ds-components/src/beta/pagination/pagination.stories.ts b/libs/@hashintel/ds-components/src/beta/pagination/pagination.stories.ts new file mode 100644 index 00000000000..2fc870d6ee8 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pagination/pagination.stories.ts @@ -0,0 +1,3 @@ +export default { title: "Primitives/Pagination" }; + +export { App as basic } from "./basic.story"; diff --git a/libs/@hashintel/ds-components/src/beta/pagination/pagination.tsx b/libs/@hashintel/ds-components/src/beta/pagination/pagination.tsx new file mode 100644 index 00000000000..3b0bce4e6d4 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pagination/pagination.tsx @@ -0,0 +1,61 @@ +"use client"; + +/* eslint-disable import/no-extraneous-dependencies, react/no-array-index-key, @typescript-eslint/prefer-nullish-coalescing */ + +import { Pagination, usePaginationContext } from "@ark-ui/react/pagination"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { pagination } from "@hashintel/ds-helpers/recipes"; +import { EllipsisIcon } from "lucide-react"; +import type { ComponentProps } from "react"; + +import { IconButton } from "../icon-button/icon-button"; + +const { withProvider, withContext } = createStyleContext(pagination); + +export type RootProps = ComponentProps; +export const Root = withProvider(Pagination.Root, "root"); +export const RootProvider = withProvider(Pagination.RootProvider, "root"); +export const Item = withContext(Pagination.Item, "item"); +export const Ellipsis = withContext(Pagination.Ellipsis, "ellipsis"); +export const PrevTrigger = withContext(Pagination.PrevTrigger, "prevTrigger"); +export const NextTrigger = withContext(Pagination.NextTrigger, "nextTrigger"); + +export { PaginationContext as Context } from "@ark-ui/react/pagination"; + +export interface PaginationItemsProps + extends React.HTMLAttributes { + render: (page: { + type: "page"; + value: number; + selected: boolean; + }) => React.ReactNode; + ellipsis?: React.ReactElement | undefined; +} + +export const Items = (props: PaginationItemsProps) => { + const ctx = usePaginationContext(); + const { render, ellipsis, ...rest } = props; + + return ctx.pages.map((page, index) => { + if (page.type === "ellipsis") { + return ( + // @ts-expect-error - color prop type mismatch with rest spread + + {ellipsis || ( + // @ts-expect-error - "gray" colorPalette not in token set + + + + )} + + ); + } + + return ( + // @ts-expect-error - color prop type mismatch with rest spread + + {render({ ...page, selected: ctx.page === page.value })} + + ); + }); +}; diff --git a/libs/@hashintel/ds-components/src/beta/password-input/password-input.recipe.ts b/libs/@hashintel/ds-components/src/beta/password-input/password-input.recipe.ts new file mode 100644 index 00000000000..02e93e28ae7 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/password-input/password-input.recipe.ts @@ -0,0 +1,8 @@ +import { passwordInputAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +export const passwordInput = defineSlotRecipe({ + className: "password-input", + slots: passwordInputAnatomy.keys(), + base: {}, +}); diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/alphanumberic.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/alphanumberic.story.tsx new file mode 100644 index 00000000000..2780ec2839f --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/alphanumberic.story.tsx @@ -0,0 +1,15 @@ +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/attached.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/attached.story.tsx new file mode 100644 index 00000000000..17d03b48fea --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/attached.story.tsx @@ -0,0 +1,18 @@ +import { Group } from "../group/group"; +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/basic.story.tsx new file mode 100644 index 00000000000..9d95252080a --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/basic.story.tsx @@ -0,0 +1,15 @@ +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/closed.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/closed.story.tsx new file mode 100644 index 00000000000..383e52627f8 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/closed.story.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { forwardRef, type InputHTMLAttributes, type RefObject } from "react"; + +import { Group } from "../group/group"; +import * as StyledPinInput from "./pin-input"; + +export interface PinInputProps extends StyledPinInput.RootProps { + rootRef?: RefObject; + count?: number; + inputProps?: InputHTMLAttributes; + attached?: boolean; +} + +export const PinInput = forwardRef( + (props, ref) => { + const { count = 4, inputProps, rootRef, attached, ...rest } = props; + return ( + + + + + {Array.from({ length: count }).map((_, index) => ( + + ))} + + + + ); + }, +); diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/controlled.story.tsx new file mode 100644 index 00000000000..c9b069cb73d --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/controlled.story.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useState } from "react"; + +import * as PinInput from "./pin-input"; + +export const App = () => { + const [value, setValue] = useState(["", "", "", ""]); + return ( + setValue(e.value)}> + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/field.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/field.story.tsx new file mode 100644 index 00000000000..c4d438377e5 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/field.story.tsx @@ -0,0 +1,22 @@ +import * as Field from "../field/field"; +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + Pin + + + + + + + + + + + Enter the 4-digit pin sent to your email address. + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/mask.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/mask.story.tsx new file mode 100644 index 00000000000..3774ebef0df --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/mask.story.tsx @@ -0,0 +1,16 @@ +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + // @ts-expect-error TODO fix PinInput types + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/otp.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/otp.story.tsx new file mode 100644 index 00000000000..94f9b498353 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/otp.story.tsx @@ -0,0 +1,15 @@ +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.recipe.ts b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.recipe.ts new file mode 100644 index 00000000000..346107d474b --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.recipe.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ + +import { pinInputAnatomy } from "@ark-ui/react/anatomy"; +import { defineSlotRecipe } from "@pandacss/dev"; + +import { input } from "../input/input.recipe"; + +export const pinInput = defineSlotRecipe({ + className: "pin-input", + slots: pinInputAnatomy.keys(), + base: { + input: { + ...input.base, + textAlign: "center", + width: "var(--input-height)", + px: "1!", + }, + control: { + display: "inline-flex", + gap: "2", + isolation: "isolate", + }, + }, + defaultVariants: { + size: "md", + variant: "outline", + }, + + variants: { + size: { + xs: { + input: input.variants?.size?.xs, + }, + sm: { + input: input.variants?.size?.sm, + }, + md: { + input: input.variants?.size?.md, + }, + lg: { + input: input.variants?.size?.lg, + }, + xl: { + input: input.variants?.size?.xl, + }, + "2xl": { + input: input.variants?.size?.["2xl"], + }, + }, + variant: { + outline: { input: input.variants?.variant?.outline }, + subtle: { input: input.variants?.variant?.subtle }, + flushed: { input: input.variants?.variant?.flushed }, + }, + }, +}); diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.stories.ts b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.stories.ts new file mode 100644 index 00000000000..b3eec404a01 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.stories.ts @@ -0,0 +1,12 @@ +export default { title: "Primitives/PinInput" }; + +export { App as alphanumeric } from "./alphanumberic.story"; +export { App as attached } from "./attached.story"; +export { App as basic } from "./basic.story"; +export { App as controlled } from "./controlled.story"; +export { App as field } from "./field.story"; +export { App as mask } from "./mask.story"; +export { App as otp } from "./otp.story"; +export { App as placeholder } from "./placeholder.story"; +export { App as sizes } from "./sizes.story"; +export { App as variants } from "./variants.story"; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.tsx new file mode 100644 index 00000000000..0611467fa54 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/pin-input.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { PinInput } from "@ark-ui/react/pin-input"; +import { createStyleContext } from "@hashintel/ds-helpers/jsx"; +import { pinInput } from "@hashintel/ds-helpers/recipes"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(pinInput); + +export type RootProps = ComponentProps; +export const Root = withProvider(PinInput.Root, "root", { + forwardProps: ["mask"], +}); +export const RootProvider = withProvider(PinInput.RootProvider, "root"); +export const Control = withContext(PinInput.Control, "control"); +export const HiddenInput = PinInput.HiddenInput; +export const Input = withContext(PinInput.Input, "input"); +export const Label = withContext(PinInput.Label, "label"); + +export { PinInputContext as Context } from "@ark-ui/react/pin-input"; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/placeholder.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/placeholder.story.tsx new file mode 100644 index 00000000000..c3358adea55 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/placeholder.story.tsx @@ -0,0 +1,15 @@ +import * as PinInput from "./pin-input"; + +export const App = () => { + return ( + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/sizes.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/sizes.story.tsx new file mode 100644 index 00000000000..91d68c9bb82 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/sizes.story.tsx @@ -0,0 +1,22 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as PinInput from "./pin-input"; + +export const App = () => { + const sizes = ["sm", "md", "lg", "xl"] as const; + return ( + + {sizes.map((size) => ( + + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/pin-input/variants.story.tsx b/libs/@hashintel/ds-components/src/beta/pin-input/variants.story.tsx new file mode 100644 index 00000000000..8f3a930d408 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/pin-input/variants.story.tsx @@ -0,0 +1,22 @@ +import { Stack } from "@hashintel/ds-helpers/jsx"; + +import * as PinInput from "./pin-input"; + +export const App = () => { + const variants = ["outline", "subtle", "flushed"] as const; + return ( + + {variants.map((variant) => ( + + + + + + + + + + ))} + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/popover/basic.story.tsx b/libs/@hashintel/ds-components/src/beta/popover/basic.story.tsx new file mode 100644 index 00000000000..208ddefa276 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/popover/basic.story.tsx @@ -0,0 +1,29 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Popover from "./popover"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/popover/controlled.story.tsx b/libs/@hashintel/ds-components/src/beta/popover/controlled.story.tsx new file mode 100644 index 00000000000..44de1294f7e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/popover/controlled.story.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { Portal } from "@ark-ui/react/portal"; +import { useState } from "react"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Popover from "./popover"; + +export const App = () => { + const [open, setOpen] = useState(false); + return ( + setOpen(e.open)}> + + + + + + + + + Title + Description + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/popover/custom-background.story.tsx b/libs/@hashintel/ds-components/src/beta/popover/custom-background.story.tsx new file mode 100644 index 00000000000..7c8ad788714 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/popover/custom-background.story.tsx @@ -0,0 +1,29 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Popover from "./popover"; + +export const App = () => { + return ( + + + + + + + + + + Title + Description + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/popover/dialog.story.tsx b/libs/@hashintel/ds-components/src/beta/popover/dialog.story.tsx new file mode 100644 index 00000000000..f351721da3e --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/popover/dialog.story.tsx @@ -0,0 +1,55 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Dialog from "../dialog/dialog"; +import * as Popover from "./popover"; + +export const App = () => { + return ( + + + + + + + + + + + + + Popover in Dialog + + + This popover is inside a dialog. Click the button below to open + the + + + + + + + + + + + + + + Title + Description + + + + + + + + + + + + + ); +}; diff --git a/libs/@hashintel/ds-components/src/beta/popover/form.story.tsx b/libs/@hashintel/ds-components/src/beta/popover/form.story.tsx new file mode 100644 index 00000000000..11576d66ce6 --- /dev/null +++ b/libs/@hashintel/ds-components/src/beta/popover/form.story.tsx @@ -0,0 +1,47 @@ +import { Portal } from "@ark-ui/react/portal"; + +import { Button } from "../button/button"; +import { CloseButton } from "../close-button/close-button"; +import * as Field from "../field/field"; +import { Input } from "../input/input"; +import { Textarea } from "../textarea/textarea"; +import * as Popover from "./popover"; + +export const App = () => { + return ( + + + + + + + + + + Report an issue + + Please fill out the form below. + + + + + Title + + + + Description +