diff --git a/.opencode/agent/duplicate-pr.md b/.opencode/agent/duplicate-pr.md new file mode 100644 index 0000000000000000000000000000000000000000..c9c932ef790decab7113bdddb6508b454e5460e1 --- /dev/null +++ b/.opencode/agent/duplicate-pr.md @@ -0,0 +1,26 @@ +--- +mode: primary +hidden: true +model: opencode/claude-haiku-4-5 +color: "#E67E22" +tools: + "*": false + "github-pr-search": true +--- + +You are a duplicate PR detection agent. When a PR is opened, your job is to search for potentially duplicate or related open PRs. + +Use the github-pr-search tool to search for PRs that might be addressing the same issue or feature. + +IMPORTANT: The input will contain a line `CURRENT_PR_NUMBER: NNNN`. This is the current PR number, you should not mark that the current PR as a duplicate of itself. + +Search using keywords from the PR title and description. Try multiple searches with different relevant terms. + +If you find potential duplicates: + +- List them with their titles and URLs +- Briefly explain why they might be related + +If no duplicates are found, say so clearly. BUT ONLY SAY "No duplicate PRs found" (don't say anything else if no dups) + +Keep your response concise and actionable. diff --git a/.opencode/agent/triage.md b/.opencode/agent/triage.md new file mode 100644 index 0000000000000000000000000000000000000000..11c4c816cfae1b6fccd3993f35dae2bdaee34270 --- /dev/null +++ b/.opencode/agent/triage.md @@ -0,0 +1,43 @@ +--- +mode: primary +hidden: true +model: opencode/gpt-5.4-mini +color: "#44BA81" +tools: + "*": false + "github-triage": true +--- + +You are a triage agent responsible for triaging github issues. + +Use your github-triage tool to triage issues. + +This file is the source of truth for ownership/routing rules. + +Assign issues by choosing the team with the strongest overlap. The github-triage tool will assign a random member from that team. + +Do not add labels to issues. Only assign an owner. + +When calling github-triage, pass one of these team values: tui, desktop_web, core, inference, windows. + +## Teams + +### TUI + +Terminal UI issues, including rendering, keybindings, scrolling, terminal compatibility, SSH behavior, crashes in the TUI, and low-level TUI performance. + +### Desktop / Web + +Desktop application and browser-based app issues, including `opencode web`, desktop-specific UI behavior, packaging, and web view problems. + +### Core + +Core opencode server and harness issues, including sqlite, snapshots, memory, API behavior, agent context construction, tool execution, provider integrations, model behavior, documentation, and larger architectural features. + +### Inference + +OpenCode Zen, OpenCode Go, and billing issues. + +### Windows + +Windows-specific issues, including native Windows behavior, WSL interactions, path handling, shell compatibility, and installation or runtime problems that only happen on Windows. diff --git a/.opencode/command/ai-deps.md b/.opencode/command/ai-deps.md new file mode 100644 index 0000000000000000000000000000000000000000..83783d5b9be066b429367e12246370ea7b68d67c --- /dev/null +++ b/.opencode/command/ai-deps.md @@ -0,0 +1,24 @@ +--- +description: "Bump AI sdk dependencies minor / patch versions only" +--- + +Please read @package.json and @packages/opencode/package.json. + +Your job is to look into AI SDK dependencies, figure out if they have versions that can be upgraded (minor or patch versions ONLY no major ignore major changes). + +I want a report of every dependency and the version that can be upgraded to. +What would be even better is if you can give me brief summary of the changes for each dep and a link to the changelog for each dependency, or at least some reference info so I can see what bugs were fixed or new features were added. + +Consider using subagents for each dep to save your context window. + +Here is a short list of some deps (please be comprehensive tho): + +- "ai" +- "@ai-sdk/openai" +- "@ai-sdk/anthropic" +- "@openrouter/ai-sdk-provider" +- etc, etc + +DO NOT upgrade the dependencies yet, just make a list of all dependencies and their versions that can be upgraded to minor or patch versions only. + +Write up your findings to ai-sdk-updates.md diff --git a/.opencode/command/changelog.md b/.opencode/command/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..b28d963d005a855c9f222096950e21cfbb623ea2 --- /dev/null +++ b/.opencode/command/changelog.md @@ -0,0 +1,49 @@ +--- +model: opencode/gpt-5.4 +--- + +Create `UPCOMING_CHANGELOG.md` from the structured changelog input below. +If `UPCOMING_CHANGELOG.md` already exists, ignore its current contents completely. +Do not preserve, merge, or reuse text from the existing file. + +The input already contains the exact commit range since the last non-draft release. +The commits are already filtered to the release-relevant packages and grouped into +the release sections. Do not fetch GitHub releases, PRs, or build your own commit list. +The input may also include a `## Community Contributors Input` section. + +Before writing any entry you keep, inspect the real diff with +`git show --stat --format='' ` or `git show --format='' ` so you can +understand the actual code changes and not just the commit message (they may be misleading). +Do not use `git log` or author metadata when deciding attribution. + +Rules: + +- Write the final file with release sections in this order: + `## Core`, `## TUI`, `## Desktop`, `## SDK`, `## Extensions` +- Only include sections that have at least one notable entry +- Within each release section, keep bug fixes grouped under `### Bugfixes` +- Keep other notable entries under `### Improvements` when a section has bug fixes too +- Omit empty subsections +- Keep one bullet per commit you keep +- Skip commits that are entirely internal, CI, tests, refactors, or otherwise not user-facing +- Start each bullet with a capital letter +- Prefer what changed for users over what code changed internally +- Do not copy raw commit prefixes like `fix:` or `feat:` or trailing PR numbers like `(#123)` +- Community attribution is deterministic: only preserve an existing `(@username)` suffix from the changelog input +- If an input bullet has no `(@username)` suffix, do not add one +- Never add a new `(@username)` suffix from `git show`, commit authors, names, or email addresses +- If no notable entries remain and there is no contributor block, write exactly `No notable changes.` +- If no notable entries remain but there is a contributor block, omit all release sections and return only the contributor block +- If the input contains `## Community Contributors Input`, append the block below that heading to the end of the final file verbatim +- Do not add, remove, rewrite, or reorder contributor names or commit titles in that block +- Do not derive the thank-you section from the main summary bullets +- Do not include the heading `## Community Contributors Input` in the final file +- Focus on writing the least words to get your point across - users will skim read the changelog, so we should be precise + +**Importantly, the changelog is for users (who are at least slightly technical), they may use the TUI, Desktop, SDK, Plugins and so forth. Be thorough in understanding flow on effects may not be immediately apparent. e.g. a package upgrade looks internal but may patch a bug. Or a refactor may also stabilise some race condition that fixes bugs for users. The PR title/body + commit message will give you the authors context, usually containing the outcome not just technical detail** + + + +!`bun script/raw-changelog.ts $ARGUMENTS` + + diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md new file mode 100644 index 0000000000000000000000000000000000000000..e88932a24485ea8e4ef5b22dbb3306039e48cefb --- /dev/null +++ b/.opencode/command/commit.md @@ -0,0 +1,37 @@ +--- +description: git commit and push +model: opencode/kimi-k2.5 +subtask: true +--- + +commit and push + +make sure it includes a prefix like +docs: +tui: +core: +ci: +ignore: +wip: + +For anything in the packages/web use the docs: prefix. + +prefer to explain WHY something was done from an end user perspective instead of +WHAT was done. + +do not do generic messages like "improved agent experience" be very specific +about what user facing changes were made + +if there are conflicts DO NOT FIX THEM. notify me and I will fix them + +## GIT DIFF + +!`git diff` + +## GIT DIFF --cached + +!`git diff --cached` + +## GIT STATUS --short + +!`git status --short` diff --git a/.opencode/command/issues.md b/.opencode/command/issues.md new file mode 100644 index 0000000000000000000000000000000000000000..75b59616743f5677a33cd519f6b51f5f898056d1 --- /dev/null +++ b/.opencode/command/issues.md @@ -0,0 +1,23 @@ +--- +description: "find issue(s) on github" +model: opencode/claude-haiku-4-5 +--- + +Search through existing issues in anomalyco/opencode using the gh cli to find issues matching this query: + +$ARGUMENTS + +Consider: + +1. Similar titles or descriptions +2. Same error messages or symptoms +3. Related functionality or components +4. Similar feature requests + +Please list any matching issues with: + +- Issue number and title +- Brief explanation of why it matches the query +- Link to the issue + +If no clear matches are found, say so. diff --git a/.opencode/command/learn.md b/.opencode/command/learn.md new file mode 100644 index 0000000000000000000000000000000000000000..fe4965a5887e0e1c175f0b3d5961f343353eb9e4 --- /dev/null +++ b/.opencode/command/learn.md @@ -0,0 +1,42 @@ +--- +description: Extract non-obvious learnings from session to AGENTS.md files to build codebase understanding +--- + +Analyze this session and extract non-obvious learnings to add to AGENTS.md files. + +AGENTS.md files can exist at any directory level, not just the project root. When an agent reads a file, any AGENTS.md in parent directories are automatically loaded into the context of the tool read. Place learnings as close to the relevant code as possible: + +- Project-wide learnings → root AGENTS.md +- Package/module-specific → packages/foo/AGENTS.md +- Feature-specific → src/auth/AGENTS.md + +What counts as a learning (non-obvious discoveries only): + +- Hidden relationships between files or modules +- Execution paths that differ from how code appears +- Non-obvious configuration, env vars, or flags +- Debugging breakthroughs when error messages were misleading +- API/tool quirks and workarounds +- Build/test commands not in README +- Architectural decisions and constraints +- Files that must change together + +What NOT to include: + +- Obvious facts from documentation +- Standard language/framework behavior +- Things already in an AGENTS.md +- Verbose explanations +- Session-specific details + +Process: + +1. Review session for discoveries, errors that took multiple attempts, unexpected connections +2. Determine scope - what directory does each learning apply to? +3. Read existing AGENTS.md files at relevant levels +4. Create or update AGENTS.md at the appropriate level +5. Keep entries to 1-3 lines per insight + +After updating, summarize which AGENTS.md files were created/updated and how many learnings per file. + +$ARGUMENTS diff --git a/.opencode/command/rmslop.md b/.opencode/command/rmslop.md new file mode 100644 index 0000000000000000000000000000000000000000..02c9fc0844a7010572352e6f29efa97a965c6ee0 --- /dev/null +++ b/.opencode/command/rmslop.md @@ -0,0 +1,15 @@ +--- +description: Remove AI code slop +--- + +Check the diff against dev, and remove all AI generated slop introduced in this branch. + +This includes: + +- Extra comments that a human wouldn't add or is inconsistent with the rest of the file +- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) +- Casts to any to get around type issues +- Any other style that is inconsistent with the file +- Unnecessary emoji usage + +Report at the end with only a 1-3 sentence summary of what you changed diff --git a/.opencode/command/spellcheck.md b/.opencode/command/spellcheck.md new file mode 100644 index 0000000000000000000000000000000000000000..0abf23c4fd090d5350a65b2ffe406b3e5e4b3a80 --- /dev/null +++ b/.opencode/command/spellcheck.md @@ -0,0 +1,5 @@ +--- +description: spellcheck all markdown file changes +--- + +Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors. diff --git a/.opencode/command/translate.md b/.opencode/command/translate.md new file mode 100644 index 0000000000000000000000000000000000000000..de18ae2ee86c6a74581fcf2dfc2959df8fffb6be --- /dev/null +++ b/.opencode/command/translate.md @@ -0,0 +1,14 @@ +--- +description: translate English to other languages +model: opencode/gpt-5.6-sol +--- + +run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time. + +Requirements: + +- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure). +- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks. +- Also preserve every term listed in the Do-Not-Translate glossary below. +- Also apply locale-specific guidance from `.opencode/glossary/.md` when available (for example, `zh-cn.md`). +- Do not modify fenced code blocks. diff --git a/.opencode/glossary/README.md b/.opencode/glossary/README.md new file mode 100644 index 0000000000000000000000000000000000000000..983900381ca9497af774e0683c255e511f02f213 --- /dev/null +++ b/.opencode/glossary/README.md @@ -0,0 +1,63 @@ +# Locale Glossaries + +Use this folder for locale-specific translation guidance that supplements `.opencode/agent/translator.md`. + +The global glossary in `translator.md` remains the source of truth for shared do-not-translate terms (commands, code, paths, product names, etc.). These locale files capture community learnings about phrasing and terminology preferences. + +## File Naming + +- One file per locale +- Use lowercase locale slugs that match docs locales when possible (for example, `zh-cn.md`, `zh-tw.md`) +- If only language-level guidance exists, use the language code (for example, `fr.md`) +- Some repo locale slugs may be aliases/non-BCP47 for consistency (for example, `br` for Brazilian Portuguese / `pt-BR`) + +## What To Put In A Locale File + +- **Sources**: PRs/issues/discussions that motivated the guidance +- **Do Not Translate (Locale Additions)**: locale-specific terms or casing decisions +- **Preferred Terms**: recurring UI/docs words with preferred translations +- **Guidance**: tone, style, and consistency notes +- **Avoid** (optional): common literal translations or wording we should avoid +- If the repo uses a locale alias slug, document the alias in **Guidance** (for example, prose may mention `pt-BR` while config/examples use `br`) + +Prefer guidance that is: + +- Repeated across multiple docs/screens +- Easy to apply consistently +- Backed by a community contribution or review discussion + +## Template + +```md +# Glossary + +## Sources + +- PR #12345: https://github.com/anomalyco/opencode/pull/12345 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing) + +## Preferred Terms + +| English | Preferred | Notes | +| ------- | --------- | --------- | +| prompt | ... | preferred | +| session | ... | preferred | + +## Guidance + +- Prefer natural phrasing over literal translation + +## Avoid + +- Avoid ... when ... +``` + +## Contribution Notes + +- Mark entries as preferred when they may evolve +- Keep examples short +- Add or update the `Sources` section whenever you add a new rule +- Prefer PR-backed guidance over invented term mappings; start with general guidance if no term-level corrections exist yet diff --git a/.opencode/glossary/ar.md b/.opencode/glossary/ar.md new file mode 100644 index 0000000000000000000000000000000000000000..37355522a0a5dba08459b674ca6d643037381c0a --- /dev/null +++ b/.opencode/glossary/ar.md @@ -0,0 +1,28 @@ +# ar Glossary + +## Sources + +- PR #9947: https://github.com/anomalyco/opencode/pull/9947 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Arabic phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- For RTL text, treat code, commands, and paths as LTR artifacts and keep their character order unchanged + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Arabic terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/br.md b/.opencode/glossary/br.md new file mode 100644 index 0000000000000000000000000000000000000000..fd3e7251cd907538c47246faf6156748b579a612 --- /dev/null +++ b/.opencode/glossary/br.md @@ -0,0 +1,34 @@ +# br Glossary + +## Sources + +- PR #10086: https://github.com/anomalyco/opencode/pull/10086 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Locale code `br` in repo config, code, and paths (repo alias for Brazilian Portuguese) + +## Preferred Terms + +These are PR-backed locale naming preferences and may evolve. + +| English / Context | Preferred | Notes | +| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------- | +| Brazilian Portuguese (prose locale name) | `pt-BR` | Use standard locale naming in prose when helpful | +| Repo locale slug (code/config) | `br` | PR #10086 uses `br` for consistency/simplicity | +| Browser locale detection | `pt`, `pt-br`, `pt-BR` -> `br` | Preserve this mapping in docs/examples about locale detection | + +## Guidance + +- This file covers Brazilian Portuguese (`pt-BR`), but the repo locale code is `br` +- Use natural Brazilian Portuguese phrasing over literal translation +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- Keep repo locale identifiers as implemented in code/config (`br`) even when prose mentions `pt-BR` + +## Avoid + +- Avoid changing repo locale code references from `br` to `pt-br` in code snippets, paths, or config examples +- Avoid mixing Portuguese variants when a Brazilian Portuguese form is established diff --git a/.opencode/glossary/bs.md b/.opencode/glossary/bs.md new file mode 100644 index 0000000000000000000000000000000000000000..aa3bd96f6f94b7f03facc47a6fc3ec1b972619d7 --- /dev/null +++ b/.opencode/glossary/bs.md @@ -0,0 +1,33 @@ +# bs Glossary + +## Sources + +- PR #12283: https://github.com/anomalyco/opencode/pull/12283 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +These are PR-backed locale naming preferences and may evolve. + +| English / Context | Preferred | Notes | +| ---------------------------------- | ---------- | ------------------------------------------------- | +| Bosnian language label (UI) | `Bosanski` | PR #12283 tested switching language to `Bosanski` | +| Repo locale slug (code/config) | `bs` | Preserve in code, config, paths, and examples | +| Browser locale detection (Bosnian) | `bs` | PR #12283 added `bs` locale auto-detection | + +## Guidance + +- Use natural Bosnian phrasing over literal translation +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- Keep repo locale references as `bs` in code/config, and use `Bosanski` for the user-facing language name when applicable + +## Avoid + +- Avoid changing repo locale references from `bs` to another slug in code snippets or config examples +- Avoid translating product and protocol names that are fixed identifiers diff --git a/.opencode/glossary/da.md b/.opencode/glossary/da.md new file mode 100644 index 0000000000000000000000000000000000000000..e63222170109dac5724af26db54b93bb1c4bbb70 --- /dev/null +++ b/.opencode/glossary/da.md @@ -0,0 +1,27 @@ +# da Glossary + +## Sources + +- PR #9821: https://github.com/anomalyco/opencode/pull/9821 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Danish phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Danish terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/de.md b/.opencode/glossary/de.md new file mode 100644 index 0000000000000000000000000000000000000000..0d2c49faceaec84ee19170044ef4c92f8f4f508f --- /dev/null +++ b/.opencode/glossary/de.md @@ -0,0 +1,27 @@ +# de Glossary + +## Sources + +- PR #9817: https://github.com/anomalyco/opencode/pull/9817 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural German phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple German terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/es.md b/.opencode/glossary/es.md new file mode 100644 index 0000000000000000000000000000000000000000..dc9b977ecffa48dc0b043efbfffb25d16ba1fd55 --- /dev/null +++ b/.opencode/glossary/es.md @@ -0,0 +1,27 @@ +# es Glossary + +## Sources + +- PR #9817: https://github.com/anomalyco/opencode/pull/9817 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Spanish phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Spanish terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/fr.md b/.opencode/glossary/fr.md new file mode 100644 index 0000000000000000000000000000000000000000..074c4de110a06ce48d36bf90cc7b05515b157090 --- /dev/null +++ b/.opencode/glossary/fr.md @@ -0,0 +1,27 @@ +# fr Glossary + +## Sources + +- PR #9821: https://github.com/anomalyco/opencode/pull/9821 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural French phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple French terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/ja.md b/.opencode/glossary/ja.md new file mode 100644 index 0000000000000000000000000000000000000000..f0159ca96690b03f3a78825e0494505eabacc8c3 --- /dev/null +++ b/.opencode/glossary/ja.md @@ -0,0 +1,33 @@ +# ja Glossary + +## Sources + +- PR #9821: https://github.com/anomalyco/opencode/pull/9821 +- PR #13160: https://github.com/anomalyco/opencode/pull/13160 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +These are PR-backed wording preferences and may evolve. + +| English / Context | Preferred | Notes | +| --------------------------- | ----------------------- | ------------------------------------- | +| WSL integration (UI label) | `WSL連携` | PR #13160 prefers this over `WSL統合` | +| WSL integration description | `WindowsのWSL環境で...` | PR #13160 improved phrasing naturally | + +## Guidance + +- Prefer natural Japanese phrasing over literal translation +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- In WSL integration text, follow PR #13160 wording direction for more natural Japanese phrasing + +## Avoid + +- Avoid `WSL統合` in the WSL integration UI context where `WSL連携` is the reviewed wording +- Avoid translating product and protocol names that are fixed identifiers diff --git a/.opencode/glossary/ko.md b/.opencode/glossary/ko.md new file mode 100644 index 0000000000000000000000000000000000000000..71385c8a10acc7edf88c1adbe33dbc3347058805 --- /dev/null +++ b/.opencode/glossary/ko.md @@ -0,0 +1,27 @@ +# ko Glossary + +## Sources + +- PR #9817: https://github.com/anomalyco/opencode/pull/9817 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Korean phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Korean terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/no.md b/.opencode/glossary/no.md new file mode 100644 index 0000000000000000000000000000000000000000..d7159dca4107dbb8033e937597d411eb36b32289 --- /dev/null +++ b/.opencode/glossary/no.md @@ -0,0 +1,38 @@ +# no Glossary + +## Sources + +- PR #10018: https://github.com/anomalyco/opencode/pull/10018 +- PR #12935: https://github.com/anomalyco/opencode/pull/12935 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Sound names (PR #10018 notes these were intentionally left untranslated) + +## Preferred Terms + +These are PR-backed corrections and may evolve. + +| English / Context | Preferred | Notes | +| ----------------------------------- | ------------ | ----------------------------- | +| Save (data persistence action) | `Lagre` | Prefer over `Spare` | +| Disabled (feature/state) | `deaktivert` | Prefer over `funksjonshemmet` | +| API keys | `API Nøkler` | Prefer over `API Taster` | +| Cost (noun) | `Kostnad` | Prefer over verb form `Koste` | +| Show/View (imperative button label) | `Vis` | Prefer over `Utsikt` | + +## Guidance + +- Prefer natural Norwegian Bokmal (Bokmål) wording over literal translation +- Keep tone clear and practical in UI labels +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- Keep recurring UI terms consistent once a preferred term is chosen + +## Avoid + +- Avoid `Spare` for save actions in persistence contexts +- Avoid `funksjonshemmet` for disabled feature states +- Avoid `API Taster`, `Koste`, and `Utsikt` in the corrected contexts above diff --git a/.opencode/glossary/pl.md b/.opencode/glossary/pl.md new file mode 100644 index 0000000000000000000000000000000000000000..e9bad7a515672d383059154bf5860e2490fecefe --- /dev/null +++ b/.opencode/glossary/pl.md @@ -0,0 +1,27 @@ +# pl Glossary + +## Sources + +- PR #9884: https://github.com/anomalyco/opencode/pull/9884 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Polish phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Polish terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/ru.md b/.opencode/glossary/ru.md new file mode 100644 index 0000000000000000000000000000000000000000..6fee0f94c06f471e56464a22b4c098721c0ac41e --- /dev/null +++ b/.opencode/glossary/ru.md @@ -0,0 +1,27 @@ +# ru Glossary + +## Sources + +- PR #9882: https://github.com/anomalyco/opencode/pull/9882 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections. + +## Guidance + +- Prefer natural Russian phrasing over literal translation +- Keep tone clear and direct in UI labels and docs prose +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths + +## Avoid + +- Avoid translating product and protocol names that are fixed identifiers +- Avoid mixing multiple Russian terms for the same recurring UI action once a preferred term is established diff --git a/.opencode/glossary/th.md b/.opencode/glossary/th.md new file mode 100644 index 0000000000000000000000000000000000000000..7b5a31d16bfca36f53fa4603121467fb28df8b78 --- /dev/null +++ b/.opencode/glossary/th.md @@ -0,0 +1,34 @@ +# th Glossary + +## Sources + +- PR #10809: https://github.com/anomalyco/opencode/pull/10809 +- PR #11496: https://github.com/anomalyco/opencode/pull/11496 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code) +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +These are PR-backed preferences and may evolve. + +| English / Context | Preferred | Notes | +| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------- | +| Thai language label in language lists | `ไทย` | PR #10809 standardized this across locales | +| Language names in language pickers | Native names (static) | PR #11496: keep names like `English`, `Deutsch`, `ไทย` consistent across locales | + +## Guidance + +- Prefer natural Thai phrasing over literal translation +- Keep tone short and clear for buttons and labels +- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths +- Keep language names static/native in language pickers instead of translating them per current locale (PR #11496) + +## Avoid + +- Avoid translating language names differently per current locale in language lists +- Avoid changing `ไทย` to another display form for the Thai language option unless the product standard changes diff --git a/.opencode/glossary/tr.md b/.opencode/glossary/tr.md new file mode 100644 index 0000000000000000000000000000000000000000..72b1cdfb40b1de6583318f2f7d23967ec0e9376d --- /dev/null +++ b/.opencode/glossary/tr.md @@ -0,0 +1,38 @@ +# tr Glossary + +## Sources + +- PR #15835: https://github.com/anomalyco/opencode/pull/15835 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose, docs, and UI copy) +- Keep lowercase `opencode` in commands, package names, paths, URLs, and other exact identifiers +- `` stays the literal key token in code blocks; use `Tab` for the nearby explanatory label in prose +- Commands, flags, file paths, and code literals (keep exactly as written) + +## Preferred Terms + +These are PR-backed wording preferences and may evolve. + +| English / Context | Preferred | Notes | +| ------------------------- | --------------------------------------- | ------------------------------------------------------------- | +| available in beta | `beta olarak mevcut` | Prefer this over `beta olarak kullanılabilir` | +| privacy-first | `Gizlilik öncelikli tasarlandı` | Prefer this over `Önce gizlilik için tasarlandı` | +| connect your local models | `yerel modellerinizi bağlayabilirsiniz` | Use the fuller, more direct action phrase | +| `` key label | `Tab` | Use `Tab` in prose; keep `` in literal UI or code blocks | +| cross-platform | `cross-platform (tüm platformlarda)` | Keep the English term, add a short clarification when helpful | + +## Guidance + +- Prefer natural Turkish phrasing over literal translation +- Merge broken sentence fragments into one clear sentence when the source is a single thought +- Keep product naming consistent: `OpenCode` in prose, `opencode` only for exact technical identifiers +- When an English technical term is intentionally kept, add a short Turkish clarification only if it improves readability + +## Avoid + +- Avoid `beta olarak kullanılabilir` when `beta olarak mevcut` fits +- Avoid `Önce gizlilik için tasarlandı`; use the more natural reviewed wording instead +- Avoid `Sekme` for the translated key label in prose when referring to `` +- Avoid changing `opencode` to `OpenCode` inside commands, URLs, package names, or code literals diff --git a/.opencode/glossary/zh-cn.md b/.opencode/glossary/zh-cn.md new file mode 100644 index 0000000000000000000000000000000000000000..054e94b7e83af8c337bb81d7894340c8f85344a6 --- /dev/null +++ b/.opencode/glossary/zh-cn.md @@ -0,0 +1,42 @@ +# zh-cn Glossary + +## Sources + +- PR #13942: https://github.com/anomalyco/opencode/pull/13942 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code) +- `OpenCode Zen` +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- `Model Context Protocol` (prefer the English expansion when introducing `MCP`) + +## Preferred Terms + +These are preferred terms for docs/UI prose and may evolve. + +| English | Preferred | Notes | +| ----------------------- | --------- | ------------------------------------------- | +| prompt | 提示词 | Keep `--prompt` unchanged in flags/code | +| session | 会话 | | +| provider | 提供商 | | +| share link / shared URL | 分享链接 | Prefer `分享` for user-facing share actions | +| headless (server) | 无界面 | Docs wording | +| authentication | 认证 | Prefer in auth/OAuth contexts | +| cache | 缓存 | | +| keybind / shortcut | 快捷键 | User-facing docs wording | +| workflow | 工作流 | e.g. GitHub Actions workflow | + +## Guidance + +- Prefer natural, concise phrasing over literal translation +- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction) +- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs +- Keep enum-like values in English when they are literals (for example, `default`, `json`) +- Prefer consistent terminology across pages once a term is chosen (`会话`, `提供商`, `提示词`, etc.) + +## Avoid + +- Avoid `opencode` in prose when referring to the product name; use `OpenCode` +- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established diff --git a/.opencode/glossary/zh-tw.md b/.opencode/glossary/zh-tw.md new file mode 100644 index 0000000000000000000000000000000000000000..283660e121980dffd8619b2c2b56358ffaa99eed --- /dev/null +++ b/.opencode/glossary/zh-tw.md @@ -0,0 +1,42 @@ +# zh-tw Glossary + +## Sources + +- PR #13942: https://github.com/anomalyco/opencode/pull/13942 + +## Do Not Translate (Locale Additions) + +- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code) +- `OpenCode Zen` +- `OpenCode CLI` +- `CLI`, `TUI`, `MCP`, `OAuth` +- `Model Context Protocol` (prefer the English expansion when introducing `MCP`) + +## Preferred Terms + +These are preferred terms for docs/UI prose and may evolve. + +| English | Preferred | Notes | +| ----------------------- | --------- | ------------------------------------------- | +| prompt | 提示詞 | Keep `--prompt` unchanged in flags/code | +| session | 工作階段 | | +| provider | 供應商 | | +| share link / shared URL | 分享連結 | Prefer `分享` for user-facing share actions | +| headless (server) | 無介面 | Docs wording | +| authentication | 認證 | Prefer in auth/OAuth contexts | +| cache | 快取 | | +| keybind / shortcut | 快捷鍵 | User-facing docs wording | +| workflow | 工作流程 | e.g. GitHub Actions workflow | + +## Guidance + +- Prefer natural, concise phrasing over literal translation +- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction) +- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs +- Keep enum-like values in English when they are literals (for example, `default`, `json`) +- Prefer consistent terminology across pages once a term is chosen (`工作階段`, `供應商`, `提示詞`, etc.) + +## Avoid + +- Avoid `opencode` in prose when referring to the product name; use `OpenCode` +- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established diff --git a/.opencode/plugins/smoke-theme.json b/.opencode/plugins/smoke-theme.json new file mode 100644 index 0000000000000000000000000000000000000000..6e4595d44657b841d869fe10d826ebc93eff3a9e --- /dev/null +++ b/.opencode/plugins/smoke-theme.json @@ -0,0 +1,223 @@ +{ + "$schema": "https://opencode.ai/theme.json", + "defs": { + "nord0": "#2E3440", + "nord1": "#3B4252", + "nord2": "#434C5E", + "nord3": "#4C566A", + "nord4": "#D8DEE9", + "nord5": "#E5E9F0", + "nord6": "#ECEFF4", + "nord7": "#8FBCBB", + "nord8": "#88C0D0", + "nord9": "#81A1C1", + "nord10": "#5E81AC", + "nord11": "#BF616A", + "nord12": "#D08770", + "nord13": "#EBCB8B", + "nord14": "#A3BE8C", + "nord15": "#B48EAD" + }, + "theme": { + "primary": { + "dark": "nord10", + "light": "nord9" + }, + "secondary": { + "dark": "nord9", + "light": "nord9" + }, + "accent": { + "dark": "nord7", + "light": "nord7" + }, + "error": { + "dark": "nord11", + "light": "nord11" + }, + "warning": { + "dark": "nord12", + "light": "nord12" + }, + "success": { + "dark": "nord14", + "light": "nord14" + }, + "info": { + "dark": "nord8", + "light": "nord10" + }, + "text": { + "dark": "nord6", + "light": "nord0" + }, + "textMuted": { + "dark": "#8B95A7", + "light": "nord1" + }, + "background": { + "dark": "nord0", + "light": "nord6" + }, + "backgroundPanel": { + "dark": "nord1", + "light": "nord5" + }, + "backgroundElement": { + "dark": "nord2", + "light": "nord4" + }, + "border": { + "dark": "nord2", + "light": "nord3" + }, + "borderActive": { + "dark": "nord3", + "light": "nord2" + }, + "borderSubtle": { + "dark": "nord2", + "light": "nord3" + }, + "diffAdded": { + "dark": "nord14", + "light": "nord14" + }, + "diffRemoved": { + "dark": "nord11", + "light": "nord11" + }, + "diffContext": { + "dark": "#8B95A7", + "light": "nord3" + }, + "diffHunkHeader": { + "dark": "#8B95A7", + "light": "nord3" + }, + "diffHighlightAdded": { + "dark": "nord14", + "light": "nord14" + }, + "diffHighlightRemoved": { + "dark": "nord11", + "light": "nord11" + }, + "diffAddedBg": { + "dark": "#36413C", + "light": "#E6EBE7" + }, + "diffRemovedBg": { + "dark": "#43393D", + "light": "#ECE6E8" + }, + "diffContextBg": { + "dark": "nord1", + "light": "nord5" + }, + "diffLineNumber": { + "dark": "nord2", + "light": "nord4" + }, + "diffAddedLineNumberBg": { + "dark": "#303A35", + "light": "#DDE4DF" + }, + "diffRemovedLineNumberBg": { + "dark": "#3C3336", + "light": "#E4DDE0" + }, + "markdownText": { + "dark": "nord4", + "light": "nord0" + }, + "markdownHeading": { + "dark": "nord8", + "light": "nord10" + }, + "markdownLink": { + "dark": "nord9", + "light": "nord9" + }, + "markdownLinkText": { + "dark": "nord7", + "light": "nord7" + }, + "markdownCode": { + "dark": "nord14", + "light": "nord14" + }, + "markdownBlockQuote": { + "dark": "#8B95A7", + "light": "nord3" + }, + "markdownEmph": { + "dark": "nord12", + "light": "nord12" + }, + "markdownStrong": { + "dark": "nord13", + "light": "nord13" + }, + "markdownHorizontalRule": { + "dark": "#8B95A7", + "light": "nord3" + }, + "markdownListItem": { + "dark": "nord8", + "light": "nord10" + }, + "markdownListEnumeration": { + "dark": "nord7", + "light": "nord7" + }, + "markdownImage": { + "dark": "nord9", + "light": "nord9" + }, + "markdownImageText": { + "dark": "nord7", + "light": "nord7" + }, + "markdownCodeBlock": { + "dark": "nord4", + "light": "nord0" + }, + "syntaxComment": { + "dark": "#8B95A7", + "light": "nord3" + }, + "syntaxKeyword": { + "dark": "nord9", + "light": "nord9" + }, + "syntaxFunction": { + "dark": "nord8", + "light": "nord8" + }, + "syntaxVariable": { + "dark": "nord7", + "light": "nord7" + }, + "syntaxString": { + "dark": "nord14", + "light": "nord14" + }, + "syntaxNumber": { + "dark": "nord15", + "light": "nord15" + }, + "syntaxType": { + "dark": "nord7", + "light": "nord7" + }, + "syntaxOperator": { + "dark": "nord9", + "light": "nord9" + }, + "syntaxPunctuation": { + "dark": "nord4", + "light": "nord0" + } + } +} diff --git a/.opencode/plugins/tui-smoke.tsx b/.opencode/plugins/tui-smoke.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2d3095a57cf1287f17ed8c0064814cec468d7d71 --- /dev/null +++ b/.opencode/plugins/tui-smoke.tsx @@ -0,0 +1,1019 @@ +/** @jsxImportSource @opentui/solid */ +import { useTerminalDimensions, type JSX } from "@opentui/solid" +import { useBindings, useKeymapSelector } from "@opentui/keymap/solid" +import { RGBA, VignetteEffect, type KeyEvent, type Renderable } from "@opentui/core" +import { createBindingLookup, type BindingConfig } from "@opentui/keymap/extras" +import type { TuiPlugin, TuiPluginApi, TuiPluginMeta, TuiPluginModule, TuiSlotPlugin } from "@opencode-ai/plugin/tui" + +const tabs = ["overview", "counter", "help"] +const command = { + modal: "smoke_modal", + screen: "smoke_screen", + alert: "smoke_alert", + confirm: "smoke_confirm", + prompt: "smoke_prompt", + select: "smoke_select", + host: "smoke_host", + home: "smoke_home", + toast: "smoke_toast", + dialog_close: "smoke_dialog_close", + local_push: "smoke_local_push", + local_pop: "smoke_local_pop", + screen_home: "smoke_screen_home", + screen_left: "smoke_screen_left", + screen_right: "smoke_screen_right", + screen_up: "smoke_screen_up", + screen_down: "smoke_screen_down", + screen_modal: "smoke_screen_modal", + screen_local: "smoke_screen_local", + screen_host: "smoke_screen_host", + screen_alert: "smoke_screen_alert", + screen_confirm: "smoke_screen_confirm", + screen_prompt: "smoke_screen_prompt", + screen_select: "smoke_screen_select", + modal_accept: "smoke_modal_accept", + modal_close: "smoke_modal_close", +} + +type SmokeBindings = BindingConfig + +const defaultKeymap = { + [command.modal]: "ctrl+shift+m", + [command.screen]: "ctrl+shift+o", + [command.dialog_close]: "escape", + [command.local_push]: "enter,return", + [command.local_pop]: "escape,q,backspace", + [command.screen_home]: "escape,ctrl+h", + [command.screen_left]: "left,h", + [command.screen_right]: "right,l", + [command.screen_up]: "up,k", + [command.screen_down]: "down,j", + [command.screen_modal]: "ctrl+shift+m", + [command.screen_local]: "x", + [command.screen_host]: "z", + [command.screen_alert]: "a", + [command.screen_confirm]: "c", + [command.screen_prompt]: "p", + [command.screen_select]: "s", + [command.modal_accept]: "enter,return", + [command.modal_close]: "escape", +} + +const pick = (value: unknown, fallback: string) => { + if (typeof value !== "string") return fallback + if (!value.trim()) return fallback + return value +} + +const num = (value: unknown, fallback: number) => { + if (typeof value !== "number") return fallback + return value +} + +const record = (value: unknown): value is Record => + !!value && typeof value === "object" && !Array.isArray(value) + +type Cfg = { + label: string + route: string + vignette: number + keybinds: SmokeBindings | undefined +} + +type Route = { + modal: string + screen: string +} + +type State = { + tab: number + count: number + source: string + note: string + selected: string + local: number +} + +const cfg = (options: Record | undefined) => { + return { + label: pick(options?.label, "smoke"), + route: pick(options?.route, "workspace-smoke"), + vignette: Math.max(0, num(options?.vignette, 0.35)), + keybinds: record(options?.keybinds) ? (options.keybinds as SmokeBindings) : undefined, + } +} + +const names = (input: Cfg) => { + return { + modal: `${input.route}.modal`, + screen: `${input.route}.screen`, + } +} + +function createKeys(input: SmokeBindings | undefined) { + return createBindingLookup({ ...defaultKeymap, ...input }) +} + +type Keys = ReturnType + +const ui = { + panel: "#1d1d1d", + border: "#4a4a4a", + text: "#f0f0f0", + muted: "#a5a5a5", + accent: "#5f87ff", +} + +type Color = RGBA | string + +const ink = (map: Record, name: string, fallback: string): Color => { + const value = map[name] + if (typeof value === "string") return value + if (value instanceof RGBA) return value + return fallback +} + +const look = (map: Record) => { + return { + panel: ink(map, "backgroundPanel", ui.panel), + border: ink(map, "border", ui.border), + text: ink(map, "text", ui.text), + muted: ink(map, "textMuted", ui.muted), + accent: ink(map, "primary", ui.accent), + selected: ink(map, "selectedListItemText", ui.text), + } +} + +const tone = (api: TuiPluginApi) => { + return look(api.theme.current) +} + +type Skin = { + panel: Color + border: Color + text: Color + muted: Color + accent: Color + selected: Color +} + +const Btn = (props: { txt: string; run: () => void; skin: Skin; on?: boolean }) => { + return ( + { + props.run() + }} + backgroundColor={props.on ? props.skin.accent : props.skin.border} + paddingLeft={1} + paddingRight={1} + > + {props.txt} + + ) +} + +const parse = (params: Record | undefined) => { + const tab = typeof params?.tab === "number" ? params.tab : 0 + const count = typeof params?.count === "number" ? params.count : 0 + const source = typeof params?.source === "string" ? params.source : "unknown" + const note = typeof params?.note === "string" ? params.note : "" + const selected = typeof params?.selected === "string" ? params.selected : "" + const local = typeof params?.local === "number" ? params.local : 0 + return { + tab: Math.max(0, Math.min(tab, tabs.length - 1)), + count, + source, + note, + selected, + local: Math.max(0, local), + } +} + +const current = (api: TuiPluginApi, route: Route) => { + const value = api.route.current + const ok = Object.values(route).includes(value.name) + if (!ok) return parse(undefined) + if (!("params" in value)) return parse(undefined) + return parse(value.params) +} + +const opts = [ + { + title: "Overview", + value: 0, + description: "Switch to overview tab", + }, + { + title: "Counter", + value: 1, + description: "Switch to counter tab", + }, + { + title: "Help", + value: 2, + description: "Switch to help tab", + }, +] + +const host = (api: TuiPluginApi, input: Cfg, skin: Skin) => { + api.ui.dialog.setSize("medium") + api.ui.dialog.replace(() => ( + + + {input.label} host overlay + + Using api.ui.dialog stack with built-in backdrop + esc closes · depth {api.ui.dialog.depth} + + api.ui.dialog.clear()} skin={skin} on /> + + + )) +} + +const warn = (api: TuiPluginApi, route: Route, value: State) => { + const DialogAlert = api.ui.DialogAlert + api.ui.dialog.setSize("medium") + api.ui.dialog.replace(() => ( + api.route.navigate(route.screen, { ...value, source: "alert" })} + /> + )) +} + +const check = (api: TuiPluginApi, route: Route, value: State) => { + const DialogConfirm = api.ui.DialogConfirm + api.ui.dialog.setSize("medium") + api.ui.dialog.replace(() => ( + api.route.navigate(route.screen, { ...value, count: value.count + 1, source: "confirm" })} + onCancel={() => api.route.navigate(route.screen, { ...value, source: "confirm-cancel" })} + /> + )) +} + +const entry = (api: TuiPluginApi, route: Route, value: State) => { + const DialogPrompt = api.ui.DialogPrompt + api.ui.dialog.setSize("medium") + api.ui.dialog.replace(() => ( + { + api.ui.dialog.clear() + api.route.navigate(route.screen, { ...value, note, source: "prompt" }) + }} + onCancel={() => { + api.ui.dialog.clear() + api.route.navigate(route.screen, value) + }} + /> + )) +} + +const picker = (api: TuiPluginApi, route: Route, value: State) => { + const DialogSelect = api.ui.DialogSelect + api.ui.dialog.setSize("medium") + api.ui.dialog.replace(() => ( + { + api.ui.dialog.clear() + api.route.navigate(route.screen, { + ...value, + tab: typeof item.value === "number" ? item.value : value.tab, + selected: item.title, + source: "select", + }) + }} + /> + )) +} + +const Screen = (props: { + api: TuiPluginApi + input: Cfg + route: Route + keys: Keys + meta: TuiPluginMeta + params?: Record +}) => { + const dim = useTerminalDimensions() + const value = parse(props.params) + const skin = tone(props.api) + const set = (local: number, base?: State) => { + const next = base ?? current(props.api, props.route) + props.api.route.navigate(props.route.screen, { ...next, local: Math.max(0, local), source: "local" }) + } + const push = (base?: State) => { + const next = base ?? current(props.api, props.route) + set(next.local + 1, next) + } + const open = () => { + const next = current(props.api, props.route) + if (next.local > 0) return + set(1, next) + } + const pop = (base?: State) => { + const next = base ?? current(props.api, props.route) + set(Math.max(0, next.local - 1), next) + } + const show = () => { + setTimeout(() => { + open() + }, 0) + } + const screenActive = () => props.api.route.current.name === props.route.screen + + useBindings(() => ({ + enabled: () => screenActive() && props.api.ui.dialog.open, + commands: [ + { + name: command.dialog_close, + run() { + props.api.ui.dialog.clear() + }, + }, + ], + bindings: props.keys.gather("smoke.dialog", [command.dialog_close]), + })) + + useBindings(() => ({ + enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local > 0, + commands: [ + { + name: command.local_push, + run() { + push(current(props.api, props.route)) + }, + }, + { + name: command.local_pop, + run() { + pop(current(props.api, props.route)) + }, + }, + ], + bindings: props.keys.gather("smoke.local", [command.local_push, command.local_pop]), + })) + + useBindings(() => ({ + enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local === 0, + commands: [ + { + name: command.screen_home, + run() { + props.api.route.navigate("home") + }, + }, + { + name: command.screen_left, + run() { + const next = current(props.api, props.route) + props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length }) + }, + }, + { + name: command.screen_right, + run() { + const next = current(props.api, props.route) + props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length }) + }, + }, + { + name: command.screen_up, + run() { + const next = current(props.api, props.route) + props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 }) + }, + }, + { + name: command.screen_down, + run() { + const next = current(props.api, props.route) + props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 }) + }, + }, + { + name: command.screen_modal, + run() { + props.api.route.navigate(props.route.modal, current(props.api, props.route)) + }, + }, + { + name: command.screen_local, + run() { + open() + }, + }, + { + name: command.screen_host, + run() { + host(props.api, props.input, skin) + }, + }, + { + name: command.screen_alert, + run() { + warn(props.api, props.route, current(props.api, props.route)) + }, + }, + { + name: command.screen_confirm, + run() { + check(props.api, props.route, current(props.api, props.route)) + }, + }, + { + name: command.screen_prompt, + run() { + entry(props.api, props.route, current(props.api, props.route)) + }, + }, + { + name: command.screen_select, + run() { + picker(props.api, props.route, current(props.api, props.route)) + }, + }, + ], + bindings: props.keys.gather("smoke.screen", [ + command.screen_home, + command.screen_left, + command.screen_right, + command.screen_up, + command.screen_down, + command.screen_modal, + command.screen_local, + command.screen_host, + command.screen_alert, + command.screen_confirm, + command.screen_prompt, + command.screen_select, + ]), + })) + const shortcuts = useKeymapSelector((keymap) => { + const bindings = keymap.getCommandBindings({ + visibility: "registered", + commands: [ + command.screen_home, + command.screen_up, + command.screen_down, + command.screen_modal, + command.screen_alert, + command.screen_confirm, + command.screen_prompt, + command.screen_select, + command.screen_local, + command.screen_host, + command.local_push, + command.local_pop, + ], + }) + + return { + screen_home: props.api.keys.formatBindings(bindings.get(command.screen_home)) ?? "", + screen_up: props.api.keys.formatBindings(bindings.get(command.screen_up)) ?? "", + screen_down: props.api.keys.formatBindings(bindings.get(command.screen_down)) ?? "", + screen_modal: props.api.keys.formatBindings(bindings.get(command.screen_modal)) ?? "", + screen_alert: props.api.keys.formatBindings(bindings.get(command.screen_alert)) ?? "", + screen_confirm: props.api.keys.formatBindings(bindings.get(command.screen_confirm)) ?? "", + screen_prompt: props.api.keys.formatBindings(bindings.get(command.screen_prompt)) ?? "", + screen_select: props.api.keys.formatBindings(bindings.get(command.screen_select)) ?? "", + screen_local: props.api.keys.formatBindings(bindings.get(command.screen_local)) ?? "", + screen_host: props.api.keys.formatBindings(bindings.get(command.screen_host)) ?? "", + local_push: props.api.keys.formatBindings(bindings.get(command.local_push)) ?? "", + local_pop: props.api.keys.formatBindings(bindings.get(command.local_pop)) ?? "", + } + }) + + return ( + + + + + {props.input.label} screen + plugin route + + {shortcuts().screen_home} home + + + + {tabs.map((item, i) => { + const on = value.tab === i + return ( + props.api.route.navigate(props.route.screen, { ...value, tab: i })} + skin={skin} + on={on} + /> + ) + })} + + + + {value.tab === 0 ? ( + + Route: {props.route.screen} + plugin state: {props.meta.state} + + first: {props.meta.state === "first" ? "yes" : "no"} · updated:{" "} + {props.meta.state === "updated" ? "yes" : "no"} · loads: {props.meta.load_count} + + plugin source: {props.meta.source} + source: {value.source} + note: {value.note || "(none)"} + selected: {value.selected || "(none)"} + local stack depth: {value.local} + host stack open: {props.api.ui.dialog.open ? "yes" : "no"} + + ) : null} + + {value.tab === 1 ? ( + + Counter: {value.count} + + {shortcuts().screen_up} / {shortcuts().screen_down} change value + + + ) : null} + + {value.tab === 2 ? ( + + + {shortcuts().screen_modal} modal | {shortcuts().screen_alert} alert | {shortcuts().screen_confirm}{" "} + confirm | {shortcuts().screen_prompt} prompt | {shortcuts().screen_select} select + + + {shortcuts().screen_local} local stack | {shortcuts().screen_host} host stack + + + local open: {shortcuts().local_push} push nested · {shortcuts().local_pop} close + + {shortcuts().screen_home} returns home + + ) : null} + + + + props.api.route.navigate("home")} skin={skin} /> + props.api.route.navigate(props.route.modal, value)} skin={skin} on /> + + host(props.api, props.input, skin)} skin={skin} /> + warn(props.api, props.route, value)} skin={skin} /> + check(props.api, props.route, value)} skin={skin} /> + entry(props.api, props.route, value)} skin={skin} /> + picker(props.api, props.route, value)} skin={skin} /> + + + + 0} + width={dim().width} + height={dim().height} + alignItems="center" + position="absolute" + zIndex={3000} + paddingTop={dim().height / 4} + left={0} + top={0} + backgroundColor={RGBA.fromInts(0, 0, 0, 160)} + onMouseUp={() => { + pop() + }} + > + { + evt.stopPropagation() + }} + width={60} + maxWidth={dim().width - 2} + backgroundColor={skin.panel} + border + borderColor={skin.border} + paddingTop={1} + paddingBottom={1} + paddingLeft={2} + paddingRight={2} + gap={1} + flexDirection="column" + > + + {props.input.label} local overlay + + Plugin-owned stack depth: {value.local} + + {shortcuts().local_push} push nested · {shortcuts().local_pop} pop/close + + + + + + + + + ) +} + +const Modal = (props: { + api: TuiPluginApi + input: Cfg + route: Route + keys: Keys + params?: Record +}) => { + const Dialog = props.api.ui.Dialog + const value = parse(props.params) + const skin = tone(props.api) + + useBindings(() => ({ + enabled: () => props.api.route.current.name === props.route.modal, + commands: [ + { + name: command.modal_accept, + run() { + props.api.route.navigate(props.route.screen, { ...parse(props.params), source: "modal" }) + }, + }, + { + name: command.modal_close, + run() { + props.api.route.navigate("home") + }, + }, + ], + bindings: props.keys.gather("smoke.modal", [command.modal_accept, command.modal_close]), + })) + const shortcuts = useKeymapSelector((keymap) => { + const bindings = keymap.getCommandBindings({ + visibility: "registered", + commands: [command.modal, command.screen, command.modal_accept, command.modal_close], + }) + + return { + modal: props.api.keys.formatBindings(bindings.get(command.modal)) ?? "", + screen: props.api.keys.formatBindings(bindings.get(command.screen)) ?? "", + modal_accept: props.api.keys.formatBindings(bindings.get(command.modal_accept)) ?? "", + modal_close: props.api.keys.formatBindings(bindings.get(command.modal_close)) ?? "", + } + }) + + return ( + + props.api.route.navigate("home")}> + + + {props.input.label} modal + + {shortcuts().modal} modal command + {shortcuts().screen} screen command + + {shortcuts().modal_accept} opens screen · {shortcuts().modal_close} closes + + + props.api.route.navigate(props.route.screen, { ...value, source: "modal" })} + skin={skin} + on + /> + props.api.route.navigate("home")} skin={skin} /> + + + + + ) +} + +const home = (api: TuiPluginApi, input: Cfg) => ({ + slots: { + home_logo(ctx) { + const map = ctx.theme.current + const skin = look(map) + const art = [ + " $$\\", + " $$ |", + " $$$$$$$\\ $$$$$$\\$$$$\\ $$$$$$\\ $$ | $$\\ $$$$$$\\", + "$$ _____|$$ _$$ _$$\\ $$ __$$\\ $$ | $$ |$$ __$$\\", + "\\$$$$$$\\ $$ / $$ / $$ |$$ / $$ |$$$$$$ / $$$$$$$$ |", + " \\____$$\\ $$ | $$ | $$ |$$ | $$ |$$ _$$< $$ ____|", + "$$$$$$$ |$$ | $$ | $$ |\\$$$$$$ |$$ | \\$$\\ \\$$$$$$$\\", + "\\_______/ \\__| \\__| \\__| \\______/ \\__| \\__| \\_______|", + ] + const fill = [ + skin.accent, + skin.muted, + ink(map, "info", ui.accent), + skin.text, + ink(map, "success", ui.accent), + ink(map, "warning", ui.accent), + ink(map, "secondary", ui.accent), + ink(map, "error", ui.accent), + ] + + return ( + + {art.map((line, i) => ( + {line} + ))} + + ) + }, + home_prompt(ctx, value) { + const skin = look(ctx.theme.current) + const Prompt = api.ui.Prompt + const Slot = api.ui.Slot + const normal = [ + `[SMOKE] route check for ${input.label}`, + "[SMOKE] confirm home_prompt slot override", + "[SMOKE] verify prompt-right slot passthrough", + ] + const shell = ["printf '[SMOKE] home prompt\n'", "git status --short", "bun --version"] + const hint = ( + + + • smoke home prompt + + + ) + + return ( + + + + + } + placeholders={{ normal, shell }} + /> + ) + }, + home_prompt_right(ctx, value) { + const skin = look(ctx.theme.current) + const id = value.workspace_id?.slice(0, 8) ?? "none" + return ( + + {input.label} home:{id} + + ) + }, + session_prompt_right(ctx, value) { + const skin = look(ctx.theme.current) + return ( + + {input.label} session:{value.session_id.slice(0, 8)} + + ) + }, + smoke_prompt_right(ctx, value) { + const skin = look(ctx.theme.current) + const id = typeof value.workspace_id === "string" ? value.workspace_id.slice(0, 8) : "none" + const label = typeof value.label === "string" ? value.label : input.label + return ( + + {label} custom:{id} + + ) + }, + home_bottom(ctx) { + const skin = look(ctx.theme.current) + const text = "extra content in the unified home bottom slot" + + return ( + + + + {input.label} {text} + + + + ) + }, + }, +}) + +const block = (input: Cfg, order: number, title: string, text: string): TuiSlotPlugin => ({ + order, + slots: { + sidebar_content(ctx, value) { + const skin = look(ctx.theme.current) + + return ( + + + {title} + + {text} + + {input.label} order {order} · session {value.session_id.slice(0, 8)} + + + ) + }, + }, +}) + +const slot = (api: TuiPluginApi, input: Cfg): TuiSlotPlugin[] => [ + home(api, input), + block(input, 50, "Smoke above", "renders above internal sidebar blocks"), + block(input, 250, "Smoke between", "renders between internal sidebar blocks"), + block(input, 650, "Smoke below", "renders below internal sidebar blocks"), +] + +const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => { + const route = names(input) + api.keymap.registerLayer({ + commands: [ + { + name: command.modal, + title: `${input.label} modal`, + category: "Plugin", + namespace: "palette", + slashName: "smoke", + run() { + api.route.navigate(route.modal, { source: "command" }) + }, + }, + { + name: command.screen, + title: `${input.label} screen`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-screen", + run() { + api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 }) + }, + }, + { + name: command.alert, + title: `${input.label} alert dialog`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-alert", + run() { + warn(api, route, current(api, route)) + }, + }, + { + name: command.confirm, + title: `${input.label} confirm dialog`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-confirm", + run() { + check(api, route, current(api, route)) + }, + }, + { + name: command.prompt, + title: `${input.label} prompt dialog`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-prompt", + run() { + entry(api, route, current(api, route)) + }, + }, + { + name: command.select, + title: `${input.label} select dialog`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-select", + run() { + picker(api, route, current(api, route)) + }, + }, + { + name: command.host, + title: `${input.label} host overlay`, + category: "Plugin", + namespace: "palette", + slashName: "smoke-host", + run() { + host(api, input, tone(api)) + }, + }, + { + name: command.home, + title: `${input.label} go home`, + category: "Plugin", + namespace: "palette", + enabled: () => api.route.current.name !== "home", + run() { + api.route.navigate("home") + }, + }, + { + name: command.toast, + title: `${input.label} toast`, + category: "Plugin", + namespace: "palette", + run() { + api.ui.toast({ + variant: "info", + title: "Smoke", + message: "Plugin toast works", + duration: 2000, + }) + }, + }, + ], + bindings: keys.gather("smoke.global", [ + command.modal, + command.screen, + command.alert, + command.confirm, + command.prompt, + command.select, + command.host, + command.home, + command.toast, + ]), + }) +} + +const tui: TuiPlugin = async (api, options, meta) => { + if (options?.enabled === false) return + + await api.theme.install("./smoke-theme.json") + api.theme.set("smoke-theme") + + const value = cfg(options) + const route = names(value) + const keys = createKeys(value.keybinds) + const fx = new VignetteEffect(value.vignette) + const post = fx.apply.bind(fx) + api.renderer.addPostProcessFn(post) + api.lifecycle.onDispose(() => { + api.renderer.removePostProcessFn(post) + }) + + api.route.register([ + { + name: route.screen, + render: ({ params }) => , + }, + { + name: route.modal, + render: ({ params }) => , + }, + ]) + + reg(api, value, keys) + for (const item of slot(api, value)) { + api.slots.register(item) + } +} + +const plugin: TuiPluginModule & { id: string } = { + id: "tui-smoke", + tui, +} + +export default plugin diff --git a/.opencode/skills/effect/SKILL.md b/.opencode/skills/effect/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..3a44fa88dcdd59ff04a94634a1bfecee05a5cc18 --- /dev/null +++ b/.opencode/skills/effect/SKILL.md @@ -0,0 +1,38 @@ +--- +name: effect +description: Work with Effect v4 / effect-smol TypeScript code in this repo +--- + +# Effect + +This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows. + +## Source Of Truth + +Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples. + +1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder. +2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code. +3. Also inspect existing repo code for local house style before introducing new patterns. +4. Prefer answers and implementations backed by specific source files or nearby repo examples. + +## Guidelines + +- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses. +- Use `Effect.gen(function* () { ... })` for multi-step workflows. +- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows. +- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces. +- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services. +- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so. +- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see. +- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior. +- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types. +- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first. + +## Testing Patterns + +- Use `testEffect(...)` from `packages/opencode/test/lib/effect.ts` for tests that exercise Effect services, layers, runtime context, scoped resources, or platform integrations. +- Use `it.live(...)` for filesystem, git repositories, HTTP servers, sockets, child processes, locks, real time, and other live platform behavior. +- Run tests from package directories such as `packages/opencode`; never run package tests from the repo root. +- Prefer explicit test layers over ad hoc managed runtimes. Keep dependency provisioning visible in the test file. +- Use scoped fixtures and finalizers for resources that must be cleaned up, including temporary directories, flags, databases, fibers, servers, and global state. diff --git a/.opencode/skills/rtl-aware-development/SKILL.md b/.opencode/skills/rtl-aware-development/SKILL.md new file mode 100644 index 0000000000000000000000000000000000000000..595c981ead81f0827d14a41ae0a86eb18bbc5819 --- /dev/null +++ b/.opencode/skills/rtl-aware-development/SKILL.md @@ -0,0 +1,63 @@ +--- +name: rtl-aware-development +description: OpenCode Desktop should be RTL-aware. Use when implementing or reviewing RTL/LTR behavior in the web app, desktop app, CSS, menus, scrolling, resizing, icons, mixed-direction text, or Electron title bars. +--- + +# RTL-Aware Development + +Treat direction as independent from language. Test English in both directions as well as real RTL and mixed-script content. + +## Guidelines + +- Set `lang` and `dir` on the document, and propagate direction through component providers used by portaled menus and popovers. Do not change the selected locale merely to force RTL. +- Keep DOM and focus order semantic. Flexbox and Grid already follow `dir`; do not add `row-reverse`, CSS `order`, or reversed markup just to mirror a layout. +- Prefer logical CSS for semantic layout. Reserve physical coordinates for pointer positions, canvas geometry, native window controls, and other genuinely physical placement. + +```css +/* Avoid */ +padding-left: 12px; +right: 0; +border-right: 1px solid; +text-align: left; + +/* Prefer */ +padding-inline-start: 12px; +inset-inline-end: 0; +border-inline-end: 1px solid; +text-align: start; +``` + +- Isolate mixed-direction text. Use `dir="auto"` or `` for unknown text; keep code, URLs, IDs, and filesystem paths LTR without forcing the surrounding component LTR. + +```html +README.md C:\src\app.ts +``` + +- Mirror directional meaning, not every image. Back/forward, previous/next, disclosure, indentation, and directional progress may need mirroring. Do not mirror brands, clocks, media controls, charts, or text. Reverse physical gradients, `translateX`, SVG transforms, and animation deltas explicitly. +- Map interactions through direction. `clientX` remains physical; resizing a logical edge needs an RTL-aware delta. Logical previous/next keyboard controls may swap ArrowLeft/ArrowRight. Follow the relevant WAI-ARIA widget pattern. +- Do not assume LTR scrolling. RTL `scrollLeft` can start at `0` and become negative. Prefer `scrollIntoView({ inline: "nearest" })` or a tested direction-normalizing helper. +- For Electron title bars, prefer native caption controls and use `titleBarOverlay` plus `env(titlebar-area-*)` for the safe content rectangle. Keep Windows/macOS native-control avoidance and `trafficLightPosition` physical; keep app navigation inside that rectangle logical. Mark interactive titlebar children `app-region: no-drag`. +- Verify behavior, not screenshots alone. Check computed styles, pseudo-element geometry, hit zones, focus order, keyboard behavior, submenu direction, zoom/scaling, and both LTR and RTL scroll endpoints. + +## Test Matrix + +- English + LTR +- English + forced RTL +- A real RTL locale + RTL +- Mixed RTL/LTR content, long labels, numbers, code, and paths +- Keyboard, pointer resize, scrolling, menus/submenus, and Electron titlebar controls in both directions + +## References + +- [RTL Styling 101, Ahmad Shadeed](https://rtlstyling.com/posts/rtl-styling/) +- [CSS-Tricks: RTL Styling 101](https://css-tricks.com/rtl-styling-101/) +- [CSS-Tricks: CSS Logical Properties and Values](https://css-tricks.com/css-logical-properties-and-values/) +- [W3C: Structural markup and right-to-left text](https://www.w3.org/International/questions/qa-html-dir) +- [W3C: Inline bidirectional markup](https://www.w3.org/International/articles/inline-bidi-markup/) +- [MDN: CSS logical properties and values](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values) +- [MDN: `dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir) +- [MDN: `scrollLeft`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft) +- [web.dev: Logical properties](https://web.dev/learn/css/logical-properties/) +- [Electron: Custom title bar](https://www.electronjs.org/docs/latest/tutorial/custom-title-bar) +- [WAI-ARIA: Window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/) +- [Kobalte: I18n Provider](https://kobalte.dev/docs/core/components/i18n-provider/) diff --git a/.opencode/themes/.gitignore b/.opencode/themes/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5b41319c6aeb0051f67461feb2f7244eb1033f57 --- /dev/null +++ b/.opencode/themes/.gitignore @@ -0,0 +1 @@ +smoke-theme.json diff --git a/.opencode/themes/mytheme.json b/.opencode/themes/mytheme.json new file mode 100644 index 0000000000000000000000000000000000000000..0e6b94800123717ffb9c9a7d3bc194fb11e851df --- /dev/null +++ b/.opencode/themes/mytheme.json @@ -0,0 +1,223 @@ +{ + "$schema": "https://opencode.ai/theme.json", + "defs": { + "nord0": "#2E3440", + "nord1": "#3B4252", + "nord2": "#434C5E", + "nord3": "#4C566A", + "nord4": "#D8DEE9", + "nord5": "#E5E9F0", + "nord6": "#ECEFF4", + "nord7": "#8FBCBB", + "nord8": "#88C0D0", + "nord9": "#81A1C1", + "nord10": "#5E81AC", + "nord11": "#BF616A", + "nord12": "#D08770", + "nord13": "#EBCB8B", + "nord14": "#A3BE8C", + "nord15": "#B48EAD" + }, + "theme": { + "primary": { + "dark": "nord8", + "light": "nord10" + }, + "secondary": { + "dark": "nord9", + "light": "nord9" + }, + "accent": { + "dark": "nord7", + "light": "nord7" + }, + "error": { + "dark": "nord11", + "light": "nord11" + }, + "warning": { + "dark": "nord12", + "light": "nord12" + }, + "success": { + "dark": "nord14", + "light": "nord14" + }, + "info": { + "dark": "nord8", + "light": "nord10" + }, + "text": { + "dark": "nord4", + "light": "nord0" + }, + "textMuted": { + "dark": "nord3", + "light": "nord1" + }, + "background": { + "dark": "nord0", + "light": "nord6" + }, + "backgroundPanel": { + "dark": "nord1", + "light": "nord5" + }, + "backgroundElement": { + "dark": "nord1", + "light": "nord4" + }, + "border": { + "dark": "nord2", + "light": "nord3" + }, + "borderActive": { + "dark": "nord3", + "light": "nord2" + }, + "borderSubtle": { + "dark": "nord2", + "light": "nord3" + }, + "diffAdded": { + "dark": "nord14", + "light": "nord14" + }, + "diffRemoved": { + "dark": "nord11", + "light": "nord11" + }, + "diffContext": { + "dark": "nord3", + "light": "nord3" + }, + "diffHunkHeader": { + "dark": "nord3", + "light": "nord3" + }, + "diffHighlightAdded": { + "dark": "nord14", + "light": "nord14" + }, + "diffHighlightRemoved": { + "dark": "nord11", + "light": "nord11" + }, + "diffAddedBg": { + "dark": "#3B4252", + "light": "#E5E9F0" + }, + "diffRemovedBg": { + "dark": "#3B4252", + "light": "#E5E9F0" + }, + "diffContextBg": { + "dark": "nord1", + "light": "nord5" + }, + "diffLineNumber": { + "dark": "#abafb7", + "light": "textMuted" + }, + "diffAddedLineNumberBg": { + "dark": "#3B4252", + "light": "#E5E9F0" + }, + "diffRemovedLineNumberBg": { + "dark": "#3B4252", + "light": "#E5E9F0" + }, + "markdownText": { + "dark": "nord4", + "light": "nord0" + }, + "markdownHeading": { + "dark": "nord8", + "light": "nord10" + }, + "markdownLink": { + "dark": "nord9", + "light": "nord9" + }, + "markdownLinkText": { + "dark": "nord7", + "light": "nord7" + }, + "markdownCode": { + "dark": "nord14", + "light": "nord14" + }, + "markdownBlockQuote": { + "dark": "nord3", + "light": "nord3" + }, + "markdownEmph": { + "dark": "nord12", + "light": "nord12" + }, + "markdownStrong": { + "dark": "nord13", + "light": "nord13" + }, + "markdownHorizontalRule": { + "dark": "nord3", + "light": "nord3" + }, + "markdownListItem": { + "dark": "nord8", + "light": "nord10" + }, + "markdownListEnumeration": { + "dark": "nord7", + "light": "nord7" + }, + "markdownImage": { + "dark": "nord9", + "light": "nord9" + }, + "markdownImageText": { + "dark": "nord7", + "light": "nord7" + }, + "markdownCodeBlock": { + "dark": "nord4", + "light": "nord0" + }, + "syntaxComment": { + "dark": "nord3", + "light": "nord3" + }, + "syntaxKeyword": { + "dark": "nord9", + "light": "nord9" + }, + "syntaxFunction": { + "dark": "nord8", + "light": "nord8" + }, + "syntaxVariable": { + "dark": "nord7", + "light": "nord7" + }, + "syntaxString": { + "dark": "nord14", + "light": "nord14" + }, + "syntaxNumber": { + "dark": "nord15", + "light": "nord15" + }, + "syntaxType": { + "dark": "nord7", + "light": "nord7" + }, + "syntaxOperator": { + "dark": "nord9", + "light": "nord9" + }, + "syntaxPunctuation": { + "dark": "nord4", + "light": "nord0" + } + } +} diff --git a/.opencode/tool/github-pr-search.ts b/.opencode/tool/github-pr-search.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bc8c554aaeeca57141b5085d5351281b836a6a7 --- /dev/null +++ b/.opencode/tool/github-pr-search.ts @@ -0,0 +1,64 @@ +/// +import { tool } from "@opencode-ai/plugin" +async function githubFetch(endpoint: string, options: RequestInit = {}) { + const response = await fetch(`https://api.github.com${endpoint}`, { + ...options, + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + ...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers), + }, + }) + if (!response.ok) { + throw new Error(`GitHub API error: ${response.status} ${response.statusText}`) + } + return response.json() +} + +interface PR { + title: string + html_url: string +} + +export default tool({ + description: `Use this tool to search GitHub pull requests by title and description. + +This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including: +- PR number and title +- Author +- State (open/closed/merged) +- Labels +- Description snippet + +Use the query parameter to search for keywords that might appear in PR titles or descriptions.`, + args: { + query: tool.schema.string().describe("Search query for PR titles and descriptions"), + limit: tool.schema.number().describe("Maximum number of results to return").default(10), + offset: tool.schema.number().describe("Number of results to skip for pagination").default(0), + }, + async execute(args) { + const owner = "anomalyco" + const repo = "opencode" + + const page = Math.floor(args.offset / args.limit) + 1 + const searchQuery = encodeURIComponent(`${args.query} repo:${owner}/${repo} type:pr state:open`) + const result = await githubFetch( + `/search/issues?q=${searchQuery}&per_page=${args.limit}&page=${page}&sort=updated&order=desc`, + ) + + if (result.total_count === 0) { + return `No PRs found matching "${args.query}"` + } + + const prs = result.items as PR[] + + if (prs.length === 0) { + return `No other PRs found matching "${args.query}"` + } + + const formatted = prs.map((pr) => `${pr.title}\n${pr.html_url}`).join("\n\n") + + return `Found ${result.total_count} PRs (showing ${prs.length}):\n\n${formatted}` + }, +}) diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts new file mode 100644 index 0000000000000000000000000000000000000000..d610a81e497e4a6b82c3ae7e7dfde381471590f5 --- /dev/null +++ b/.opencode/tool/github-triage.ts @@ -0,0 +1,60 @@ +/// +import { tool } from "@opencode-ai/plugin" + +const TEAM = { + tui: ["kommander", "simonklee"], + desktop_web: ["Hona", "Brendonovich"], + core: ["jlongster", "rekram1-node", "neriousy", "nexxeln", "kitlangton"], + inference: ["fwang", "MrMushrooooom", "starptech"], + windows: ["Hona"], +} as const + +function pick(items: readonly T[]) { + return items[Math.floor(Math.random() * items.length)]! +} + +function getIssueNumber(): number { + const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10) + if (!issue) throw new Error("ISSUE_NUMBER env var not set") + return issue +} + +async function githubFetch(endpoint: string, options: RequestInit = {}) { + const response = await fetch(`https://api.github.com${endpoint}`, { + ...options, + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + ...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers), + }, + }) + if (!response.ok) { + throw new Error(`GitHub API error: ${response.status} ${response.statusText}`) + } + return response.json() +} + +export default tool({ + description: `Use this tool to assign a GitHub issue. + +Provide the team that should own the issue. This tool picks a random assignee from that team and does not apply labels.`, + args: { + team: tool.schema + .enum(Object.keys(TEAM) as [keyof typeof TEAM, ...(keyof typeof TEAM)[]]) + .describe("The owning team"), + }, + async execute(args) { + const issue = getIssueNumber() + const owner = "anomalyco" + const repo = "opencode" + const assignee = pick(TEAM[args.team]) + + await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, { + method: "POST", + body: JSON.stringify({ assignees: [assignee] }), + }) + + return `Assigned @${assignee} from ${args.team} to issue #${issue}` + }, +}) diff --git a/artifacts/glm52-rise-video/.gitignore b/artifacts/glm52-rise-video/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9d38bb25911c0a9ba3f3604e8b09f5d127b7fca3 --- /dev/null +++ b/artifacts/glm52-rise-video/.gitignore @@ -0,0 +1,3 @@ +node_modules +.remotion +out/frame-*.png diff --git a/artifacts/glm52-rise-video/bun.lock b/artifacts/glm52-rise-video/bun.lock new file mode 100644 index 0000000000000000000000000000000000000000..cac009ae812fe7a58c873495340bdd982ededbea --- /dev/null +++ b/artifacts/glm52-rise-video/bun.lock @@ -0,0 +1,483 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "glm52-rise-video", + "dependencies": { + "@remotion/cli": "^4.0.384", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "remotion": "^4.0.384", + }, + "devDependencies": { + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "typescript": "^5.8.2", + }, + }, + }, + "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.24.1", "", { "bin": "./bin/babel-parser.js" }, "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg=="], + + "@babel/types": ["@babel/types@7.24.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w=="], + + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mediabunny/aac-encoder": ["@mediabunny/aac-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-JNzgdJoHMFFnv5imi1+dmjZMudsJ1zNCUCEkKjBh90cqGhAFg8xu4V2gsyxE2i6oq/YpWx23P+OvoANLSMcDzA=="], + + "@mediabunny/flac-encoder": ["@mediabunny/flac-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-VpKmJO0xlYcFCRD6JvJlMbNQ6d/6YMHdO1gFIqWlZABHjSSL6BquNNEgWSCv5vdF8ELDBwIYBVZEslakIB/7GA=="], + + "@mediabunny/mp3-encoder": ["@mediabunny/mp3-encoder@1.47.0", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-JyzZyGeGRm2HVUQaGJ/VZT9OG+kG00mA8SLP/f3CO7+qWAmBncKc16WYXWHbZEo8Jn/ZGA6De32S5R2bTQbDqA=="], + + "@module-federation/error-codes": ["@module-federation/error-codes@0.22.0", "", {}, "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug=="], + + "@module-federation/runtime": ["@module-federation/runtime@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/runtime-core": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA=="], + + "@module-federation/runtime-core": ["@module-federation/runtime-core@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA=="], + + "@module-federation/runtime-tools": ["@module-federation/runtime-tools@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/webpack-bundler-runtime": "0.22.0" } }, "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA=="], + + "@module-federation/sdk": ["@module-federation/sdk@0.22.0", "", {}, "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g=="], + + "@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + + "@remotion/bundler": ["@remotion/bundler@4.0.483", "", { "dependencies": { "@remotion/media-parser": "4.0.483", "@remotion/studio": "4.0.483", "@remotion/studio-shared": "4.0.483", "@remotion/timeline-utils": "4.0.483", "@rspack/core": "1.7.11", "@rspack/plugin-react-refresh": "1.6.1", "css-loader": "7.1.4", "esbuild": "0.28.1", "react-refresh": "0.18.0", "remotion": "4.0.483", "style-loader": "4.0.0", "webpack": "5.105.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-5fQRMYgr2sxZPBOThnIVuFwHBq/FhusJ+Ke6xopga8io8ecK/mj9GaRXC3tgfMa+YWuu8ZSXZ0U9EsMZjjP5YQ=="], + + "@remotion/canvas-capture": ["@remotion/canvas-capture@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JABgbfvXwjFp1E61tlDN9gVAH8OoeF4kUUELNwG9kcNOE4ex8+vCYEO6VKIaXZ4l8A1PEAQ5EK//NheTJ80gfw=="], + + "@remotion/cli": ["@remotion/cli@4.0.483", "", { "dependencies": { "@remotion/bundler": "4.0.483", "@remotion/media-utils": "4.0.483", "@remotion/player": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio": "4.0.483", "@remotion/studio-server": "4.0.483", "@remotion/studio-shared": "4.0.483", "dotenv": "17.3.1", "minimist": "1.2.6", "prompts": "2.4.2", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" }, "bin": { "remotion": "remotion-cli.js", "remotionb": "remotionb-cli.js", "remotiond": "remotiond-cli.js" } }, "sha512-z4TQcgTfQbSIV4GQpCvIihDL77M+leQelbxv962PmfSKTT7z3XnNN732PcZ4sei2+Xg+d2SD95K4tI5XBYs5Gw=="], + + "@remotion/compositor-darwin-arm64": ["@remotion/compositor-darwin-arm64@4.0.483", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CkEJXouGEoCs5PX3KOufkXrlT/Kgn0ZF7SEQ4meCYGYMZD+QPVtsmS1MXH8pQ4RHlFO6Tk7g0CIHMT3Y4yzpuA=="], + + "@remotion/compositor-darwin-x64": ["@remotion/compositor-darwin-x64@4.0.483", "", { "os": "darwin", "cpu": "x64" }, "sha512-vDZK5U8FYbHGMjFC2lUgJRBz8KXwbwFSvEQi8FATD0roNg75SoaEJWrqAOIVv8v5eBjPvo18znDc36NLRYn1Eg=="], + + "@remotion/compositor-linux-arm64-gnu": ["@remotion/compositor-linux-arm64-gnu@4.0.483", "", { "os": "linux", "cpu": "arm64" }, "sha512-1LzfYDY/DQxy3MIc9pCbTn1ObcBDcyI6MzODOwe1bTZl8TZxdUUt1M3hDrQ60AB6iHwruBAnzT9dnogiMIdSew=="], + + "@remotion/compositor-linux-arm64-musl": ["@remotion/compositor-linux-arm64-musl@4.0.483", "", { "os": "linux", "cpu": "arm64" }, "sha512-nG1btxg0HXuLzulIqAg9NnxWyewZNrA0iLe82HdniaJjeWLORreGo4cEEJz1SjbDpQuPEY562XGkTO2DLKK5mA=="], + + "@remotion/compositor-linux-x64-gnu": ["@remotion/compositor-linux-x64-gnu@4.0.483", "", { "os": "linux", "cpu": "x64" }, "sha512-CV4h6r2rNYh4P8utUzP6RM2vW2vq43nA3IvVOwRSuh9LwJh/7rwG53Oco4HanY/sstaNuymGpT57wdXwONU5CQ=="], + + "@remotion/compositor-linux-x64-musl": ["@remotion/compositor-linux-x64-musl@4.0.483", "", { "os": "linux", "cpu": "x64" }, "sha512-moKvavbkoz5nteJZA4K6LMgTTlewhwIbtO7DYARZ3LK3ClGUKnYfFcmaPS15hMun02JPTOMIWbrlaszdR75mxA=="], + + "@remotion/compositor-win32-x64-msvc": ["@remotion/compositor-win32-x64-msvc@4.0.483", "", { "os": "win32", "cpu": "x64" }, "sha512-eDpWe6Vy7ySHugwxlgaeohqdBwR1etxiymXzT13cgbPw2+p6d55z1tJ7VPTXyfu0o+UdyRTauWqD4msoxN0wow=="], + + "@remotion/licensing": ["@remotion/licensing@4.0.483", "", {}, "sha512-GRtjykrxMmB5Cjpq1oVqeYIRXI95vid6afX0LTLfp1shD6MR4lTZRoRuopxrrZp1E4XFGXoY64b6dlIudgTyBQ=="], + + "@remotion/media-parser": ["@remotion/media-parser@4.0.483", "", {}, "sha512-iva9Eof7QQX+3hGxH/N8pGi80wgdGuzOr825Zn7IuEe7IWdtRYWlFpCEghmyfhrwgaWa7RY51wg77ycR3c3nRg=="], + + "@remotion/media-utils": ["@remotion/media-utils@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4T4WQh/U95kCBzgmz/EL/HL8Zeox/AKAL5ufsehrZFgwlcvH1EsKNJH/RWaAtNPRLF+pskEOgFzPYaczHeI8hg=="], + + "@remotion/player": ["@remotion/player@4.0.483", "", { "dependencies": { "remotion": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yyIQ9vinUqqS0fQeL52bPCWQIr8qDXCqQ4iys8P7dSZncdu8V29p4W2a0/4eKV8bv5+mjnJQ/U6nl9NxBF1hdA=="], + + "@remotion/renderer": ["@remotion/renderer@4.0.483", "", { "dependencies": { "@remotion/licensing": "4.0.483", "@remotion/streaming": "4.0.483", "execa": "5.1.1", "remotion": "4.0.483", "source-map": "0.8.0-beta.0", "ws": "8.21.0" }, "optionalDependencies": { "@remotion/compositor-darwin-arm64": "4.0.483", "@remotion/compositor-darwin-x64": "4.0.483", "@remotion/compositor-linux-arm64-gnu": "4.0.483", "@remotion/compositor-linux-arm64-musl": "4.0.483", "@remotion/compositor-linux-x64-gnu": "4.0.483", "@remotion/compositor-linux-x64-musl": "4.0.483", "@remotion/compositor-win32-x64-msvc": "4.0.483" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-eSKdQqm8rcl+GNyOhFsBgcPYWMcxTJWHL5xaNrd037piCaL3sdJ57OEO/AbfEvvOO14zwUvlsONKjlkLlEVyKg=="], + + "@remotion/streaming": ["@remotion/streaming@4.0.483", "", {}, "sha512-96rqrk+l5AfriHOqmqxMn8rI6UJfFY7r5UMDZoSKqXWzTOwj+0VWHTRl9ilZQMXhYACaMSA/5yrL9jgQEK8Yuw=="], + + "@remotion/studio": ["@remotion/studio@4.0.483", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "@remotion/canvas-capture": "4.0.483", "@remotion/media-utils": "4.0.483", "@remotion/player": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio-shared": "4.0.483", "@remotion/timeline-utils": "4.0.483", "@remotion/web-renderer": "4.0.483", "@remotion/zod-types": "4.0.483", "mediabunny": "1.47.0", "memfs": "3.4.3", "open": "8.4.2", "remotion": "4.0.483", "semver": "7.5.3", "zod": "4.3.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-gNa1Lw8NmHl0jwFb5fk7rzA+WFTJQpGDT/49A38t9jpEwoF+h1mKTgY8gA202apzjBHrEECdFE450npnBOUpUw=="], + + "@remotion/studio-server": ["@remotion/studio-server@4.0.483", "", { "dependencies": { "@babel/parser": "7.24.1", "@babel/types": "7.24.0", "@remotion/bundler": "4.0.483", "@remotion/renderer": "4.0.483", "@remotion/studio-shared": "4.0.483", "memfs": "3.4.3", "open": "8.4.2", "prettier": "3.8.1", "recast": "0.23.11", "remotion": "4.0.483", "semver": "7.5.3" } }, "sha512-2FSLbVN5N8bgtQPX+RQRbis557tJJnVTOZuEBZndfC9NOoPdARlSgEnwVer9tBNczUlc6B3KrPxyPSlS3UjL+Q=="], + + "@remotion/studio-shared": ["@remotion/studio-shared@4.0.483", "", { "dependencies": { "remotion": "4.0.483" } }, "sha512-aXxBYUgsWgphHVq7T7bz+ICMGzfii7pcX1IxS5hbLMLJHqlDtk9g+qcxm4kJ2Fjj/Gb5gERKiNzYym8WZTIM2Q=="], + + "@remotion/timeline-utils": ["@remotion/timeline-utils@4.0.483", "", { "dependencies": { "mediabunny": "1.47.0" } }, "sha512-KTTMdpNA5uRLX+iisAITO+V9cJBk17F8EWvuUnAEVmAFOQTbGIyPI9HNw5pZmL7SlzOJdFjAtksTjipeYGkEug=="], + + "@remotion/web-renderer": ["@remotion/web-renderer@4.0.483", "", { "dependencies": { "@mediabunny/aac-encoder": "1.47.0", "@mediabunny/flac-encoder": "1.47.0", "@mediabunny/mp3-encoder": "1.47.0", "@remotion/licensing": "4.0.483", "mediabunny": "1.47.0", "remotion": "4.0.483" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-14iURfVWA/hcF/rJvkid1ZR5nmnVhkTIf071FabCFi6kdViOSVBugSglJ8tUPvWVbWmxlXGDGQD9CSU18IATww=="], + + "@remotion/zod-types": ["@remotion/zod-types@4.0.483", "", { "dependencies": { "remotion": "4.0.483" }, "peerDependencies": { "zod": "4.3.6" } }, "sha512-KIVYIzFpZBDB0wxGGQu5tDBr4XOYRzvrPXjmo6O/FI5Xx7O8hgvJmBs9VDkEwonAOft86sg79hI19F1DTX1p4w=="], + + "@rspack/binding": ["@rspack/binding@1.7.11", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "1.7.11", "@rspack/binding-darwin-x64": "1.7.11", "@rspack/binding-linux-arm64-gnu": "1.7.11", "@rspack/binding-linux-arm64-musl": "1.7.11", "@rspack/binding-linux-x64-gnu": "1.7.11", "@rspack/binding-linux-x64-musl": "1.7.11", "@rspack/binding-wasm32-wasi": "1.7.11", "@rspack/binding-win32-arm64-msvc": "1.7.11", "@rspack/binding-win32-ia32-msvc": "1.7.11", "@rspack/binding-win32-x64-msvc": "1.7.11" } }, "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q=="], + + "@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@1.7.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig=="], + + "@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@1.7.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA=="], + + "@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@1.7.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA=="], + + "@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@1.7.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw=="], + + "@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@1.7.11", "", { "os": "linux", "cpu": "x64" }, "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ=="], + + "@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@1.7.11", "", { "os": "linux", "cpu": "x64" }, "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg=="], + + "@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@1.7.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "1.0.7" }, "cpu": "none" }, "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ=="], + + "@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@1.7.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA=="], + + "@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@1.7.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw=="], + + "@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@1.7.11", "", { "os": "win32", "cpu": "x64" }, "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q=="], + + "@rspack/core": ["@rspack/core@1.7.11", "", { "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.11", "@rspack/lite-tapable": "1.1.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" }, "optionalPeers": ["@swc/helpers"] }, "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew=="], + + "@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.0", "", {}, "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw=="], + + "@rspack/plugin-react-refresh": ["@rspack/plugin-react-refresh@1.6.1", "", { "dependencies": { "error-stack-parser": "^2.1.4", "html-entities": "^2.6.0" }, "peerDependencies": { "react-refresh": ">=0.10.0 <1.0.0", "webpack-hot-middleware": "2.x" }, "optionalPeers": ["webpack-hot-middleware"] }, "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="], + + "@types/dom-webcodecs": ["@types/dom-webcodecs@0.1.13", "", {}, "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ=="], + + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], + + "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.40", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw=="], + + "browserslist": ["browserslist@4.28.4", "", { "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", "electron-to-chromium": "^1.5.376", "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-loader": ["css-loader@7.1.4", "", { "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.6.3" }, "peerDependencies": { "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.379", "", {}, "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA=="], + + "enhanced-resolve": ["enhanced-resolve@5.24.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw=="], + + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + + "fs-monkey": ["fs-monkey@1.0.3", "", {}, "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="], + + "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="], + + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "mediabunny": ["mediabunny@1.47.0", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-XQMZAcaKPkJ7hQ/Q2fvBdl3ZazQl2WVxDysUbJWh4PuAnLoerdsQBdPTDWdUdK6hh26LQ8Ue94MLLnmpWvlUYg=="], + + "memfs": ["memfs@3.4.3", "", { "dependencies": { "fs-monkey": "1.0.3" } }, "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimist": ["minimist@1.2.6", "", {}, "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="], + + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-releases": ["node-releases@2.0.50", "", {}, "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "postcss-modules-extract-imports": ["postcss-modules-extract-imports@3.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q=="], + + "postcss-modules-local-by-default": ["postcss-modules-local-by-default@4.2.0", "", { "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw=="], + + "postcss-modules-scope": ["postcss-modules-scope@3.2.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA=="], + + "postcss-modules-values": ["postcss-modules-values@4.0.0", "", { "dependencies": { "icss-utils": "^5.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], + + "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + + "remotion": ["remotion@4.0.483", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-lf4xq4Twn75TQeTavFLrTE4zdck7EKBSx07ZrPv4om40Sist+3G0kq7NNzSjMeJ6G2Wx6vI+0oICD/vv4sGWtg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "semver": ["semver@7.5.3", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "style-loader": ["style-loader@4.0.0", "", { "peerDependencies": { "webpack": "^5.27.0" } }, "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "terser": ["terser@5.48.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q=="], + + "terser-webpack-plugin": ["terser-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "to-fast-properties": ["to-fast-properties@2.0.0", "", {}, "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="], + + "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], + + "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], + + "webpack": ["webpack@5.105.0", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw=="], + + "webpack-sources": ["webpack-sources@3.5.0", "", {}, "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ=="], + + "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "css-loader/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + } +} diff --git a/artifacts/glm52-rise-video/out/june-totals.png b/artifacts/glm52-rise-video/out/june-totals.png new file mode 100644 index 0000000000000000000000000000000000000000..678376dbed6998f063dde5ef26de9fb918119792 Binary files /dev/null and b/artifacts/glm52-rise-video/out/june-totals.png differ diff --git a/artifacts/glm52-rise-video/package.json b/artifacts/glm52-rise-video/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fb08574eb222019220938ae549facd3da84e52b7 --- /dev/null +++ b/artifacts/glm52-rise-video/package.json @@ -0,0 +1,24 @@ +{ + "name": "glm52-rise-video", + "private": true, + "type": "module", + "scripts": { + "render": "remotion render src/index.tsx GLM52Rise out/glm-52-broke-out.mp4 --codec h264 --pixel-format yuv420p", + "render:sheep": "remotion render src/index.tsx NZSheep out/nz-sheep.mp4 --codec h264 --pixel-format yuv420p", + "render:novel": "remotion render src/index.tsx NovelTokens out/novel-1984.mp4 --codec h264 --pixel-format yuv420p", + "render:flash": "remotion render src/index.tsx FlashShare out/flash-share.mp4 --codec h264 --pixel-format yuv420p", + "render:minimax": "remotion render src/index.tsx MiniMaxClimb out/minimax-climb.mp4 --codec h264 --pixel-format yuv420p", + "still:june": "remotion still src/index.tsx JuneTotals out/june-totals.png --frame=0" + }, + "dependencies": { + "@remotion/cli": "^4.0.384", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "remotion": "^4.0.384" + }, + "devDependencies": { + "@types/react": "^19.2.8", + "@types/react-dom": "^19.2.3", + "typescript": "^5.8.2" + } +} diff --git a/artifacts/glm52-rise-video/src/data.ts b/artifacts/glm52-rise-video/src/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9fab2a63114e12227c874f0b7bb3e378bba300f --- /dev/null +++ b/artifacts/glm52-rise-video/src/data.ts @@ -0,0 +1,156 @@ +// Verified against the production opencode-stats PlanetScale DB. +// Scope: tier='Go' (OpenCode Go), dataset='zen', client='all', source='all', grain='day'. +// metric = total_tokens. Daily token volume per model; GLM-5.2 (zhipu) launched Jun 17. +// Segments per day (tokens): glm-5.2, deepseek-v4-flash, deepseek-v4-pro, minimax-m3, all others. + +export type Day = { + date: string + total: number + glm: number + dsf: number + dsp: number + mm: number + others: number +} + +export const days: Day[] = [ + { + date: "Jun 12", + total: 2_283_799_449_383, + glm: 0, + dsf: 1_176_701_653_509, + dsp: 569_527_034_307, + mm: 159_016_250_684, + others: 378_554_510_883, + }, + { + date: "Jun 13", + total: 2_008_462_388_420, + glm: 0, + dsf: 995_338_131_997, + dsp: 445_817_536_548, + mm: 211_743_241_967, + others: 355_563_477_908, + }, + { + date: "Jun 14", + total: 2_007_785_405_251, + glm: 0, + dsf: 983_954_176_228, + dsp: 428_151_999_341, + mm: 262_476_527_930, + others: 333_202_701_752, + }, + { + date: "Jun 15", + total: 2_694_736_103_062, + glm: 0, + dsf: 1_255_893_953_859, + dsp: 632_223_338_376, + mm: 352_507_442_991, + others: 454_111_367_836, + }, + { + date: "Jun 16", + total: 2_838_153_758_908, + glm: 0, + dsf: 1_336_625_283_800, + dsp: 676_480_415_730, + mm: 305_268_829_013, + others: 519_779_230_365, + }, + { + date: "Jun 17", + total: 2_778_964_711_109, + glm: 70_095_977_043, + dsf: 1_339_831_523_555, + dsp: 660_414_395_220, + mm: 251_302_096_157, + others: 457_320_719_134, + }, + { + date: "Jun 18", + total: 2_806_992_430_656, + glm: 201_130_231_172, + dsf: 1_295_599_996_869, + dsp: 595_665_008_776, + mm: 322_205_104_324, + others: 392_392_089_515, + }, + { + date: "Jun 19", + total: 2_419_611_630_232, + glm: 199_086_413_910, + dsf: 1_115_750_468_802, + dsp: 475_965_869_304, + mm: 303_586_698_735, + others: 325_222_179_481, + }, + { + date: "Jun 20", + total: 2_188_278_916_865, + glm: 193_931_516_396, + dsf: 1_050_194_681_012, + dsp: 395_303_435_278, + mm: 281_998_000_337, + others: 266_851_283_842, + }, + { + date: "Jun 21", + total: 2_042_309_961_344, + glm: 181_894_043_118, + dsf: 985_164_570_580, + dsp: 368_194_079_542, + mm: 259_812_551_324, + others: 247_244_716_780, + }, + { + date: "Jun 22", + total: 2_893_934_325_663, + glm: 301_759_048_475, + dsf: 1_298_124_282_989, + dsp: 581_012_596_194, + mm: 371_581_117_839, + others: 341_457_280_166, + }, + { + date: "Jun 23", + total: 3_109_009_321_480, + glm: 282_277_235_158, + dsf: 1_423_571_678_821, + dsp: 627_374_654_587, + mm: 429_416_300_508, + others: 346_369_452_406, + }, + { + date: "Jun 24", + total: 2_939_149_971_595, + glm: 256_497_442_533, + dsf: 1_373_583_023_234, + dsp: 601_270_997_775, + mm: 391_586_493_231, + others: 316_212_014_822, + }, + { + date: "Jun 25", + total: 3_029_641_552_948, + glm: 256_279_657_734, + dsf: 1_481_084_002_776, + dsp: 602_077_167_287, + mm: 375_985_302_874, + others: 314_215_422_277, + }, +] + +export const launchIndex = 5 // Jun 17, first day of GLM-5.2 usage +// GLM-5.2 weekly token volume, Jun 19-25 (sum of glm): 1,671,725,357,324 = 1.67T +export const glmWeekTokensT = 1.672 + +// stacked segments, bottom -> top. GLM-5.2 is the hero (blue); the rest are the field it cut into. +export const segments = [ + { key: "glm", label: "GLM-5.2", color: "#3b5cf6", hero: true }, + { key: "dsf", label: "deepseek-v4-flash", color: "#9ca3ad" }, + { key: "dsp", label: "deepseek-v4-pro", color: "#b3b9c1" }, + { key: "mm", label: "minimax-m3", color: "#c8cdd3" }, + { key: "others", label: "other models", color: "#dde0e4" }, +] as const diff --git a/artifacts/glm52-rise-video/src/flash.tsx b/artifacts/glm52-rise-video/src/flash.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5d7aaef9bf5ef64d9a3c2ee06c452639a6147876 --- /dev/null +++ b/artifacts/glm52-rise-video/src/flash.tsx @@ -0,0 +1,185 @@ +import React from "react" +import { AbsoluteFill, Easing, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion" + +// stats.opencode.ai design tokens (light theme) +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#e4e4e4", + gray: "#aab0b8", + accent: "#3b5cf6", + accentHi: "#5b78ff", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: OpenCode Go, week of Jun 22-28, 2026 (2026-W26). share = % of 19.64T total Go tokens. +const bars = [ + { label: "deepseek-v4-flash", share: 48.3, hero: true }, + { label: "deepseek-v4-pro", share: 19.4 }, + { label: "minimax-m3", share: 13.0 }, + { label: "glm-5.2", share: 8.3 }, + { label: "mimo-v2.5", share: 4.3 }, + { label: "kimi-k2.7-code", share: 2.6 }, + { label: "other models", share: 4.1 }, +] + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function FlashShare() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + const grow = (i: number) => + Math.min( + 1, + Math.max(0, spring({ frame: frame - 18 - i * 7, fps, config: { damping: 18, stiffness: 120, mass: 0.6 } })), + ) + + return ( + +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* headline (static) */} +
+
+ OPENCODE GO · SHARE OF TOKENS +
+
+
DeepSeek V4 Flash
+
+ 48% +
+
+
+ + {/* bar chart */} +
+ {bars.map((b, i) => { + const g = grow(i) + const pct = b.share * g + return ( +
+
+ {b.label} +
+
+ {/* dotted 100% track — height is a multiple of the 12px tile, anchored bottom, so dots never clip */} +
+ {/* fill */} +
+
+
+ {Math.round(pct)}% +
+
+ ) + })} +
+ + {/* footer */} +
+
+ + DeepSeek V4 Flash · 9.48T tokens · 83.6M requests +
+
opencode.ai/data
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/index.tsx b/artifacts/glm52-rise-video/src/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3265151ac54285810633c9494c8fce670673b7ea --- /dev/null +++ b/artifacts/glm52-rise-video/src/index.tsx @@ -0,0 +1,36 @@ +import { Composition, registerRoot } from "remotion" +import { GLM52Rise } from "./video" +import { NZSheep } from "./sheep" +import { NovelTokens } from "./novel" +import { FlashShare } from "./flash" +import { MiniMaxClimb } from "./minimax" +import { JuneTotals } from "./june" + +function Root() { + return ( + <> + + + + + + + + ) +} + +registerRoot(Root) diff --git a/artifacts/glm52-rise-video/src/june.tsx b/artifacts/glm52-rise-video/src/june.tsx new file mode 100644 index 0000000000000000000000000000000000000000..aa3f9de9bef4d1549c0c2410fa8ac2c000d15fb0 --- /dev/null +++ b/artifacts/glm52-rise-video/src/june.tsx @@ -0,0 +1,144 @@ +import React from "react" +import { AbsoluteFill } from "remotion" + +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#dcdcdc", + accent: "#3b5cf6", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: OpenCode Go (tier=Go, dataset=zen), June 1-30, 2026. +// 72.78T tokens · 651.4M requests · 11.42M sessions -> rounded headline figures. + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +function DotBand() { + return ( +
+ ) +} + +function Metric({ value, label }: { value: string; label: string }) { + return ( +
+
+ {value} +
+
{label}
+
+ ) +} + +export function JuneTotals() { + return ( + +
+ {/* header */} +
+ +
MONTHLY RECAP
+
+ + + {/* hero */} +
+
OPENCODE GO · JUNE 2026
+
+
+ 73T +
+
tokens processed
+
+ + {/* supporting metrics */} +
+ + +
+
+ + {/* footer */} + +
+
opencode.ai/data
+
+
+
+ ) +} diff --git a/artifacts/glm52-rise-video/src/minimax.tsx b/artifacts/glm52-rise-video/src/minimax.tsx new file mode 100644 index 0000000000000000000000000000000000000000..44cf09a1e75acc2d570519a28d96955793117fb7 --- /dev/null +++ b/artifacts/glm52-rise-video/src/minimax.tsx @@ -0,0 +1,201 @@ +import React from "react" +import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion" + +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#e4e4e4", + accent: "#3b5cf6", + accentHi: "#5b78ff", + accentDim: "#aebcf3", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +// verified: minimax-m3, OpenCode Go (tier=Go, dataset=zen), weekly total_tokens. +// W26 2.559T is +23.2% / +482.3B vs W25 2.077T. +const weeks = [ + { label: "May 25", t: 0.008 }, + { label: "Jun 1", t: 0.429 }, + { label: "Jun 8", t: 1.192 }, + { label: "Jun 15", t: 2.077 }, + { label: "Jun 22", t: 2.559, latest: true }, +] +const AXIS_MAX = 3.0 + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +const CHART_H = 420 // multiple of 12 so the dotted tracks never clip + +export function MiniMaxClimb() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + const grow = (i: number) => + Math.min( + 1, + Math.max(0, spring({ frame: frame - 22 - i * 9, fps, config: { damping: 18, stiffness: 110, mass: 0.6 } })), + ) + + return ( + +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* headline (static) */} +
+
+ OPENCODE GO · WEEKLY TOKENS +
+
+
MiniMax M3
+
+
+ +23.2% +
+
+ WEEK OVER WEEK +
+
+
+
+ + {/* column chart */} +
+
+ {weeks.map((w, i) => { + const g = grow(i) + const h = Math.round((w.t / AXIS_MAX) * CHART_H * g) + return ( +
+ {/* value + bar */} +
+ {/* dotted track */} +
+ {/* fill */} +
+ {/* value label */} +
+ {w.t.toFixed(2)}T +
+
+ {/* week label */} +
+ {w.label} +
+
+ ) + })} +
+
+ + {/* footer */} +
+
+ + 2.56T tokens last week · +482.3B added +
+
opencode.ai/data
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/novel.tsx b/artifacts/glm52-rise-video/src/novel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9d649de4bbbb7e446ebd929c5ef6b0528d98da58 --- /dev/null +++ b/artifacts/glm52-rise-video/src/novel.tsx @@ -0,0 +1,135 @@ +import React from "react" +import { AbsoluteFill, Easing, Img, interpolate, staticFile, useCurrentFrame } from "remotion" + +const c = { + white: "#ffffff", + dim: "rgba(255,255,255,0.74)", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +// verified: OpenCode Go, week of Jun 22-28, 2026 (2026-W26) +// 19,642,742,937,105 tokens / 173,651,197 requests = 113,116 tokens/request +const AVG = 113116 +const K = Math.round(AVG / 1000) // 113 + +const nf = new Intl.NumberFormat("en-US") + +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function NovelTokens() { + const frame = useCurrentFrame() + + const k = Math.round( + K * + interpolate(frame, [18, 92], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }), + ) + + const zoom = interpolate(frame, [0, 150], [1.06, 1.12], { + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.quad), + }) + + return ( + + + + {/* legibility scrims */} +
+ +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* bottom block */} +
+
+ OPENCODE GO · LAST WEEK +
+
+ {k}K +
+
tokens per request
+ +
+
{nf.format(AVG)} tokens / request · last week
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/sheep.tsx b/artifacts/glm52-rise-video/src/sheep.tsx new file mode 100644 index 0000000000000000000000000000000000000000..53abb39ee567234a5ca99dad4668d7c6dfc4ab7e --- /dev/null +++ b/artifacts/glm52-rise-video/src/sheep.tsx @@ -0,0 +1,139 @@ +import React from "react" +import { AbsoluteFill, Easing, Img, interpolate, staticFile, useCurrentFrame } from "remotion" + +const c = { + white: "#ffffff", + dim: "rgba(255,255,255,0.72)", + faint: "rgba(255,255,255,0.55)", +} +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' + +// verified: NZ OpenCode Go, week of Jun 22-28, 2026 (2026-W26) +const TOKENS = 40_915_594_381 // 40.9B +const SHEEP = 23_600_000 // 23.6M +const PER_SHEEP = Math.round(TOKENS / SHEEP) // 1,734 +const nf = new Intl.NumberFormat("en-US") + +// the correct opencode "DATA" wordmark (white, over photo) +function DataWordmark({ height = 30 }: { height?: number }) { + return ( + + + + + + + + + ) +} + +export function NZSheep() { + const frame = useCurrentFrame() + + const count = Math.round( + PER_SHEEP * + interpolate(frame, [18, 90], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }), + ) + + // slow Ken Burns push-in (scale up only — never reveals an edge) + const zoom = interpolate(frame, [0, 150], [1.06, 1.12], { + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.quad), + }) + + return ( + + {/* the sheep, staring */} + + + {/* legibility scrims */} +
+ + {/* content */} +
+ {/* header */} +
+ +
JUN 22–28, 2026
+
+ + {/* bottom block */} +
+
+ OPENCODE GO · NEW ZEALAND +
+
+ {nf.format(count)} +
+
tokens per sheep
+ +
+
40.9B tokens ÷ 23.6M sheep · last week
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/src/video.tsx b/artifacts/glm52-rise-video/src/video.tsx new file mode 100644 index 0000000000000000000000000000000000000000..41466a7e6c39f10aa0230ea683e3a6cce0917486 --- /dev/null +++ b/artifacts/glm52-rise-video/src/video.tsx @@ -0,0 +1,254 @@ +import React from "react" +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion" +import { days, launchIndex, glmWeekTokensT, segments } from "./data" + +// stats.opencode.ai design tokens (light theme) +const c = { + bg: "#ffffff", + ink: "#161616", + muted: "#5c5c5c", + faint: "#808080", + line: "#e6e6e6", + dot: "#ededed", + accent: "#3b5cf6", + accentHi: "#5b78ff", +} + +const MONO = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace' +const W = 1080 + +const DOT_MASK = + "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E\")" + +const field = segments.filter((s) => !s.hero) +const glmColor = segments.find((s) => s.hero)!.color + +const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v)) + +// the correct opencode "DATA" wordmark (from stats.opencode.ai header) +function DataWordmark({ height = 30, color = c.ink }: { height?: number; color?: string }) { + return ( + + + + + + + + + ) +} + +export function GLM52Rise() { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + // ---------- virtual camera ---------- + // open zoomed-in on the field, pan right while the blue fills, then pull back to reveal. + const K = [0, 32, 150, 206, 240] + const ease = Easing.inOut(Easing.cubic) + const opt = { extrapolateLeft: "clamp" as const, extrapolateRight: "clamp" as const, easing: ease } + const s = interpolate(frame, K, [1.82, 1.72, 1.72, 1.0, 1.0], opt) + let fx = interpolate(frame, K, [420, 438, 760, 540, 540], opt) + let fy = interpolate(frame, K, [664, 664, 664, 540, 540], opt) + // keep the framing inside the 1080 canvas so edges never reveal black + fx = clamp(fx, 540 / s, W - 540 / s) + fy = clamp(fy, 540 / s, W - 540 / s) + const camera = `translate(${540 - fx * s}px, ${540 - fy * s}px) scale(${s})` + + // ---------- blue sweep (synced to the pan) ---------- + const p = interpolate(frame, [32, 150], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: ease }) + const revealAmount = p * (days.length - launchIndex) + 0.35 + const fillOf = (i: number) => clamp(revealAmount - (i - launchIndex), 0, 1) + + // token number climbs as the camera pulls back and the headline re-enters frame + const tokensT = + glmWeekTokensT * + interpolate(frame, [150, 202], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }) + + // chart geometry + const chartH = 440 + const chartW = 936 + const gap = 14 + const EXAGGERATE = 2.876 // broken y-axis: GLM-5.2 magnified ~2.9x for emphasis + + return ( + +
+
+ {/* header */} +
+ +
JUN 12–25, 2026
+
+ + {/* headline (static) */} +
+
+ GLM-5.2 broke out +
+
+ From 0 to {tokensT.toFixed(2)}T tokens in a week. +
+
+ + {/* stacked chart */} +
+
+ {/* faint dotted backdrop */} +
+ + {/* columns */} +
+ {days.map((d, i) => { + const glmShare = d.glm / d.total + const blueH = Math.round(glmShare * EXAGGERATE * chartH) + const filled = Math.round(blueH * fillOf(i)) + const slotGap = filled > 2 ? 5 : 0 + const fieldH = chartH - filled - slotGap + const fieldTotal = d.dsf + d.dsp + d.mm + d.others + return ( +
+ {/* gray field of other models (already in place) */} +
+ {field.map((seg) => ( +
+ ))} +
+ {/* GLM-5.2, animates in */} + {filled > 2 && ( +
+ )} +
+ ) + })} +
+
+
+ + {/* day axis */} +
+ {days.map((d, i) => ( +
+ {i === 0 || i === launchIndex || i === days.length - 1 ? d.date : ""} +
+ ))} +
+ + {/* footer */} +
+
+ + GLM-5.2 +
+
opencode.ai/data
+
+
+
+ + ) +} diff --git a/artifacts/glm52-rise-video/sst-env.d.ts b/artifacts/glm52-rise-video/sst-env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..64441936d7a02748b870556190c893d73e560948 --- /dev/null +++ b/artifacts/glm52-rise-video/sst-env.d.ts @@ -0,0 +1,10 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +/* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ + +/// + +import "sst" +export {} \ No newline at end of file diff --git a/github/script/publish b/github/script/publish new file mode 100644 index 0000000000000000000000000000000000000000..ac0e09effd233bc5c6f301c54fbea8bcbb0677e6 --- /dev/null +++ b/github/script/publish @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Get the latest Git tag +latest_tag=$(git tag --sort=committerdate | grep -E '^github-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1) +if [ -z "$latest_tag" ]; then + echo "No tags found" + exit 1 +fi +echo "Latest tag: $latest_tag" + +# Update latest tag +git tag -d latest +git push origin :refs/tags/latest +git tag -a latest $latest_tag -m "Update latest to $latest_tag" +git push origin latest \ No newline at end of file diff --git a/github/script/release b/github/script/release new file mode 100644 index 0000000000000000000000000000000000000000..35180b4543601883160380e09ea3f98f36977cf0 --- /dev/null +++ b/github/script/release @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Parse command line arguments +minor=false +while [ "$#" -gt 0 ]; do + case "$1" in + --minor) minor=true; shift 1;; + *) echo "Unknown parameter: $1"; exit 1;; + esac +done + +# Get the latest Git tag +git fetch --force --tags +latest_tag=$(git tag --sort=committerdate | grep -E '^github-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1) +if [ -z "$latest_tag" ]; then + echo "No tags found" + exit 1 +fi + +echo "Latest tag: $latest_tag" + +# Split the tag into major, minor, and patch numbers +IFS='.' read -ra VERSION <<< "$latest_tag" + +if [ "$minor" = true ]; then + # Increment the minor version and reset patch to 0 + minor_number=${VERSION[1]} + let "minor_number++" + new_version="${VERSION[0]}.$minor_number.0" +else + # Increment the patch version + patch_number=${VERSION[2]} + let "patch_number++" + new_version="${VERSION[0]}.${VERSION[1]}.$patch_number" +fi + +echo "New version: $new_version" + +# Tag +git tag $new_version +git push --tags \ No newline at end of file diff --git a/nix/scripts/canonicalize-node-modules.ts b/nix/scripts/canonicalize-node-modules.ts new file mode 100644 index 0000000000000000000000000000000000000000..7997a3cd2325751c3e581089af094e3e1478a35e --- /dev/null +++ b/nix/scripts/canonicalize-node-modules.ts @@ -0,0 +1,101 @@ +import { lstat, mkdir, readdir, rm, symlink } from "fs/promises" +import { join, relative } from "path" + +type Entry = { + dir: string + version: string +} + +async function isDirectory(path: string) { + try { + const info = await lstat(path) + return info.isDirectory() + } catch { + return false + } +} + +const isValidSemver = (v: string) => Bun.semver.satisfies(v, "x.x.x") + +const root = process.cwd() +const bunRoot = join(root, "node_modules/.bun") +const linkRoot = join(bunRoot, "node_modules") +const directories = (await readdir(bunRoot)).sort() + +const versions = new Map() + +for (const entry of directories) { + const full = join(bunRoot, entry) + if (!(await isDirectory(full))) { + continue + } + const parsed = parseEntry(entry) + if (!parsed) { + continue + } + const list = versions.get(parsed.name) ?? [] + list.push({ dir: full, version: parsed.version }) + versions.set(parsed.name, list) +} + +const selections = new Map() + +for (const [slug, list] of versions) { + list.sort((a, b) => { + const aValid = isValidSemver(a.version) + const bValid = isValidSemver(b.version) + if (aValid && bValid) return -Bun.semver.order(a.version, b.version) + if (aValid) return -1 + if (bValid) return 1 + return b.version.localeCompare(a.version) + }) + const first = list[0] + if (first) selections.set(slug, first) +} + +await rm(linkRoot, { recursive: true, force: true }) +await mkdir(linkRoot, { recursive: true }) + +const rewrites: string[] = [] + +for (const [slug, entry] of Array.from(selections.entries()).sort((a, b) => a[0].localeCompare(b[0]))) { + const parts = slug.split("/") + const leaf = parts.pop() + if (!leaf) { + continue + } + const parent = join(linkRoot, ...parts) + await mkdir(parent, { recursive: true }) + const linkPath = join(parent, leaf) + const desired = join(entry.dir, "node_modules", slug) + if (!(await isDirectory(desired))) { + continue + } + const relativeTarget = relative(parent, desired) + const resolved = relativeTarget.length === 0 ? "." : relativeTarget + await rm(linkPath, { recursive: true, force: true }) + await symlink(resolved, linkPath) + rewrites.push(slug + " -> " + resolved) +} + +rewrites.sort() +console.log("[canonicalize-node-modules] rebuilt", rewrites.length, "links") +for (const line of rewrites.slice(0, 20)) { + console.log(" ", line) +} +if (rewrites.length > 20) { + console.log(" ...") +} + +function parseEntry(label: string) { + const marker = label.startsWith("@") ? label.indexOf("@", 1) : label.indexOf("@") + if (marker <= 0) { + return null + } + const name = label.slice(0, marker).replace(/\+/g, "/") + const version = label.slice(marker + 1) + if (!name || !version) { + return null + } + return { name, version } +} diff --git a/nix/scripts/normalize-bun-binaries.ts b/nix/scripts/normalize-bun-binaries.ts new file mode 100644 index 0000000000000000000000000000000000000000..978ab325b7bb70c951a9b7559634e9435527916a --- /dev/null +++ b/nix/scripts/normalize-bun-binaries.ts @@ -0,0 +1,130 @@ +import { lstat, mkdir, readdir, rm, symlink } from "fs/promises" +import { join, relative } from "path" + +type PackageManifest = { + name?: string + bin?: string | Record +} + +const root = process.cwd() +const bunRoot = join(root, "node_modules/.bun") +const bunEntries = (await readdir(bunRoot)).sort() +let rewritten = 0 + +for (const entry of bunEntries) { + const modulesRoot = join(bunRoot, entry, "node_modules") + if (!(await exists(modulesRoot))) { + continue + } + const binRoot = join(modulesRoot, ".bin") + await rm(binRoot, { recursive: true, force: true }) + await mkdir(binRoot, { recursive: true }) + + const packageDirs = await collectPackages(modulesRoot) + for (const packageDir of packageDirs) { + const manifest = await readManifest(packageDir) + if (!manifest) { + continue + } + const binField = manifest.bin + if (!binField) { + continue + } + const seen = new Set() + if (typeof binField === "string") { + const fallback = manifest.name ?? packageDir.split("/").pop() + if (fallback) { + await linkBinary(binRoot, fallback, packageDir, binField, seen) + } + } else { + const entries = Object.entries(binField).sort((a, b) => a[0].localeCompare(b[0])) + for (const [name, target] of entries) { + await linkBinary(binRoot, name, packageDir, target, seen) + } + } + } +} + +console.log(`[normalize-bun-binaries] rebuilt ${rewritten} links`) + +async function collectPackages(modulesRoot: string) { + const found: string[] = [] + const topLevel = (await readdir(modulesRoot)).sort() + for (const name of topLevel) { + if (name === ".bin" || name === ".bun") { + continue + } + const full = join(modulesRoot, name) + if (!(await isDirectory(full))) { + continue + } + if (name.startsWith("@")) { + const scoped = (await readdir(full)).sort() + for (const child of scoped) { + const scopedDir = join(full, child) + if (await isDirectory(scopedDir)) { + found.push(scopedDir) + } + } + continue + } + found.push(full) + } + return found.sort() +} + +async function readManifest(dir: string) { + const file = Bun.file(join(dir, "package.json")) + if (!(await file.exists())) { + return null + } + const data = (await file.json()) as PackageManifest + return data +} + +async function linkBinary(binRoot: string, name: string, packageDir: string, target: string, seen: Set) { + if (!name || !target) { + return + } + const normalizedName = normalizeBinName(name) + if (seen.has(normalizedName)) { + return + } + const resolved = join(packageDir, target) + const script = Bun.file(resolved) + if (!(await script.exists())) { + return + } + seen.add(normalizedName) + const destination = join(binRoot, normalizedName) + const relativeTarget = relative(binRoot, resolved) || "." + await rm(destination, { force: true }) + await symlink(relativeTarget, destination) + rewritten++ +} + +async function exists(path: string) { + try { + await lstat(path) + return true + } catch { + return false + } +} + +async function isDirectory(path: string) { + try { + const info = await lstat(path) + return info.isDirectory() + } catch { + return false + } +} + +function normalizeBinName(name: string) { + const slash = name.lastIndexOf("/") + if (slash >= 0) { + return name.slice(slash + 1) + } + return name +} diff --git a/packages/effect-drizzle-sqlite/examples/basic.ts b/packages/effect-drizzle-sqlite/examples/basic.ts new file mode 100644 index 0000000000000000000000000000000000000000..80397cd6e6c491d2591825ef009f2101bfb54793 --- /dev/null +++ b/packages/effect-drizzle-sqlite/examples/basic.ts @@ -0,0 +1,92 @@ +import { SqliteClient } from "@effect/sql-sqlite-bun" +import { eq } from "drizzle-orm" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Schema from "effect/Schema" +import { EffectDrizzleSqlite } from "../src" + +const users = sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), +}) + +type User = typeof users.$inferSelect + +const makeDatabase = EffectDrizzleSqlite.makeWithDefaults() +type DatabaseShape = Effect.Success + +const sqliteLayer = SqliteClient.layer({ filename: ":memory:", disableWAL: true }) + +class Database extends Context.Service()("@opencode/example/Database") { + static layer = Layer.effect(Database, makeDatabase).pipe(Layer.provide(sqliteLayer)) +} + +class UserStoreError extends Schema.TaggedErrorClass()("UserStoreError", { + message: Schema.String, + cause: Schema.optional(Schema.Defect()), +}) {} + +const mapStoreError = (message: string) => (cause: unknown) => new UserStoreError({ message, cause }) + +interface UserStoreShape { + migrate(): Effect.Effect + create(name: string): Effect.Effect + rename(from: string, to: string): Effect.Effect + list(): Effect.Effect +} + +class UserStore extends Context.Service()("@opencode/example/UserStore") { + static layer = Layer.effect( + UserStore, + Effect.gen(function* () { + const db = yield* Database + + return UserStore.of({ + migrate: Effect.fn("UserStore.migrate")(function* () { + yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder: `${import.meta.dirname}/migrations` }).pipe( + Effect.mapError((cause) => new UserStoreError({ message: "Failed to migrate users", cause })), + ) + }), + create: Effect.fn("UserStore.create")(function* (name: string) { + yield* db + .insert(users) + .values({ name }) + .pipe(Effect.asVoid, Effect.mapError(mapStoreError("Failed to create user"))) + }), + rename: Effect.fn("UserStore.rename")(function* (from: string, to: string) { + yield* db + .transaction( + Effect.fnUntraced(function* (tx) { + yield* tx.insert(users).values({ name: from }) + yield* tx.update(users).set({ name: to }).where(eq(users.name, from)) + }), + { behavior: "immediate" }, + ) + .pipe(Effect.asVoid, Effect.mapError(mapStoreError("Failed to rename user"))) + }), + list: Effect.fn("UserStore.list")(function* () { + return yield* db + .select() + .from(users) + .pipe(Effect.mapError(mapStoreError("Failed to list users"))) + }), + }) + }), + ).pipe(Layer.provide(Database.layer)) +} + +const program = Effect.gen(function* () { + const userStore = yield* UserStore + + yield* userStore.migrate() + yield* userStore.create("Ada") + yield* userStore.rename("Grace", "Grace Hopper") + + return yield* userStore.list() +}) + +const rows = await Effect.runPromise(program.pipe(Effect.provide(UserStore.layer))) + +console.log(rows) diff --git a/packages/effect-drizzle-sqlite/examples/migrations/20240101000000_create_users/migration.sql b/packages/effect-drizzle-sqlite/examples/migrations/20240101000000_create_users/migration.sql new file mode 100644 index 0000000000000000000000000000000000000000..f1aa81cb45407e77095bf1477affafb31526efe9 --- /dev/null +++ b/packages/effect-drizzle-sqlite/examples/migrations/20240101000000_create_users/migration.sql @@ -0,0 +1,4 @@ +CREATE TABLE users ( + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + name text NOT NULL +); diff --git a/packages/effect-drizzle-sqlite/src/effect-sqlite/driver.ts b/packages/effect-drizzle-sqlite/src/effect-sqlite/driver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2ffc207d355b4227c39dbc831d7701786330256 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/effect-sqlite/driver.ts @@ -0,0 +1,77 @@ +/* oxlint-disable */ +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import { SqlClient } from "effect/unstable/sql/SqlClient" +import { EffectCache } from "drizzle-orm/cache/core/cache-effect" +import { EffectLogger } from "drizzle-orm/effect-core" +import { entityKind } from "drizzle-orm/entity" +import type { AnyRelations, EmptyRelations } from "drizzle-orm/relations" +import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" +import { SQLiteEffectDatabase } from "../sqlite-core/effect/db" +import type { DrizzleConfig } from "drizzle-orm/utils" +import { jitCompatCheck } from "../internal/drizzle-utils" +import { type EffectSQLiteQueryEffectHKT, type EffectSQLiteRunResult, EffectSQLiteSession } from "./session" + +export class EffectSQLiteDatabase extends SQLiteEffectDatabase< + EffectSQLiteQueryEffectHKT, + EffectSQLiteRunResult, + TRelations +> { + static override readonly [entityKind]: string = "EffectSQLiteDatabase" +} + +export type EffectDrizzleSQLiteConfig = Omit< + DrizzleConfig, TRelations>, + "cache" | "logger" | "schema" +> + +export const DefaultServices = Layer.merge(EffectCache.Default, EffectLogger.Default) + +/** + * Creates an EffectSQLiteDatabase instance. + * + * Requires a generic Effect `SqlClient`, `EffectLogger`, and `EffectCache` services to be provided. + * Drizzle only depends on the generic `SqlClient`; install and provide a compatible SQLite provider such as + * `@effect/sql-sqlite-node`, `@effect/sql-sqlite-bun`, or another package that exposes `SqlClient`. + * + * @example + * ```ts + * import { SqliteClient } from '@effect/sql-sqlite-node'; + * import * as SQLiteDrizzle from 'drizzle-orm/effect-sqlite'; + * import * as Effect from 'effect/Effect'; + * + * const db = yield* SQLiteDrizzle.make({ relations }).pipe( + * Effect.provide(SQLiteDrizzle.DefaultServices), + * Effect.provide(SqliteClient.layer({ filename: 'sqlite.db' })), + * ); + * ``` + */ +export const make = Effect.fn("SQLiteDrizzle.make")(function* ( + config: EffectDrizzleSQLiteConfig = {}, +) { + const client = yield* SqlClient + const cache = yield* EffectCache + const logger = yield* EffectLogger + + const dialect = new SQLiteAsyncDialect() + const relations = config.relations ?? ({} as TRelations) + const session = new EffectSQLiteSession(client, dialect, relations, { + logger, + cache, + useJitMappers: jitCompatCheck(config.jit), + }) + const db = new EffectSQLiteDatabase(dialect, session, relations) as EffectSQLiteDatabase & { + $client: SqlClient + } + db.$client = client + db.$cache.invalidate = cache.onMutate + + return db +}) + +/** + * Convenience function that creates an EffectSQLiteDatabase with `DefaultServices` already provided. + */ +export const makeWithDefaults = ( + config: EffectDrizzleSQLiteConfig = {}, +) => make(config).pipe(Effect.provide(DefaultServices)) diff --git a/packages/effect-drizzle-sqlite/src/effect-sqlite/index.ts b/packages/effect-drizzle-sqlite/src/effect-sqlite/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1133e48b3a5295a0fd8d95c50e1d4772d08220f4 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/effect-sqlite/index.ts @@ -0,0 +1,4 @@ +/* oxlint-disable */ +export { EffectLogger } from "drizzle-orm/effect-core" +export * from "./driver" +export * from "./session" diff --git a/packages/effect-drizzle-sqlite/src/effect-sqlite/migrator.ts b/packages/effect-drizzle-sqlite/src/effect-sqlite/migrator.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d0d15514373a98b149083ef04ef42e48ccbf10f --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/effect-sqlite/migrator.ts @@ -0,0 +1,14 @@ +/* oxlint-disable */ +import type { MigrationConfig } from "drizzle-orm/migrator" +import { readMigrationFiles } from "drizzle-orm/migrator" +import type { AnyRelations } from "drizzle-orm/relations" +import { migrate as coreMigrate } from "../sqlite-core/effect/session" +import type { EffectSQLiteDatabase } from "./driver" + +export function migrate( + db: EffectSQLiteDatabase, + config: MigrationConfig, +) { + const migrations = readMigrationFiles(config) + return coreMigrate(migrations, db.session, config) +} diff --git a/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts b/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts new file mode 100644 index 0000000000000000000000000000000000000000..cba4c11bd42470cb622c1d93b478a434dc48c74e --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts @@ -0,0 +1,218 @@ +/* oxlint-disable */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as Scope from "effect/Scope" +import type { SqlClient } from "effect/unstable/sql/SqlClient" +import type { SqlError } from "effect/unstable/sql/SqlError" +import type { EffectCacheShape } from "drizzle-orm/cache/core/cache-effect" +import type { WithCacheConfig } from "drizzle-orm/cache/core/types" +import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core/errors" +import type { EffectLoggerShape } from "drizzle-orm/effect-core/logger" +import type { QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import type { AnyRelations } from "drizzle-orm/relations" +import type { RelationalQueryMapperConfig } from "drizzle-orm/relations" +import type { Query } from "drizzle-orm/sql/sql" +import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" +import { SQLiteEffectPreparedQuery, SQLiteEffectSession, SQLiteEffectTransaction } from "../sqlite-core/effect/session" +import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { PreparedQueryConfig, SQLiteExecuteMethod, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" + +export interface EffectSQLiteQueryEffectHKT extends QueryEffectHKTBase { + readonly error: EffectDrizzleQueryError + readonly context: never +} + +export type EffectSQLiteRunResult = readonly never[] + +export interface EffectSQLiteSessionOptions { + logger: EffectLoggerShape + cache: EffectCacheShape + useJitMappers?: boolean +} + +export class EffectSQLiteSession extends SQLiteEffectSession< + EffectSQLiteQueryEffectHKT, + EffectSQLiteRunResult, + TRelations +> { + static override readonly [entityKind]: string = "EffectSQLiteSession" + + constructor( + private client: SqlClient, + dialect: SQLiteAsyncDialect, + protected relations: TRelations, + private options: EffectSQLiteSessionOptions, + ) { + super(dialect) + } + + override prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, + queryMetadata?: { + type: "select" | "update" | "delete" | "insert" + tables: string[] + }, + cacheConfig?: WithCacheConfig, + ): SQLiteEffectPreparedQuery { + return new SQLiteEffectPreparedQuery( + (params, method) => this.execute(query, params, method), + query, + this.options.logger, + this.options.cache, + queryMetadata, + cacheConfig, + fields, + executeMethod, + this.options.useJitMappers, + customResultMapper, + undefined, + undefined, + this.isInTransaction(), + ) + } + + override prepareRelationalQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper: (rows: Record[], mapColumnValue?: (value: unknown) => unknown) => unknown, + config: RelationalQueryMapperConfig, + ): SQLiteEffectPreparedQuery { + return new SQLiteEffectPreparedQuery( + (params, method) => this.execute(query, params, method), + query, + this.options.logger, + this.options.cache, + undefined, + undefined, + fields, + executeMethod, + this.options.useJitMappers, + customResultMapper, + true, + config, + this.isInTransaction(), + ) + } + + private execute(query: Query, params: unknown[], method: SQLiteExecuteMethod | "values") { + const statement = this.client.unsafe(query.sql, params) + if (method === "values") return statement.values + if (method === "get") return statement.withoutTransform.pipe(Effect.map((rows) => rows[0])) + return statement.withoutTransform + } + + private isInTransaction() { + return Effect.serviceOption(this.client.transactionService).pipe(Effect.map((option) => option._tag === "Some")) + } + + private executeTransactionStatement(connection: Effect.Success, query: string) { + return connection.executeUnprepared(query, [], undefined).pipe(Effect.asVoid) + } + + private withTransaction(effect: Effect.Effect, config: SQLiteTransactionConfig | undefined) { + return Effect.uninterruptibleMask((restore) => + Effect.withFiber((fiber) => { + const services = fiber.context + const connectionOption = Context.getOption(services, this.client.transactionService) + const connection: Effect.Effect< + readonly [Scope.Closeable | undefined, Effect.Success], + SqlError + > = + connectionOption._tag === "Some" + ? Effect.succeed([undefined, connectionOption.value[0]] as const) + : Scope.make().pipe( + Effect.flatMap((scope) => + Scope.provide(this.client.reserve, scope).pipe( + Effect.map((connection) => [scope, connection] as const), + Effect.catch((error) => + Scope.close(scope, Exit.fail(error)).pipe(Effect.andThen(Effect.fail(error))), + ), + ), + ), + ) + const id = connectionOption._tag === "Some" ? connectionOption.value[1] + 1 : 0 + + return connection.pipe( + Effect.flatMap(([scope, connection]) => { + const transaction = this.executeTransactionStatement( + connection, + id === 0 ? `begin ${config?.behavior ?? "deferred"}` : `savepoint effect_sql_${id}`, + ).pipe( + Effect.flatMap(() => + Effect.provideContext( + restore(effect), + Context.add(services, this.client.transactionService, [connection, id]), + ).pipe( + Effect.exit, + Effect.flatMap((exit) => { + const finalize = Exit.isSuccess(exit) + ? id === 0 + ? this.executeTransactionStatement(connection, "commit").pipe( + // SQLite keeps the transaction open after deferred constraint commit failures. + Effect.catch((error) => + this.executeTransactionStatement(connection, "rollback").pipe( + Effect.catch(() => Effect.void), + Effect.andThen(Effect.fail(error)), + ), + ), + ) + : this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`) + : id === 0 + ? this.executeTransactionStatement(connection, "rollback") + : this.executeTransactionStatement(connection, `rollback to savepoint effect_sql_${id}`).pipe( + Effect.andThen( + this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`), + ), + ) + + return finalize.pipe(Effect.flatMap(() => exit)) + }), + ), + ), + ) + + return scope === undefined + ? transaction + : transaction.pipe(Effect.onExit((exit) => Scope.close(scope, exit))) + }), + ) + }), + ) + } + + override transaction( + transaction: (tx: EffectSQLiteTransaction) => Effect.Effect, + config?: SQLiteTransactionConfig, + ): Effect.Effect { + const { dialect, relations } = this + + return this.withTransaction( + Effect.gen({ self: this }, function* () { + const tx = new EffectSQLiteTransaction(dialect, this, relations) + + return yield* transaction(tx) + }), + config, + ) + } +} + +export class EffectSQLiteTransaction extends SQLiteEffectTransaction< + EffectSQLiteQueryEffectHKT, + EffectSQLiteRunResult, + TRelations +> { + static override readonly [entityKind]: string = "EffectSQLiteTransaction" + + override transaction: ( + transaction: ( + tx: SQLiteEffectTransaction, + ) => Effect.Effect, + ) => Effect.Effect = (tx) => this.session.transaction(tx) +} diff --git a/packages/effect-drizzle-sqlite/src/index.ts b/packages/effect-drizzle-sqlite/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6606b7d9fe153801a8ec42ce3a5d29d0ee6c8e5 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/index.ts @@ -0,0 +1,6 @@ +export { EffectLogger } from "drizzle-orm/effect-core" +export * from "./effect-sqlite/driver" +export * from "./effect-sqlite/session" +export { migrate } from "./effect-sqlite/migrator" + +export * as EffectDrizzleSqlite from "." diff --git a/packages/effect-drizzle-sqlite/src/internal/drizzle-utils.ts b/packages/effect-drizzle-sqlite/src/internal/drizzle-utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..1998a08318cd5f2cd80a7b8d3e63b94b2bb039c9 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/internal/drizzle-utils.ts @@ -0,0 +1,127 @@ +/* oxlint-disable */ +import { Column, getColumnTable } from "drizzle-orm/column" +import { is } from "drizzle-orm/entity" +import type { JoinNullability } from "drizzle-orm/query-builders/select.types" +import { Param, SQL } from "drizzle-orm/sql/sql" +import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { SQLiteUpdateSetSource } from "drizzle-orm/sqlite-core/query-builders/update" +import type { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import { SQLiteViewBase } from "drizzle-orm/sqlite-core/view-base" +import { Subquery } from "drizzle-orm/subquery" +import { Table, getTableName } from "drizzle-orm/table" +import type { UpdateSet } from "drizzle-orm/utils" +import { ViewBaseConfig } from "drizzle-orm/view-common" + +const TableSymbol = ( + Table as unknown as { + Symbol: { Columns: symbol; IsAlias: symbol; Name: symbol; BaseName: symbol } + } +).Symbol + +export function getTableColumnsRuntime(table: SQLiteTable) { + return (table as unknown as Record>)[TableSymbol.Columns] +} + +export function getViewSelectedFieldsRuntime(view: SQLiteViewBase) { + return (view as unknown as Record; name: string }>)[ViewBaseConfig] +} + +export function jitCompatCheck(isEnabled: boolean | undefined) { + if (!isEnabled) return false + try { + return new Function("input", '"use strict"; return input;')(true) === true + } catch { + return false + } +} + +export function orderSelectedFields( + fields: Record, + pathPrefix?: string[], +): SelectedFieldsOrdered { + return Object.entries(fields).flatMap(([name, field]) => { + const path = pathPrefix ? [...pathPrefix, name] : [name] + if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased) || is(field, Subquery)) { + return [{ path, field }] as SelectedFieldsOrdered + } + if (is(field, Table)) return orderSelectedFields(getTableColumnsRuntime(field as SQLiteTable), path) + return orderSelectedFields(field as Record, path) + }) as SelectedFieldsOrdered +} + +export function mapUpdateSet(table: TTable, values: SQLiteUpdateSetSource) { + const entries = Object.entries(values).filter(([, value]) => value !== undefined) + if (entries.length === 0) throw new Error("No values to set") + + return Object.fromEntries( + entries.map(([key, value]) => [ + key, + is(value, SQL) || is(value, Column) ? value : new Param(value, getTableColumnsRuntime(table)[key]), + ]), + ) as UpdateSet +} + +export function mapResultRow( + columns: SelectedFieldsOrdered, + row: unknown[], + joinsNotNullableMap: Record | undefined, +) { + const nullifyMap: Record = {} + const result: Record = {} + + columns.forEach((column, columnIndex) => { + const decoder = ( + is(column.field, Column) + ? column.field + : is(column.field, SQL) + ? (column.field as unknown as { decoder: { mapFromDriverValue(value: unknown): unknown } }).decoder + : is(column.field, Subquery) + ? (column.field._.sql as unknown as { decoder: { mapFromDriverValue(value: unknown): unknown } }).decoder + : (column.field.sql as unknown as { decoder: { mapFromDriverValue(value: unknown): unknown } }).decoder + ) as { + mapFromDriverValue(value: unknown): unknown + } + const rawValue = row[columnIndex] + const value = rawValue === null ? null : decoder.mapFromDriverValue(rawValue) + const objectName = column.path[0] + let node = result + + column.path.forEach((pathChunk, pathChunkIndex) => { + if (pathChunkIndex === column.path.length - 1) { + node[pathChunk] = value + return + } + node[pathChunk] = (node[pathChunk] ?? {}) as Record + node = node[pathChunk] as Record + }) + + if (joinsNotNullableMap && is(column.field, Column) && column.path.length === 2 && objectName) { + const tableName = getTableName(getColumnTable(column.field)) + nullifyMap[objectName] = + !(objectName in nullifyMap) && value === null + ? tableName + : typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== tableName + ? false + : nullifyMap[objectName] + } + }) + + Object.entries(nullifyMap).forEach(([objectName, tableName]) => { + if (typeof tableName === "string" && !joinsNotNullableMap?.[tableName]) result[objectName] = null + }) + + return result +} + +export function getTableLikeName(table: SQLiteTable | Subquery | SQLiteViewBase | SQL) { + if (is(table, Subquery)) return table._.alias + if (is(table, SQLiteViewBase)) return getViewSelectedFieldsRuntime(table).name + if (is(table, SQL)) return undefined + return (table as unknown as Record)[ + (table as unknown as Record)[TableSymbol.IsAlias] + ? TableSymbol.Name + : TableSymbol.BaseName + ] as string +} + +export type { JoinNullability } diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/count.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/count.ts new file mode 100644 index 0000000000000000000000000000000000000000..c420d6fbb052940756945507b2141984eb23d0bd --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/count.ts @@ -0,0 +1,58 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import { SQL, sql, type SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import type { SQLiteView } from "drizzle-orm/sqlite-core/view" +import type { SQLiteEffectSession } from "./session" + +function buildSQLiteEmbeddedCount(source: SQLiteTable | SQLiteView | SQL | SQLWrapper, filters?: SQL) { + return sql`(select count(*) from ${source}${sql.raw(" where ").if(filters)}${filters})` +} + +function buildSQLiteCount(source: SQLiteTable | SQLiteView | SQL | SQLWrapper, filters?: SQL) { + return sql`select count(*) from ${source}${sql.raw(" where ").if(filters)}${filters}` +} + +export interface SQLiteEffectCountBuilder + extends SQL, + SQLWrapper, + Effect.Effect {} + +export class SQLiteEffectCountBuilder extends SQL { + static override readonly [entityKind]: string = "SQLiteEffectCountBuilder" + + private sql: SQL + private session: SQLiteEffectSession + + constructor(params: { + source: SQLiteTable | SQLiteView | SQL | SQLWrapper + filters?: SQL + session: SQLiteEffectSession + }) { + super(buildSQLiteEmbeddedCount(params.source, params.filters).queryChunks) + + this.session = params.session + this.sql = buildSQLiteCount(params.source, params.filters) + } + + execute(placeholderValues?: Record) { + return this.session + .prepareQuery<{ + type: "async" + execute: number + run: unknown + all: unknown + get: unknown + values: unknown + }>(this.session.dialect.sqlToQuery(this.sql), undefined, "all", (rows) => { + const v = rows[0]?.[0] + if (typeof v === "number") return v + return v ? Number(v) : 0 + }) + .execute(placeholderValues) + } +} + +applyEffectWrapper(SQLiteEffectCountBuilder) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/db.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/db.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac4cc140ad0d80d397ff0712a525780a00f5a7d7 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/db.ts @@ -0,0 +1,296 @@ +/* oxlint-disable */ +import { Effect } from "effect" +import type { SqlError } from "effect/unstable/sql/SqlError" +import type { EffectCacheShape } from "drizzle-orm/cache/core/cache-effect" +import type { MutationOption } from "drizzle-orm/cache/core/cache" +import type { QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import type { TypedQueryBuilder } from "drizzle-orm/query-builders/query-builder" +import type { AnyRelations, EmptyRelations } from "drizzle-orm/relations" +import { SelectionProxyHandler } from "drizzle-orm/selection-proxy" +import { type ColumnsSelection, type SQL, sql, type SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" +import { QueryBuilder } from "drizzle-orm/sqlite-core/query-builders/query-builder" +import type { SelectedFields } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" +import type { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import type { SQLiteViewBase } from "drizzle-orm/sqlite-core/view-base" +import { WithSubquery } from "drizzle-orm/subquery" +import type { WithBuilder } from "drizzle-orm/sqlite-core/subquery" +import { SQLiteEffectCountBuilder } from "./count" +import { SQLiteEffectDeleteBase } from "./delete" +import { SQLiteEffectInsertBuilder } from "./insert" +import { SQLiteEffectRelationalQueryBuilder } from "./query" +import { SQLiteEffectRaw } from "./raw" +import { SQLiteEffectSelectBuilder } from "./select" +import type { SQLiteEffectSelectBase } from "./select" +import type { SQLiteEffectSession, SQLiteEffectTransaction } from "./session" +import { SQLiteEffectUpdateBuilder } from "./update" + +export class SQLiteEffectDatabase< + TEffectHKT extends QueryEffectHKTBase, + TRunResult, + TRelations extends AnyRelations = EmptyRelations, +> { + static readonly [entityKind]: string = "SQLiteEffectDatabase" + + declare readonly _: { + readonly relations: TRelations + readonly session: SQLiteEffectSession + } + + query: { + [K in keyof TRelations]: SQLiteEffectRelationalQueryBuilder + } + + constructor( + /** @internal */ + readonly dialect: SQLiteAsyncDialect, + /** @internal */ + readonly session: SQLiteEffectSession, + relations: TRelations, + readonly rowModeRQB?: boolean, + readonly forbidJsonb?: boolean, + ) { + this._ = { + relations, + session, + } + + this.query = {} as (typeof this)["query"] + for (const [tableName, relation] of Object.entries(relations)) { + ;(this.query as SQLiteEffectDatabase["query"])[tableName] = + new SQLiteEffectRelationalQueryBuilder( + relations, + relations[relation.name]!.table as SQLiteTable, + relation, + dialect, + session, + rowModeRQB, + forbidJsonb, + ) + } + + this.$cache = { + invalidate: (_params: MutationOption) => Effect.void, + } + } + + $with: WithBuilder = (alias: string, selection?: ColumnsSelection) => { + const self = this + const as = ( + qb: + | TypedQueryBuilder + | SQL + | ((qb: QueryBuilder) => TypedQueryBuilder | SQL), + ) => { + if (typeof qb === "function") { + qb = qb(new QueryBuilder(self.dialect)) + } + + return new Proxy( + new WithSubquery( + qb.getSQL(), + selection ?? + (("getSelectedFields" in qb + ? ((qb as { getSelectedFields(): SelectedFields | undefined }).getSelectedFields() ?? {}) + : {}) as SelectedFields), + alias, + true, + ), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: "alias", sqlBehavior: "error" }), + ) + } + return { as } + } + + $cache: { invalidate: EffectCacheShape["onMutate"] } + + $count(source: SQLiteTable | SQLiteViewBase | SQL | SQLWrapper, filters?: SQL) { + return new SQLiteEffectCountBuilder({ source, filters, session: this.session }) + } + + with(...queries: WithSubquery[]) { + const self = this + + function select(): SQLiteEffectSelectBuilder + function select( + fields: TSelection, + ): SQLiteEffectSelectBuilder + function select( + fields?: SelectedFields, + ): SQLiteEffectSelectBuilder { + return new SQLiteEffectSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + }) + } + + function selectDistinct(): SQLiteEffectSelectBuilder + function selectDistinct( + fields: TSelection, + ): SQLiteEffectSelectBuilder + function selectDistinct( + fields?: SelectedFields, + ): SQLiteEffectSelectBuilder { + return new SQLiteEffectSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + distinct: true, + }) + } + + function update( + table: TTable, + ): SQLiteEffectUpdateBuilder { + return new SQLiteEffectUpdateBuilder(table, self.session, self.dialect, queries) + } + + function insert( + into: TTable, + ): SQLiteEffectInsertBuilder { + return new SQLiteEffectInsertBuilder(into, self.session, self.dialect, queries) + } + + function delete_( + from: TTable, + ): SQLiteEffectDeleteBase { + return new SQLiteEffectDeleteBase(from, self.session, self.dialect, queries) + } + + return { select, selectDistinct, update, insert, delete: delete_ } + } + + select(): SQLiteEffectSelectBuilder + select( + fields: TSelection, + ): SQLiteEffectSelectBuilder + select(fields?: SelectedFields): SQLiteEffectSelectBuilder { + return new SQLiteEffectSelectBuilder({ fields: fields ?? undefined, session: this.session, dialect: this.dialect }) + } + + selectDistinct(): SQLiteEffectSelectBuilder + selectDistinct( + fields: TSelection, + ): SQLiteEffectSelectBuilder + selectDistinct( + fields?: SelectedFields, + ): SQLiteEffectSelectBuilder { + return new SQLiteEffectSelectBuilder({ + fields: fields ?? undefined, + session: this.session, + dialect: this.dialect, + distinct: true, + }) + } + + update(table: TTable): SQLiteEffectUpdateBuilder { + return new SQLiteEffectUpdateBuilder(table, this.session, this.dialect) + } + + insert(into: TTable): SQLiteEffectInsertBuilder { + return new SQLiteEffectInsertBuilder(into, this.session, this.dialect) + } + + delete( + from: TTable, + ): SQLiteEffectDeleteBase { + return new SQLiteEffectDeleteBase(from, this.session, this.dialect) + } + + private raw( + query: SQLWrapper | string, + action: "all" | "get" | "run" | "values", + execute: (query: SQL) => Effect.Effect, + ): SQLiteEffectRaw { + const sequel = typeof query === "string" ? sql.raw(query) : query.getSQL() + return new SQLiteEffectRaw( + () => execute(sequel), + () => sequel, + action, + this.dialect, + (result) => result, + ) + } + + run(query: SQLWrapper | string): SQLiteEffectRaw { + return this.raw(query, "run", (sequel) => this.session.run(sequel)) + } + + all(query: SQLWrapper | string): SQLiteEffectRaw { + return this.raw(query, "all", (sequel) => this.session.all(sequel)) + } + + get(query: SQLWrapper | string): SQLiteEffectRaw { + return this.raw(query, "get", (sequel) => this.session.get(sequel)) + } + + values(query: SQLWrapper | string): SQLiteEffectRaw { + return this.raw(query, "values", (sequel) => this.session.values(sequel)) + } + + transaction: ( + transaction: (tx: SQLiteEffectTransaction) => Effect.Effect, + config?: SQLiteTransactionConfig, + ) => Effect.Effect = (tx, config) => this.session.transaction(tx, config) +} + +export type SQLiteEffectWithReplicas = Q & { $primary: Q; $replicas: Q[] } + +export const withReplicas = < + TEffectHKT extends QueryEffectHKTBase, + TRunResult, + TRelations extends AnyRelations, + Q extends SQLiteEffectDatabase, +>( + primary: Q, + replicas: [Q, ...Q[]], + getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, +): SQLiteEffectWithReplicas => { + const select: Q["select"] = (...args: []) => getReplica(replicas).select(...args) + const selectDistinct: Q["selectDistinct"] = (...args: []) => getReplica(replicas).selectDistinct(...args) + const $count: Q["$count"] = (...args: [any]) => getReplica(replicas).$count(...args) + const _with: Q["with"] = (...args: []) => getReplica(replicas).with(...args) + const $with = ((...args: [string] | [string, ColumnsSelection]) => + args.length === 1 + ? getReplica(replicas).$with(args[0]) + : getReplica(replicas).$with(args[0], args[1])) as Q["$with"] + + const update: Q["update"] = (...args: [any]) => primary.update(...args) + const insert: Q["insert"] = (...args: [any]) => primary.insert(...args) + const $delete: Q["delete"] = (...args: [any]) => primary.delete(...args) + const run: Q["run"] = (...args: [any]) => primary.run(...args) + const all: Q["all"] = (...args: [any]) => primary.all(...args) + const get: Q["get"] = (...args: [any]) => primary.get(...args) + const values: Q["values"] = (...args: [any]) => primary.values(...args) + const transaction: Q["transaction"] = (...args: [any]) => primary.transaction(...args) + + return { + ...primary, + update, + insert, + delete: $delete, + run, + all, + get, + values, + transaction, + $primary: primary, + $replicas: replicas, + select, + selectDistinct, + $count, + $with, + with: _with, + get query() { + return getReplica(replicas).query + }, + } +} + +export type AnySQLiteEffectDatabase = SQLiteEffectDatabase +export type AnySQLiteEffectSelectBase = SQLiteEffectSelectBase diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/delete.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/delete.ts new file mode 100644 index 0000000000000000000000000000000000000000..15e90d19746e6d713735acc3eb8c24d151c75c22 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/delete.ts @@ -0,0 +1,261 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import type { SelectResultFields } from "drizzle-orm/query-builders/select.types" +import type { RunnableQuery } from "drizzle-orm/runnable-query" +import { SelectionProxyHandler } from "drizzle-orm/selection-proxy" +import type { Placeholder, Query, SQL, SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteDialect } from "drizzle-orm/sqlite-core/dialect" +import type { SQLiteDeleteConfig } from "drizzle-orm/sqlite-core/query-builders/delete" +import type { SelectedFieldsFlat } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { PreparedQueryConfig } from "drizzle-orm/sqlite-core/session" +import { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import { extractUsedTable } from "drizzle-orm/sqlite-core/utils" +import type { Subquery } from "drizzle-orm/subquery" +import { type DrizzleTypeError, type ValueOrArray } from "drizzle-orm/utils" +import type { SQLiteColumn } from "drizzle-orm/sqlite-core/columns/common" +import { getTableColumnsRuntime, orderSelectedFields } from "../../internal/drizzle-utils" +import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" + +export type SQLiteEffectDeleteWithout< + T extends AnySQLiteEffectDelete, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true + ? T + : Omit< + SQLiteEffectDeleteBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["returning"], + TDynamic, + T["_"]["excludedMethods"] | K, + T["_"]["effectHKT"] + >, + T["_"]["excludedMethods"] | K + > + +export type SQLiteEffectDeleteReturningAll< + T extends AnySQLiteEffectDelete, + TDynamic extends boolean, +> = SQLiteEffectDeleteWithout< + SQLiteEffectDeleteBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["table"]["$inferSelect"], + T["_"]["dynamic"], + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectDeleteReturning< + T extends AnySQLiteEffectDelete, + TDynamic extends boolean, + TSelectedFields extends SelectedFieldsFlat, +> = SQLiteEffectDeleteWithout< + SQLiteEffectDeleteBase< + T["_"]["table"], + T["_"]["runResult"], + SelectResultFields, + T["_"]["dynamic"], + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectDeleteExecute = T["_"]["returning"] extends undefined + ? T["_"]["runResult"] + : T["_"]["returning"][] + +export type SQLiteEffectDeletePrepare< + T extends AnySQLiteEffectDelete, + TEffectHKT extends QueryEffectHKTBase = T["_"]["effectHKT"], +> = SQLiteEffectPreparedQuery< + PreparedQueryConfig & { + run: T["_"]["runResult"] + all: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".all() cannot be used without .returning()"> + : T["_"]["returning"][] + get: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".get() cannot be used without .returning()"> + : T["_"]["returning"] | undefined + values: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".values() cannot be used without .returning()"> + : any[][] + execute: SQLiteEffectDeleteExecute + }, + TEffectHKT +> + +export type SQLiteEffectDeleteDynamic = SQLiteEffectDelete< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["returning"], + T["_"]["effectHKT"] +> + +export type SQLiteEffectDelete< + TTable extends SQLiteTable = SQLiteTable, + TRunResult = unknown, + TReturning extends Record | undefined = undefined, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> = SQLiteEffectDeleteBase + +export type AnySQLiteEffectDelete = SQLiteEffectDeleteBase + +export interface SQLiteEffectDeleteBase< + TTable extends SQLiteTable, + TRunResult, + TReturning extends Record | undefined = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> extends RunnableQuery, + SQLWrapper, + Effect.Effect< + TReturning extends undefined ? TRunResult : TReturning[], + TEffectHKT["error"], + TEffectHKT["context"] + > { + readonly _: { + dialect: "sqlite" + readonly table: TTable + readonly resultType: "async" + readonly runResult: TRunResult + readonly returning: TReturning + readonly dynamic: TDynamic + readonly excludedMethods: _TExcludedMethods + readonly result: TReturning extends undefined ? TRunResult : TReturning[] + readonly effectHKT: TEffectHKT + } +} + +export class SQLiteEffectDeleteBase< + TTable extends SQLiteTable, + TRunResult, + TReturning extends Record | undefined = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + > + implements RunnableQuery, SQLWrapper +{ + static readonly [entityKind]: string = "SQLiteEffectDelete" + + /** @internal */ + config: SQLiteDeleteConfig + + constructor( + private table: TTable, + private effectSession: SQLiteEffectSession, + private effectDialect: SQLiteDialect, + withList?: Subquery[], + ) { + this.config = { table, withList } + } + + where(where: SQL | undefined): SQLiteEffectDeleteWithout { + this.config.where = where + return this as any + } + + orderBy( + builder: (deleteTable: TTable) => ValueOrArray, + ): SQLiteEffectDeleteWithout + orderBy(...columns: (SQLiteColumn | SQL | SQL.Aliased)[]): SQLiteEffectDeleteWithout + orderBy( + ...columns: + | [(deleteTable: TTable) => ValueOrArray] + | (SQLiteColumn | SQL | SQL.Aliased)[] + ): SQLiteEffectDeleteWithout { + if (typeof columns[0] === "function") { + const orderBy = columns[0]( + new Proxy( + getTableColumnsRuntime(this.config.table), + new SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" }), + ) as any, + ) + + this.config.orderBy = Array.isArray(orderBy) ? orderBy : [orderBy] + return this as any + } + + this.config.orderBy = columns as (SQLiteColumn | SQL | SQL.Aliased)[] + return this as any + } + + limit(limit: number | Placeholder): SQLiteEffectDeleteWithout { + this.config.limit = limit + return this as any + } + + returning(): SQLiteEffectDeleteReturningAll + returning( + fields: TSelectedFields, + ): SQLiteEffectDeleteReturning + returning( + fields: SelectedFieldsFlat = getTableColumnsRuntime(this.table), + ): SQLiteEffectDeleteReturning | SQLiteEffectDeleteReturningAll { + this.config.returning = orderSelectedFields(fields) + return this as any + } + + /** @internal */ + getSQL(): SQL { + return this.effectDialect.buildDeleteQuery(this.config) + } + + toSQL(): Query { + return this.effectDialect.sqlToQuery(this.getSQL()) + } + + /** @internal */ + _prepare(isOneTimeQuery = true): SQLiteEffectDeletePrepare { + return this.effectSession[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"]( + this.effectDialect.sqlToQuery(this.getSQL()), + this.config.returning, + this.config.returning ? "all" : "run", + undefined, + { + type: "delete", + tables: extractUsedTable(this.config.table), + }, + ) as SQLiteEffectDeletePrepare + } + + prepare(): SQLiteEffectDeletePrepare { + return this._prepare(false) + } + + run: ReturnType["run"] = (placeholderValues) => { + return this._prepare().run(placeholderValues) + } + + all: ReturnType["all"] = (placeholderValues) => { + return this._prepare().all(placeholderValues) + } + + get: ReturnType["get"] = (placeholderValues) => { + return this._prepare().get(placeholderValues) + } + + values: ReturnType["values"] = (placeholderValues) => { + return this._prepare().values(placeholderValues) + } + + execute: ReturnType["execute"] = (placeholderValues) => { + return this._prepare().execute(placeholderValues) + } + + $dynamic(): SQLiteEffectDeleteDynamic { + return this as any + } +} + +applyEffectWrapper(SQLiteEffectDeleteBase) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/index.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab7aeb1ab60bf088b78989a9f9e03ef879e06c63 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/index.ts @@ -0,0 +1,10 @@ +/* oxlint-disable */ +export * from "./count" +export * from "./db" +export * from "./delete" +export * from "./insert" +export * from "./query" +export * from "./raw" +export * from "./select" +export * from "./session" +export * from "./update" diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/insert.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/insert.ts new file mode 100644 index 0000000000000000000000000000000000000000..f85dc24475650b9c8a7056042342360598338baa --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/insert.ts @@ -0,0 +1,349 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind, is } from "drizzle-orm/entity" +import type { SelectResultFields } from "drizzle-orm/query-builders/select.types" +import type { RunnableQuery } from "drizzle-orm/runnable-query" +import type { Query, SQLWrapper } from "drizzle-orm/sql/sql" +import { Param, SQL, sql } from "drizzle-orm/sql/sql" +import type { SQLiteDialect } from "drizzle-orm/sqlite-core/dialect" +import type { IndexColumn } from "drizzle-orm/sqlite-core/indexes" +import type { + SQLiteInsertConfig, + SQLiteInsertSelectQueryBuilder, + SQLiteInsertValue, +} from "drizzle-orm/sqlite-core/query-builders/insert" +import type { SelectedFieldsFlat } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { PreparedQueryConfig } from "drizzle-orm/sqlite-core/session" +import { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import { extractUsedTable } from "drizzle-orm/sqlite-core/utils" +import type { Subquery } from "drizzle-orm/subquery" +import { type DrizzleTypeError, haveSameKeys } from "drizzle-orm/utils" +import type { SQLiteColumn } from "drizzle-orm/sqlite-core/columns/common" +import { QueryBuilder } from "drizzle-orm/sqlite-core/query-builders/query-builder" +import type { SQLiteUpdateSetSource } from "drizzle-orm/sqlite-core/query-builders/update" +import { getTableColumnsRuntime, mapUpdateSet, orderSelectedFields } from "../../internal/drizzle-utils" +import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" + +export type SQLiteEffectInsertWithout< + T extends AnySQLiteEffectInsert, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true + ? T + : Omit< + SQLiteEffectInsertBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["returning"], + TDynamic, + T["_"]["excludedMethods"] | K, + T["_"]["effectHKT"] + >, + T["_"]["excludedMethods"] | K + > + +export type SQLiteEffectInsertReturning< + T extends AnySQLiteEffectInsert, + TDynamic extends boolean, + TSelectedFields extends SelectedFieldsFlat, +> = SQLiteEffectInsertWithout< + SQLiteEffectInsertBase< + T["_"]["table"], + T["_"]["runResult"], + SelectResultFields, + TDynamic, + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectInsertReturningAll< + T extends AnySQLiteEffectInsert, + TDynamic extends boolean, +> = SQLiteEffectInsertWithout< + SQLiteEffectInsertBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["table"]["$inferSelect"], + TDynamic, + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectInsertDynamic = SQLiteEffectInsert< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["returning"], + T["_"]["effectHKT"] +> + +export type SQLiteEffectInsertOnConflictDoUpdateConfig = { + target: IndexColumn | IndexColumn[] + /** @deprecated - use either `targetWhere` or `setWhere` */ + where?: SQL + targetWhere?: SQL + setWhere?: SQL + set: SQLiteUpdateSetSource +} + +export type SQLiteEffectInsertExecute = T["_"]["returning"] extends undefined + ? T["_"]["runResult"] + : T["_"]["returning"][] + +export type SQLiteEffectInsertPrepare< + T extends AnySQLiteEffectInsert, + TEffectHKT extends QueryEffectHKTBase = T["_"]["effectHKT"], +> = SQLiteEffectPreparedQuery< + PreparedQueryConfig & { + run: T["_"]["runResult"] + all: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".all() cannot be used without .returning()"> + : T["_"]["returning"][] + get: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".get() cannot be used without .returning()"> + : T["_"]["returning"] + values: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".values() cannot be used without .returning()"> + : any[][] + execute: SQLiteEffectInsertExecute + }, + TEffectHKT +> + +export type SQLiteEffectInsert< + TTable extends SQLiteTable = SQLiteTable, + TRunResult = unknown, + TReturning = any, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> = SQLiteEffectInsertBase + +export type AnySQLiteEffectInsert = SQLiteEffectInsertBase + +export class SQLiteEffectInsertBuilder< + TTable extends SQLiteTable, + TRunResult, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> { + static readonly [entityKind]: string = "SQLiteEffectInsertBuilder" + + constructor( + protected table: TTable, + protected session: SQLiteEffectSession, + protected dialect: SQLiteDialect, + private withList?: Subquery[], + ) {} + + values( + value: SQLiteInsertValue, + ): SQLiteEffectInsertBase + values( + values: SQLiteInsertValue[], + ): SQLiteEffectInsertBase + values( + values: SQLiteInsertValue | SQLiteInsertValue[], + ): SQLiteEffectInsertBase { + values = Array.isArray(values) ? values : [values] + if (values.length === 0) { + throw new Error("values() must be called with at least one value") + } + const mappedValues = values.map((entry) => { + const result: Record = {} + const cols = getTableColumnsRuntime(this.table) + for (const colKey of Object.keys(entry)) { + const colValue = entry[colKey as keyof typeof entry] + result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]) + } + return result + }) + + return new SQLiteEffectInsertBase(this.table, mappedValues, this.session, this.dialect, this.withList) + } + + select( + selectQuery: (qb: QueryBuilder) => SQLiteInsertSelectQueryBuilder, + ): SQLiteEffectInsertBase + select( + selectQuery: (qb: QueryBuilder) => SQL, + ): SQLiteEffectInsertBase + select(selectQuery: SQL): SQLiteEffectInsertBase + select( + selectQuery: SQLiteInsertSelectQueryBuilder, + ): SQLiteEffectInsertBase + select( + selectQuery: + | SQL + | SQLiteInsertSelectQueryBuilder + | ((qb: QueryBuilder) => SQLiteInsertSelectQueryBuilder | SQL), + ): SQLiteEffectInsertBase { + const select = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery + + if (!is(select, SQL) && !haveSameKeys(getTableColumnsRuntime(this.table), select._.selectedFields)) { + throw new Error( + "Insert select error: selected fields are not the same or are in a different order compared to the table definition", + ) + } + + return new SQLiteEffectInsertBase(this.table, select, this.session, this.dialect, this.withList, true) + } +} + +export interface SQLiteEffectInsertBase< + TTable extends SQLiteTable, + TRunResult, + TReturning = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> extends SQLWrapper, + RunnableQuery, + Effect.Effect< + TReturning extends undefined ? TRunResult : TReturning[], + TEffectHKT["error"], + TEffectHKT["context"] + > { + readonly _: { + readonly dialect: "sqlite" + readonly table: TTable + readonly resultType: "async" + readonly runResult: TRunResult + readonly returning: TReturning + readonly dynamic: TDynamic + readonly excludedMethods: _TExcludedMethods + readonly result: TReturning extends undefined ? TRunResult : TReturning[] + readonly effectHKT: TEffectHKT + } +} + +export class SQLiteEffectInsertBase< + TTable extends SQLiteTable, + TRunResult, + TReturning = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + > + implements RunnableQuery, SQLWrapper +{ + static readonly [entityKind]: string = "SQLiteEffectInsert" + + /** @internal */ + config: SQLiteInsertConfig + + constructor( + private table: TTable, + values: SQLiteInsertConfig["values"], + private effectSession: SQLiteEffectSession, + private effectDialect: SQLiteDialect, + withList?: Subquery[], + select?: boolean, + ) { + this.config = { table, values: values as any, withList, select } + } + + returning(): SQLiteEffectInsertReturningAll + returning( + fields: TSelectedFields, + ): SQLiteEffectInsertReturning + returning( + fields: SelectedFieldsFlat = getTableColumnsRuntime(this.config.table), + ): SQLiteEffectInsertWithout { + this.config.returning = orderSelectedFields(fields) + return this as any + } + + onConflictDoNothing(config: { target?: IndexColumn | IndexColumn[]; where?: SQL } = {}): this { + if (!this.config.onConflict) this.config.onConflict = [] + + if (config.target === undefined) { + this.config.onConflict.push(sql` on conflict do nothing`) + return this + } + + const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}` + const whereSql = config.where ? sql` where ${config.where}` : sql`` + this.config.onConflict.push(sql` on conflict ${targetSql} do nothing${whereSql}`) + return this + } + + onConflictDoUpdate(config: SQLiteEffectInsertOnConflictDoUpdateConfig): this { + if (config.where && (config.targetWhere || config.setWhere)) { + throw new Error( + 'You cannot use both "where" and "targetWhere"/"setWhere" at the same time - "where" is deprecated, use "targetWhere" or "setWhere" instead.', + ) + } + + if (!this.config.onConflict) this.config.onConflict = [] + + const whereSql = config.where ? sql` where ${config.where}` : undefined + const targetWhereSql = config.targetWhere ? sql` where ${config.targetWhere}` : undefined + const setWhereSql = config.setWhere ? sql` where ${config.setWhere}` : undefined + const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}` + const setSql = this.effectDialect.buildUpdateSet( + this.config.table, + mapUpdateSet(this.config.table, config.set as SQLiteUpdateSetSource), + ) + this.config.onConflict.push( + sql` on conflict ${targetSql}${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`, + ) + return this + } + + /** @internal */ + getSQL(): SQL { + return this.effectDialect.buildInsertQuery(this.config) + } + + toSQL(): Query { + return this.effectDialect.sqlToQuery(this.getSQL()) + } + + /** @internal */ + _prepare(isOneTimeQuery = true): SQLiteEffectInsertPrepare { + return this.effectSession[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"]( + this.effectDialect.sqlToQuery(this.getSQL()), + this.config.returning, + this.config.returning ? "all" : "run", + undefined, + { + type: "insert", + tables: extractUsedTable(this.config.table), + }, + ) as SQLiteEffectInsertPrepare + } + + prepare(): SQLiteEffectInsertPrepare { + return this._prepare(false) + } + + run: ReturnType["run"] = (placeholderValues) => { + return this._prepare().run(placeholderValues) + } + + all: ReturnType["all"] = (placeholderValues) => { + return this._prepare().all(placeholderValues) + } + + get: ReturnType["get"] = (placeholderValues) => { + return this._prepare().get(placeholderValues) + } + + values: ReturnType["values"] = (placeholderValues) => { + return this._prepare().values(placeholderValues) + } + + execute: ReturnType["execute"] = (placeholderValues) => { + return this._prepare().execute(placeholderValues) + } + + $dynamic(): SQLiteEffectInsertDynamic { + return this as any + } +} + +applyEffectWrapper(SQLiteEffectInsertBase) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/query.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/query.ts new file mode 100644 index 0000000000000000000000000000000000000000..4676039e5504780f4a8456ce7c713df91ea9484e --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/query.ts @@ -0,0 +1,198 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import { + type BuildQueryResult, + type BuildRelationalQueryResult, + type DBQueryConfig, + makeDefaultRqbMapper, + type TableRelationalConfig, + type TablesRelationalConfig, +} from "drizzle-orm/relations" +import type { RunnableQuery } from "drizzle-orm/runnable-query" +import { type Query, type SQL, sql, type SQLWrapper } from "drizzle-orm/sql/sql" +import type { KnownKeysOnly } from "drizzle-orm/utils" +import type { SQLiteDialect } from "drizzle-orm/sqlite-core/dialect" +import type { PreparedQueryConfig } from "drizzle-orm/sqlite-core/session" +import type { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" + +export class SQLiteEffectRelationalQueryBuilder< + TSchema extends TablesRelationalConfig, + TFields extends TableRelationalConfig, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> { + static readonly [entityKind]: string = "SQLiteEffectRelationalQueryBuilderV2" + + constructor( + private schema: TSchema, + private table: SQLiteTable, + private tableConfig: TableRelationalConfig, + private dialect: SQLiteDialect, + private session: SQLiteEffectSession, + private rowMode?: boolean, + private forbidJsonb?: boolean, + ) {} + + findMany>( + config?: KnownKeysOnly>, + ): SQLiteEffectRelationalQuery[], TEffectHKT> { + return new SQLiteEffectRelationalQuery( + this.schema, + this.table, + this.tableConfig, + this.dialect, + this.session, + (config as DBQueryConfig<"many"> | undefined) ?? true, + "many", + this.rowMode, + this.forbidJsonb, + ) + } + + findFirst>( + config?: KnownKeysOnly>, + ): SQLiteEffectRelationalQuery | undefined, TEffectHKT> { + return new SQLiteEffectRelationalQuery( + this.schema, + this.table, + this.tableConfig, + this.dialect, + this.session, + (config as DBQueryConfig<"one"> | undefined) ?? true, + "first", + this.rowMode, + this.forbidJsonb, + ) + } +} + +export interface SQLiteEffectRelationalQuery + extends Effect.Effect, + RunnableQuery, + SQLWrapper {} + +export class SQLiteEffectRelationalQuery + implements RunnableQuery, SQLWrapper +{ + static readonly [entityKind]: string = "SQLiteEffectRelationalQueryV2" + + declare readonly _: { + readonly dialect: "sqlite" + readonly type: "async" + readonly result: TResult + } + + /** @internal */ + mode: "many" | "first" + /** @internal */ + table: SQLiteTable + + constructor( + private schema: TablesRelationalConfig, + table: SQLiteTable, + private tableConfig: TableRelationalConfig, + private dialect: SQLiteDialect, + private session: SQLiteEffectSession, + private config: DBQueryConfig<"many" | "one"> | true, + mode: "many" | "first", + private rowMode?: boolean, + private forbidJsonb?: boolean, + ) { + this.mode = mode + this.table = table + } + + /** @internal */ + getSQL(): SQL { + return this._getQuery().sql + } + + /** @internal */ + _prepare( + isOneTimeQuery = true, + ): SQLiteEffectPreparedQuery< + PreparedQueryConfig & { all: TResult; get: TResult; execute: TResult }, + TEffectHKT, + true + > { + const { query, builtQuery } = this._toSQL() + const mapperConfig = { + isFirst: this.mode === "first", + parseJson: !this.rowMode, + parseJsonIfString: false, + rootJsonMappers: true, + selection: query.selection, + } + + return this.session[isOneTimeQuery ? "prepareOneTimeRelationalQuery" : "prepareRelationalQuery"]( + builtQuery, + undefined, + this.mode === "first" ? "get" : "all", + makeDefaultRqbMapper(mapperConfig), + mapperConfig, + ) as SQLiteEffectPreparedQuery< + PreparedQueryConfig & { all: TResult; get: TResult; execute: TResult }, + TEffectHKT, + true + > + } + + prepare(): SQLiteEffectPreparedQuery< + PreparedQueryConfig & { all: TResult; get: TResult; execute: TResult }, + TEffectHKT, + true + > { + return this._prepare(false) + } + + private _getQuery() { + const jsonb = this.forbidJsonb ? sql`json` : sql`jsonb` + + const query = this.dialect.buildRelationalQuery({ + schema: this.schema, + table: this.table, + tableConfig: this.tableConfig, + queryConfig: this.config, + mode: this.mode, + isNested: this.rowMode, + jsonb, + }) + + if (this.rowMode) { + const jsonColumns = sql.join( + query.selection.map((s) => { + return sql`${sql.raw(this.dialect.escapeString(s.key))}, ${ + s.selection ? sql`${jsonb}(${sql.identifier(s.key)})` : sql.identifier(s.key) + }` + }), + sql`, `, + ) + + query.sql = sql`select json_object(${jsonColumns}) as ${sql.identifier("r")} from (${query.sql}) as ${sql.identifier( + "t", + )}` + } + + return query + } + + private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: Query } { + const query = this._getQuery() + + const builtQuery = this.dialect.sqlToQuery(query.sql) + + return { query, builtQuery } + } + + toSQL(): Query { + return this._toSQL().builtQuery + } + + execute(placeholderValues?: Record) { + return this.mode === "first" ? this._prepare().get(placeholderValues) : this._prepare().all(placeholderValues) + } +} + +applyEffectWrapper(SQLiteEffectRelationalQuery) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/raw.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/raw.ts new file mode 100644 index 0000000000000000000000000000000000000000..26e3a63d83c1c4390b89116db65d6e7995e3da22 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/raw.ts @@ -0,0 +1,49 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind } from "drizzle-orm/entity" +import type { RunnableQuery } from "drizzle-orm/runnable-query" +import type { PreparedQuery } from "drizzle-orm/session" +import type { Query, SQL, SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" + +type SQLiteEffectRawAction = "all" | "get" | "values" | "run" + +export interface SQLiteEffectRaw + extends Effect.Effect, + RunnableQuery, + SQLWrapper {} + +export class SQLiteEffectRaw + implements RunnableQuery, SQLWrapper, PreparedQuery +{ + static readonly [entityKind]: string = "SQLiteEffectRaw" + + declare readonly _: { + readonly dialect: "sqlite" + readonly result: TResult + } + + constructor( + public execute: () => Effect.Effect, + /** @internal */ + public getSQL: () => SQL, + private action: SQLiteEffectRawAction, + private dialect: SQLiteAsyncDialect, + private mapBatchResult: (result: unknown) => unknown, + ) {} + + getQuery(): Query & { method: SQLiteEffectRawAction } { + return { ...this.dialect.sqlToQuery(this.getSQL()), method: this.action } + } + + mapResult(result: unknown, isFromBatch?: boolean) { + return isFromBatch ? this.mapBatchResult(result) : result + } + + _prepare(): PreparedQuery { + return this + } +} + +applyEffectWrapper(SQLiteEffectRaw) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/select.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/select.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cfde54331dc556273476fd09eb9afba894d444a --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/select.ts @@ -0,0 +1,279 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import type { CacheConfig } from "drizzle-orm/cache/core/types" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind, is } from "drizzle-orm/entity" +import type { + BuildSubquerySelection, + GetSelectTableName, + GetSelectTableSelection, + JoinNullability, + SelectMode, + SelectResult, +} from "drizzle-orm/query-builders/select.types" +import { SQL } from "drizzle-orm/sql/sql" +import type { ColumnsSelection, SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteColumn } from "drizzle-orm/sqlite-core/columns" +import type { SQLiteDialect } from "drizzle-orm/sqlite-core/dialect" +import { SQLiteSelectQueryBuilderBase } from "drizzle-orm/sqlite-core/query-builders/select" +import type { + CreateSQLiteSelectFromBuilderMode, + SelectedFields, + SQLiteSelectConfig, + SQLiteSelectHKTBase, +} from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import { SQLiteViewBase } from "drizzle-orm/sqlite-core/view-base" +import { Subquery } from "drizzle-orm/subquery" +import { type Assume, getTableColumns } from "drizzle-orm/utils" +import { getViewSelectedFieldsRuntime, orderSelectedFields } from "../../internal/drizzle-utils" +import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" + +export type SQLiteEffectSelectPrepare< + T extends AnySQLiteEffectSelect, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> = SQLiteEffectPreparedQuery< + { + type: "async" + run: T["_"]["runResult"] + all: T["_"]["result"] + get: T["_"]["result"][number] | undefined + values: any[][] + execute: T["_"]["result"] + }, + TEffectHKT +> + +export class SQLiteEffectSelectBuilder< + TSelection extends SelectedFields | undefined, + TRunResult, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + TBuilderMode extends "db" | "qb" = "db", +> { + static readonly [entityKind]: string = "SQLiteEffectSelectBuilder" + + private fields: TSelection + private session: SQLiteEffectSession | undefined + private dialect: SQLiteDialect + private withList: Subquery[] | undefined + private distinct: boolean | undefined + + constructor(config: { + fields: TSelection + session: SQLiteEffectSession | undefined + dialect: SQLiteDialect + withList?: Subquery[] + distinct?: boolean + }) { + this.fields = config.fields + this.session = config.session + this.dialect = config.dialect + this.withList = config.withList + this.distinct = config.distinct + } + + from( + source: TFrom, + ): TBuilderMode extends "db" + ? SQLiteEffectSelectBase< + GetSelectTableName, + TRunResult, + TSelection extends undefined ? GetSelectTableSelection : TSelection, + TSelection extends undefined ? "single" : "partial", + GetSelectTableName extends string ? Record, "not-null"> : {}, + false, + never, + SelectResult< + TSelection extends undefined ? GetSelectTableSelection : TSelection, + TSelection extends undefined ? "single" : "partial", + GetSelectTableName extends string ? Record, "not-null"> : {} + >[], + BuildSubquerySelection< + TSelection extends undefined ? GetSelectTableSelection : TSelection, + GetSelectTableName extends string ? Record, "not-null"> : {} + >, + TEffectHKT + > + : CreateSQLiteSelectFromBuilderMode< + TBuilderMode, + GetSelectTableName, + "async", + TRunResult, + TSelection extends undefined ? GetSelectTableSelection : TSelection, + TSelection extends undefined ? "single" : "partial" + > { + const isPartialSelect = !!this.fields + + let fields: SelectedFields + if (this.fields) { + fields = this.fields + } else if (is(source, Subquery)) { + fields = Object.fromEntries( + Object.keys(source._.selectedFields).map((key) => [ + key, + source[key as unknown as keyof typeof source] as unknown as SelectedFields[string], + ]), + ) + } else if (is(source, SQLiteViewBase)) { + fields = getViewSelectedFieldsRuntime(source).selectedFields as SelectedFields + } else if (is(source, SQL)) { + fields = {} + } else { + fields = getTableColumns(source) + } + + return new SQLiteEffectSelectBase({ + table: source, + fields, + isPartialSelect, + session: this.session as any, + dialect: this.dialect, + withList: this.withList, + distinct: this.distinct, + }) as any + } +} + +export interface SQLiteEffectSelectHKT + extends SQLiteSelectHKTBase { + _type: SQLiteEffectSelectBase< + this["tableName"], + this["runResult"], + Assume, + this["selectMode"], + Assume>, + this["dynamic"], + this["excludedMethods"], + Assume, + Assume, + TEffectHKT + > +} + +export interface SQLiteEffectSelectBase< + TTableName extends string | undefined, + TRunResult, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode = "single", + TNullabilityMap extends Record = TTableName extends string + ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> extends SQLiteSelectQueryBuilderBase< + SQLiteEffectSelectHKT, + TTableName, + "async", + TRunResult, + TSelection, + TSelectMode, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + Effect.Effect {} + +export class SQLiteEffectSelectBase< + TTableName extends string | undefined, + TRunResult, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode = "single", + TNullabilityMap extends Record = TTableName extends string + ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + > + extends SQLiteSelectQueryBuilderBase< + SQLiteEffectSelectHKT, + TTableName, + "async", + TRunResult, + TSelection, + TSelectMode, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + > + implements SQLWrapper +{ + static override readonly [entityKind]: string = "SQLiteEffectSelect" + + private get effectConfig() { + return (this as unknown as { config: SQLiteSelectConfig }).config + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildSelectQuery(this.effectConfig) + } + + /** @internal */ + _prepare(isOneTimeQuery = true): SQLiteEffectSelectPrepare { + if (!this.session) { + throw new Error("Cannot execute a query on a query builder. Please use a database instance instead.") + } + const session = this.session as unknown as SQLiteEffectSession + const query = session[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"]( + this.dialect.sqlToQuery(this.getSQL()), + orderSelectedFields(this.effectConfig.fields), + "all", + undefined, + { + type: "select", + tables: [...this.usedTables], + }, + this.cacheConfig, + ) + query.joinsNotNullableMap = this.joinsNotNullableMap + return query as ReturnType + } + + $withCache(config?: { config?: CacheConfig; tag?: string; autoInvalidate?: boolean } | false) { + this.cacheConfig = + config === undefined + ? { config: {}, enabled: true, autoInvalidate: true } + : config === false + ? { enabled: false } + : { enabled: true, autoInvalidate: true, ...config } + return this + } + + prepare(): SQLiteEffectSelectPrepare { + return this._prepare(false) + } + + run: ReturnType["run"] = (placeholderValues) => { + return this._prepare().run(placeholderValues) + } + + all: ReturnType["all"] = (placeholderValues) => { + return this._prepare().all(placeholderValues) + } + + get: ReturnType["get"] = (placeholderValues) => { + return this._prepare().get(placeholderValues) + } + + values: ReturnType["values"] = (placeholderValues) => { + return this._prepare().values(placeholderValues) + } + + execute: ReturnType["execute"] = (placeholderValues) => { + return this._prepare().execute(placeholderValues) + } +} + +applyEffectWrapper(SQLiteEffectSelectBase) + +export type AnySQLiteEffectSelect = SQLiteEffectSelectBase diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/session.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/session.ts new file mode 100644 index 0000000000000000000000000000000000000000..15a56f2ca7d9fe73a1fdaaa244d3430fb74cd965 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/session.ts @@ -0,0 +1,490 @@ +/* oxlint-disable */ +import * as Cause from "effect/Cause" +import * as Effect from "effect/Effect" +import type { SqlError } from "effect/unstable/sql/SqlError" +import type { EffectCacheShape } from "drizzle-orm/cache/core/cache-effect" +import { NoopCache, strategyFor } from "drizzle-orm/cache/core/cache" +import type { WithCacheConfig } from "drizzle-orm/cache/core/types" +import { MigratorInitError } from "drizzle-orm/effect-core/errors" +import { EffectDrizzleQueryError, EffectTransactionRollbackError } from "drizzle-orm/effect-core/errors" +import type { EffectLoggerShape } from "drizzle-orm/effect-core/logger" +import type { QueryEffectHKTBase, QueryEffectKind } from "drizzle-orm/effect-core/query-effect" +import { entityKind, is } from "drizzle-orm/entity" +import type { MigrationConfig, MigrationMeta } from "drizzle-orm/migrator" +import { getMigrationsToRun } from "drizzle-orm/migrator.utils" +import type { + AnyRelations, + EmptyRelations, + RelationalQueryMapperConfig, + RelationalRowsMapper, +} from "drizzle-orm/relations" +import { makeJitRqbMapper } from "drizzle-orm/relations" +import type { PreparedQuery } from "drizzle-orm/session" +import { fillPlaceholders, type Query, type SQL, sql } from "drizzle-orm/sql/sql" +import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" +import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { PreparedQueryConfig, SQLiteExecuteMethod, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" +import { upgradeIfNeeded } from "../../up-migrations/effect-sqlite" +import { assertUnreachable, makeJitQueryMapper, type RowsMapper } from "drizzle-orm/utils" +import { mapResultRow } from "../../internal/drizzle-utils" +import { SQLiteEffectDatabase } from "./db" + +type MigrationConfigWithInit = MigrationConfig & { init?: boolean } + +type SQLiteEffectExecuteMethod = SQLiteExecuteMethod | "values" + +export class SQLiteEffectPreparedQuery< + T extends PreparedQueryConfig, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + TIsRqbV2 extends boolean = false, +> implements PreparedQuery +{ + static readonly [entityKind]: string = "SQLiteEffectPreparedQuery" + + /** @internal */ + joinsNotNullableMap?: Record + private jitMapper?: RowsMapper | RelationalRowsMapper + private cacheConfig: WithCacheConfig | undefined + private effectExecuteMethod: SQLiteExecuteMethod + + constructor( + private executor: ( + params: unknown[], + executeMethod: SQLiteEffectExecuteMethod, + ) => Effect.Effect, + protected query: Query, + private logger: EffectLoggerShape, + private cache: EffectCacheShape, + private queryMetadata: + | { + type: "select" | "update" | "delete" | "insert" + tables: string[] + } + | undefined, + cacheConfig: WithCacheConfig | undefined, + private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + private useJitMappers: boolean | undefined, + private customResultMapper?: ( + rows: TIsRqbV2 extends true ? Record[] : unknown[][], + mapColumnValue?: (value: unknown) => unknown, + ) => unknown, + private isRqbV2Query?: TIsRqbV2, + private rqbConfig?: RelationalQueryMapperConfig, + private isInTransaction: Effect.Effect = Effect.succeed(false), + ) { + this.effectExecuteMethod = executeMethod + this.cacheConfig = + cache.strategy() === "all" && cacheConfig === undefined ? { enabled: true, autoInvalidate: true } : cacheConfig + if (!this.cacheConfig?.enabled) { + this.cacheConfig = undefined + } + } + + run(placeholderValues?: Record): QueryEffectKind + run(placeholderValues?: Record): any { + return this.executeWithCache(placeholderValues, "run") + } + + all(placeholderValues?: Record): QueryEffectKind + all(placeholderValues?: Record): any { + if (this.isRqbV2Query) return this.allRqbV2(placeholderValues) + + if (!this.fields && !this.customResultMapper) { + return this.executeWithCache(placeholderValues, "all") + } + + return this.executeWithCache( + placeholderValues, + "values", + (rows) => this.mapAllResult(rows) as T["all"], + ) + } + + get(placeholderValues?: Record): QueryEffectKind + get(placeholderValues?: Record): any { + if (this.isRqbV2Query) return this.getRqbV2(placeholderValues) + + if (!this.fields && !this.customResultMapper) { + return this.executeWithCache(placeholderValues, "get") + } + + return this.executeWithCache( + placeholderValues, + "values", + (rows) => this.mapGetResult(rows) as T["get"], + ) + } + + values(placeholderValues?: Record): QueryEffectKind + values(placeholderValues?: Record): any { + return this.executeWithCache(placeholderValues, "values") + } + + execute(placeholderValues?: Record): QueryEffectKind + execute(placeholderValues?: Record): any { + return this[this.effectExecuteMethod](placeholderValues) as QueryEffectKind + } + + mapRunResult(result: unknown, _isFromBatch?: boolean): unknown { + return result + } + + mapAllResult(rows: unknown, isFromBatch?: boolean): unknown { + if (isFromBatch) { + rows = Array.isArray(rows) ? rows : [] + } + + if (!this.fields && !this.customResultMapper) { + return rows + } + + if (this.isRqbV2Query) { + return this.useJitMappers + ? (this.jitMapper = + (this.jitMapper as RelationalRowsMapper) ?? makeJitRqbMapper(this.rqbConfig!))( + rows as Record[], + ) + : (this.customResultMapper as (rows: Record[]) => unknown)(rows as Record[]) + } + + if (this.customResultMapper) { + return (this.customResultMapper as (rows: unknown[][]) => unknown)(rows as unknown[][]) as T["all"] + } + + return this.useJitMappers + ? (this.jitMapper = + (this.jitMapper as RowsMapper) ?? + makeJitQueryMapper(this.fields!, this.joinsNotNullableMap))(rows as unknown[][]) + : (rows as unknown[][]).map((row) => mapResultRow(this.fields!, row, this.joinsNotNullableMap)) + } + + mapGetResult(rows: unknown, isFromBatch?: boolean): unknown { + if (isFromBatch) { + rows = Array.isArray(rows) ? rows : [] + } + + if (!this.fields && !this.customResultMapper) { + return Array.isArray(rows) ? rows[0] : rows + } + + const row = Array.isArray(rows) ? rows[0] : rows + if (!row) return undefined + + if (this.isRqbV2Query) { + return this.useJitMappers + ? (this.jitMapper = + (this.jitMapper as RelationalRowsMapper) ?? makeJitRqbMapper(this.rqbConfig!))([ + row as Record, + ]) + : (this.customResultMapper as (rows: Record[]) => unknown)([row as Record]) + } + + if (this.customResultMapper) { + return (this.customResultMapper as (rows: unknown[][]) => unknown)([row as unknown[]]) as T["get"] + } + + return this.useJitMappers + ? (this.jitMapper = + (this.jitMapper as RowsMapper) ?? + makeJitQueryMapper(this.fields!, this.joinsNotNullableMap))([row as unknown[]])[0] + : mapResultRow(this.fields!, row as unknown[], this.joinsNotNullableMap) + } + + private allRqbV2(placeholderValues?: Record) { + return this.executeWithCache( + placeholderValues, + "all", + (rows) => this.mapAllResult(rows) as T["all"], + ) + } + + private getRqbV2(placeholderValues?: Record) { + return this.executeWithCache(placeholderValues, "get", (row) => + row === undefined ? undefined : (this.mapGetResult(row) as T["get"]), + ) + } + + private executeWithCache( + placeholderValues: Record | undefined, + executeMethod: SQLiteEffectExecuteMethod, + mapResult?: (result: A) => B, + ) { + return Effect.gen({ self: this }, function* () { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) + + yield* this.logger.logQuery(this.query.sql, params) + + return yield* this.queryWithCache( + this.query.sql, + params, + Effect.suspend(() => this.executor(params, executeMethod) as Effect.Effect), + mapResult, + ) + }) + } + + private mapCachedResult(result: A, mapResult: ((result: A) => B) | undefined) { + if (!mapResult) return Effect.succeed(result as unknown as B) + return Effect.try({ + try: () => mapResult(result), + catch: (cause) => cause, + }) + } + + private queryWithCache( + queryString: string, + params: unknown[], + query: Effect.Effect, + mapResult?: (result: A) => B, + ) { + return Effect.gen({ self: this }, function* () { + if (this.queryMetadata?.type === "select" && this.cacheConfig?.enabled && (yield* this.isInTransaction)) { + return yield* this.mapCachedResult(yield* query, mapResult) + } + + const cacheStrat: Awaited> = !is(this.cache.cache, NoopCache) + ? yield* Effect.tryPromise(() => strategyFor(queryString, params, this.queryMetadata, this.cacheConfig)) + : { type: "skip" as const } + + if (cacheStrat.type === "skip") { + return yield* this.mapCachedResult(yield* query, mapResult) + } + + if (cacheStrat.type === "invalidate") { + const result = yield* query + yield* this.cache.onMutate({ tables: cacheStrat.tables }) + return yield* this.mapCachedResult(result, mapResult) + } + + if (cacheStrat.type === "try") { + if (yield* this.isInTransaction) { + return yield* this.mapCachedResult(yield* query, mapResult) + } + + const { tables, key, isTag, autoInvalidate, config } = cacheStrat + const fromCache: any[] | undefined = yield* this.cache.get(key, tables, isTag, autoInvalidate) + + if (typeof fromCache !== "undefined") { + return yield* this.mapCachedResult(fromCache as unknown as A, mapResult) + } + + const result = yield* query + + yield* this.cache.put(key, result, autoInvalidate ? tables : [], isTag, config) + + return yield* this.mapCachedResult(result, mapResult) + } + + assertUnreachable(cacheStrat) + }).pipe( + Effect.catch((e) => { + return Effect.fail(new EffectDrizzleQueryError({ query: queryString, params, cause: Cause.fail(e) })) + }), + ) + } + + getQuery(): Query { + return this.query + } + + mapResult(response: unknown, isFromBatch?: boolean) { + switch (this.effectExecuteMethod) { + case "run": { + return this.mapRunResult(response, isFromBatch) + } + case "all": { + return this.mapAllResult(response, isFromBatch) + } + case "get": { + return this.mapGetResult(response, isFromBatch) + } + } + } +} + +export abstract class SQLiteEffectSession< + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + TRunResult = unknown, + TRelations extends AnyRelations = EmptyRelations, +> { + static readonly [entityKind]: string = "SQLiteEffectSession" + + constructor(readonly dialect: SQLiteAsyncDialect) {} + + abstract prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, + queryMetadata?: { + type: "select" | "update" | "delete" | "insert" + tables: string[] + }, + cacheConfig?: WithCacheConfig, + ): SQLiteEffectPreparedQuery + + prepareOneTimeQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, + queryMetadata?: { + type: "select" | "update" | "delete" | "insert" + tables: string[] + }, + cacheConfig?: WithCacheConfig, + ): SQLiteEffectPreparedQuery { + return this.prepareQuery(query, fields, executeMethod, customResultMapper, queryMetadata, cacheConfig) + } + + abstract prepareRelationalQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper: (rows: Record[], mapColumnValue?: (value: unknown) => unknown) => unknown, + config: RelationalQueryMapperConfig, + ): SQLiteEffectPreparedQuery + + prepareOneTimeRelationalQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper: (rows: Record[], mapColumnValue?: (value: unknown) => unknown) => unknown, + config: RelationalQueryMapperConfig, + ): SQLiteEffectPreparedQuery { + return this.prepareRelationalQuery(query, fields, executeMethod, customResultMapper, config) + } + + run(query: SQL): QueryEffectKind + run(query: SQL): any { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + "run", + ).run() + } + + all(query: SQL): QueryEffectKind + all(query: SQL): any { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + "all", + ).all() + } + + get(query: SQL): QueryEffectKind + get(query: SQL): any { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + "get", + ).get() + } + + values(query: SQL): QueryEffectKind + values(query: SQL): any { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + "all", + ).values() + } + + count(query: SQL): QueryEffectKind + count(query: SQL): any { + return this.values<[number]>(query).pipe(Effect.map((result) => result[0]?.[0] ?? 0)) + } + + abstract transaction( + transaction: (tx: SQLiteEffectTransaction) => Effect.Effect, + config?: SQLiteTransactionConfig, + ): Effect.Effect +} + +export abstract class SQLiteEffectTransaction< + TEffectHKT extends QueryEffectHKTBase, + TRunResult, + TRelations extends AnyRelations = EmptyRelations, +> extends SQLiteEffectDatabase { + static override readonly [entityKind]: string = "SQLiteEffectTransaction" + + constructor( + dialect: SQLiteAsyncDialect, + session: SQLiteEffectSession, + protected relations: TRelations, + ) { + super(dialect, session, relations) + } + + rollback() { + return new EffectTransactionRollbackError() + } +} + +export const migrate = Effect.fn("migrate")(function* ( + migrations: MigrationMeta[], + session: SQLiteEffectSession, + config: string | MigrationConfigWithInit, +) { + const migrationsTable = + typeof config === "string" ? "__drizzle_migrations" : (config.migrationsTable ?? "__drizzle_migrations") + + const { newDb } = yield* upgradeIfNeeded(migrationsTable, session, migrations) + + if (newDb) { + yield* session.run(sql` + CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} ( + id INTEGER PRIMARY KEY, + hash text NOT NULL, + created_at numeric, + name text, + applied_at TEXT + ) + `) + } + + const dbMigrations = yield* session.all<{ id: number; hash: string; created_at: string; name: string | null }>( + sql`SELECT id, hash, created_at, name FROM ${sql.identifier(migrationsTable)}`, + ) + + if (typeof config === "object" && config.init) { + if (dbMigrations.length) { + return yield* new MigratorInitError({ exitCode: "databaseMigrations" }) + } + + if (migrations.length > 1) { + return yield* new MigratorInitError({ exitCode: "localMigrations" }) + } + + const [migration] = migrations + if (!migration) return + + yield* session.run( + sql`insert into ${sql.identifier( + migrationsTable, + )} ("hash", "created_at", "name", "applied_at") values(${migration.hash}, ${migration.folderMillis}, ${migration.name}, ${new Date().toISOString()})`, + ) + + return + } + + const migrationsToRun = getMigrationsToRun({ localMigrations: migrations, dbMigrations }) + if (migrationsToRun.length === 0) return + + yield* session.transaction((tx) => + Effect.gen(function* () { + for (const migration of migrationsToRun) { + for (const stmt of migration.sql) { + yield* tx.run(sql.raw(stmt)) + } + yield* tx.run( + sql`insert into ${sql.identifier( + migrationsTable, + )} ("hash", "created_at", "name", "applied_at") values(${migration.hash}, ${migration.folderMillis}, ${migration.name}, ${new Date().toISOString()})`, + ) + } + }), + ) +}) diff --git a/packages/effect-drizzle-sqlite/src/sqlite-core/effect/update.ts b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/update.ts new file mode 100644 index 0000000000000000000000000000000000000000..369e08e878ef8ebd25f3ca35ed88c9df7c8d2a08 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/sqlite-core/effect/update.ts @@ -0,0 +1,402 @@ +/* oxlint-disable */ +import type * as Effect from "effect/Effect" +import { applyEffectWrapper, type QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import { entityKind, is } from "drizzle-orm/entity" +import type { SelectResultFields } from "drizzle-orm/query-builders/select.types" +import type { RunnableQuery } from "drizzle-orm/runnable-query" +import { SelectionProxyHandler } from "drizzle-orm/selection-proxy" +import type { Placeholder, Query, SQL, SQLWrapper } from "drizzle-orm/sql/sql" +import type { SQLiteDialect } from "drizzle-orm/sqlite-core/dialect" +import type { SelectedFields, SQLiteSelectJoinConfig } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { SQLiteUpdateConfig, SQLiteUpdateSetSource } from "drizzle-orm/sqlite-core/query-builders/update" +import type { PreparedQueryConfig } from "drizzle-orm/sqlite-core/session" +import { SQLiteTable } from "drizzle-orm/sqlite-core/table" +import { extractUsedTable } from "drizzle-orm/sqlite-core/utils" +import { SQLiteViewBase } from "drizzle-orm/sqlite-core/view-base" +import { Subquery } from "drizzle-orm/subquery" +import { type DrizzleTypeError, type UpdateSet, type ValueOrArray } from "drizzle-orm/utils" +import type { SQLiteColumn } from "drizzle-orm/sqlite-core/columns/common" +import { + getTableColumnsRuntime, + getTableLikeName, + getViewSelectedFieldsRuntime, + mapUpdateSet, + orderSelectedFields, +} from "../../internal/drizzle-utils" +import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" + +export type SQLiteEffectUpdateWithout< + T extends AnySQLiteEffectUpdate, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true + ? T + : Omit< + SQLiteEffectUpdateBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["from"], + T["_"]["returning"], + TDynamic, + T["_"]["excludedMethods"] | K, + T["_"]["effectHKT"] + >, + T["_"]["excludedMethods"] | K + > + +export type SQLiteEffectUpdateWithJoins< + T extends AnySQLiteEffectUpdate, + TDynamic extends boolean, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL, +> = TDynamic extends true + ? T + : Omit< + SQLiteEffectUpdateBase< + T["_"]["table"], + T["_"]["runResult"], + TFrom, + T["_"]["returning"], + TDynamic, + Exclude, + T["_"]["effectHKT"] + >, + Exclude + > + +export type SQLiteEffectUpdateReturningAll< + T extends AnySQLiteEffectUpdate, + TDynamic extends boolean, +> = SQLiteEffectUpdateWithout< + SQLiteEffectUpdateBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["from"], + T["_"]["table"]["$inferSelect"], + TDynamic, + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectUpdateReturning< + T extends AnySQLiteEffectUpdate, + TDynamic extends boolean, + TSelectedFields extends SelectedFields, +> = SQLiteEffectUpdateWithout< + SQLiteEffectUpdateBase< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["from"], + SelectResultFields, + TDynamic, + T["_"]["excludedMethods"], + T["_"]["effectHKT"] + >, + TDynamic, + "returning" +> + +export type SQLiteEffectUpdateExecute = T["_"]["returning"] extends undefined + ? T["_"]["runResult"] + : T["_"]["returning"][] + +export type SQLiteEffectUpdatePrepare< + T extends AnySQLiteEffectUpdate, + TEffectHKT extends QueryEffectHKTBase = T["_"]["effectHKT"], +> = SQLiteEffectPreparedQuery< + PreparedQueryConfig & { + run: T["_"]["runResult"] + all: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".all() cannot be used without .returning()"> + : T["_"]["returning"][] + get: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".get() cannot be used without .returning()"> + : T["_"]["returning"] + values: T["_"]["returning"] extends undefined + ? DrizzleTypeError<".values() cannot be used without .returning()"> + : any[][] + execute: SQLiteEffectUpdateExecute + }, + TEffectHKT +> + +export type SQLiteEffectUpdateDynamic = SQLiteEffectUpdate< + T["_"]["table"], + T["_"]["runResult"], + T["_"]["from"], + T["_"]["returning"], + T["_"]["effectHKT"] +> + +export type SQLiteEffectUpdate< + TTable extends SQLiteTable = SQLiteTable, + TRunResult = unknown, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, + TReturning extends Record | undefined = Record | undefined, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> = SQLiteEffectUpdateBase + +export type AnySQLiteEffectUpdate = SQLiteEffectUpdateBase + +export type SQLiteEffectUpdateJoinFn = < + TJoinedTable extends SQLiteTable | Subquery | SQLiteViewBase | SQL, +>( + table: TJoinedTable, + on: + | (( + updateTable: T["_"]["table"]["_"]["columns"], + from: T["_"]["from"] extends SQLiteTable + ? T["_"]["from"]["_"]["columns"] + : T["_"]["from"] extends Subquery | SQLiteViewBase + ? T["_"]["from"]["_"]["selectedFields"] + : never, + ) => SQL | undefined) + | SQL + | undefined, +) => T + +export class SQLiteEffectUpdateBuilder< + TTable extends SQLiteTable, + TRunResult, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> { + static readonly [entityKind]: string = "SQLiteEffectUpdateBuilder" + + declare readonly _: { + readonly table: TTable + } + + constructor( + protected table: TTable, + protected session: SQLiteEffectSession, + protected dialect: SQLiteDialect, + private withList?: Subquery[], + ) {} + + set( + values: SQLiteUpdateSetSource, + ): SQLiteEffectUpdateWithout< + SQLiteEffectUpdateBase, + false, + "leftJoin" | "rightJoin" | "innerJoin" | "fullJoin" + > { + return new SQLiteEffectUpdateBase( + this.table, + mapUpdateSet(this.table, values), + this.session, + this.dialect, + this.withList, + ) as any + } +} + +export interface SQLiteEffectUpdateBase< + TTable extends SQLiteTable = SQLiteTable, + TRunResult = unknown, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, + TReturning = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, +> extends SQLWrapper, + RunnableQuery, + Effect.Effect< + TReturning extends undefined ? TRunResult : TReturning[], + TEffectHKT["error"], + TEffectHKT["context"] + > { + readonly _: { + readonly dialect: "sqlite" + readonly table: TTable + readonly resultType: "async" + readonly runResult: TRunResult + readonly from: TFrom + readonly returning: TReturning + readonly dynamic: TDynamic + readonly excludedMethods: _TExcludedMethods + readonly result: TReturning extends undefined ? TRunResult : TReturning[] + readonly effectHKT: TEffectHKT + } +} + +export class SQLiteEffectUpdateBase< + TTable extends SQLiteTable = SQLiteTable, + TRunResult = unknown, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, + TReturning = undefined, + TDynamic extends boolean = false, + _TExcludedMethods extends string = never, + TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, + > + implements RunnableQuery, SQLWrapper +{ + static readonly [entityKind]: string = "SQLiteEffectUpdate" + + /** @internal */ + config: SQLiteUpdateConfig + + constructor( + table: TTable, + set: UpdateSet, + private effectSession: SQLiteEffectSession, + private effectDialect: SQLiteDialect, + withList?: Subquery[], + ) { + this.config = { set, table, withList, joins: [] } + } + + from( + source: TFrom, + ): SQLiteEffectUpdateWithJoins { + this.config.from = source + return this as any + } + + private createJoin( + joinType: TJoinType, + ): SQLiteEffectUpdateJoinFn { + return (( + table: SQLiteTable | Subquery | SQLiteViewBase | SQL, + on: ((updateTable: TTable, from: TFrom) => SQL | undefined) | SQL | undefined, + ) => { + const tableName = getTableLikeName(table) + + if (typeof tableName === "string" && this.config.joins.some((join) => join.alias === tableName)) { + throw new Error(`Alias "${tableName}" is already used in this query`) + } + + if (typeof on === "function") { + const from = this.config.from + ? is(table, SQLiteTable) + ? getTableColumnsRuntime(table) + : is(table, Subquery) + ? table._.selectedFields + : is(table, SQLiteViewBase) + ? getViewSelectedFieldsRuntime(table).selectedFields + : undefined + : undefined + on = on( + new Proxy( + this.config.table._.columns, + new SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" }), + ) as any, + from && + (new Proxy(from, new SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" })) as any), + ) + } + + this.config.joins.push({ on, table, joinType, alias: tableName }) + + return this as any + }) as any + } + + leftJoin = this.createJoin("left") + + rightJoin = this.createJoin("right") + + innerJoin = this.createJoin("inner") + + fullJoin = this.createJoin("full") + + where(where: SQL | undefined): SQLiteEffectUpdateWithout { + this.config.where = where + return this as any + } + + orderBy( + builder: (updateTable: TTable) => ValueOrArray, + ): SQLiteEffectUpdateWithout + orderBy(...columns: (SQLiteColumn | SQL | SQL.Aliased)[]): SQLiteEffectUpdateWithout + orderBy( + ...columns: + | [(updateTable: TTable) => ValueOrArray] + | (SQLiteColumn | SQL | SQL.Aliased)[] + ): SQLiteEffectUpdateWithout { + if (typeof columns[0] === "function") { + const orderBy = columns[0]( + new Proxy( + getTableColumnsRuntime(this.config.table), + new SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" }), + ) as any, + ) + + this.config.orderBy = Array.isArray(orderBy) ? orderBy : [orderBy] + return this as any + } + + this.config.orderBy = columns as (SQLiteColumn | SQL | SQL.Aliased)[] + return this as any + } + + limit(limit: number | Placeholder): SQLiteEffectUpdateWithout { + this.config.limit = limit + return this as any + } + + returning(): SQLiteEffectUpdateReturningAll + returning( + fields: TSelectedFields, + ): SQLiteEffectUpdateReturning + returning( + fields: SelectedFields = getTableColumnsRuntime(this.config.table), + ): SQLiteEffectUpdateWithout { + this.config.returning = orderSelectedFields(fields) + return this as any + } + + /** @internal */ + getSQL(): SQL { + return this.effectDialect.buildUpdateQuery(this.config) + } + + toSQL(): Query { + return this.effectDialect.sqlToQuery(this.getSQL()) + } + + /** @internal */ + _prepare(isOneTimeQuery = true): SQLiteEffectUpdatePrepare { + return this.effectSession[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"]( + this.effectDialect.sqlToQuery(this.getSQL()), + this.config.returning, + this.config.returning ? "all" : "run", + undefined, + { + type: "update", + tables: extractUsedTable(this.config.table), + }, + ) as SQLiteEffectUpdatePrepare + } + + prepare(): SQLiteEffectUpdatePrepare { + return this._prepare(false) + } + + run: ReturnType["run"] = (placeholderValues) => { + return this._prepare().run(placeholderValues) + } + + all: ReturnType["all"] = (placeholderValues) => { + return this._prepare().all(placeholderValues) + } + + get: ReturnType["get"] = (placeholderValues) => { + return this._prepare().get(placeholderValues) + } + + values: ReturnType["values"] = (placeholderValues) => { + return this._prepare().values(placeholderValues) + } + + execute: ReturnType["execute"] = (placeholderValues) => { + return this._prepare().execute(placeholderValues) + } + + $dynamic(): SQLiteEffectUpdateDynamic { + return this as any + } +} + +applyEffectWrapper(SQLiteEffectUpdateBase) diff --git a/packages/effect-drizzle-sqlite/src/up-migrations/effect-sqlite.ts b/packages/effect-drizzle-sqlite/src/up-migrations/effect-sqlite.ts new file mode 100644 index 0000000000000000000000000000000000000000..3418752271ed4c1f550977e85d7ca2935625bd08 --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/up-migrations/effect-sqlite.ts @@ -0,0 +1,102 @@ +/* oxlint-disable */ +import * as Effect from "effect/Effect" +import type { SqlError } from "effect/unstable/sql/SqlError" +import { EffectDrizzleError } from "drizzle-orm/effect-core/errors" +import type { QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" +import type { MigrationMeta } from "drizzle-orm/migrator" +import { sql } from "drizzle-orm/sql/sql" +import type { SQLiteEffectSession } from "../sqlite-core/effect/session" +import { + buildSQLiteMigrationBackfillStatements, + prepareSQLiteMigrationBackfill, + type SQLiteMigrationTableRow, +} from "./sqlite" +import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from "./utils" + +const migrationUpgradeError = (cause: unknown) => + new EffectDrizzleError({ + message: + typeof cause === "object" && cause !== null && "message" in cause && typeof cause.message === "string" + ? cause.message + : String(cause), + cause, + }) + +export const upgradeIfNeeded: ( + migrationsTable: string, + session: SQLiteEffectSession, + localMigrations: MigrationMeta[], +) => Effect.Effect = + Effect.fn("upgradeIfNeeded")(function* ( + migrationsTable: string, + session: SQLiteEffectSession, + localMigrations: MigrationMeta[], + ) { + const tableExists = yield* session.all( + sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`, + ) + + if (tableExists.length === 0) { + return { newDb: true } + } + + const rows = yield* session.all<{ column_name: string }>( + sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`, + ) + + const version = GET_VERSION_FOR.sqlite(rows.map((r) => r.column_name)) + + for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) { + const upgradeFn = upgradeFunctions[v] + if (!upgradeFn) { + return yield* new EffectDrizzleError({ + message: `No upgrade path from migration table version ${v} to ${v + 1}`, + cause: { version: v }, + }) + } + yield* upgradeFn(migrationsTable, session, localMigrations) + } + + return { newDb: false } + }) + +const upgradeFunctions: Record< + number, + ( + migrationsTable: string, + session: SQLiteEffectSession, + localMigrations: MigrationMeta[], + ) => Effect.Effect +> = { + 0: upgradeFromV0, +} + +function upgradeFromV0( + migrationsTable: string, + session: SQLiteEffectSession, + localMigrations: MigrationMeta[], +): Effect.Effect { + return Effect.gen(function* () { + const table = sql`${sql.identifier(migrationsTable)}` + + const dbRows = yield* session.all( + sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`, + ) + const statements = yield* Effect.try({ + try: () => + buildSQLiteMigrationBackfillStatements( + migrationsTable, + prepareSQLiteMigrationBackfill(dbRows, localMigrations), + ), + catch: migrationUpgradeError, + }) + + yield* session.transaction((tx) => + Effect.gen(function* () { + for (const statement of statements) { + yield* tx.run(statement) + } + }), + ) + }) +} diff --git a/packages/effect-drizzle-sqlite/src/up-migrations/sqlite.ts b/packages/effect-drizzle-sqlite/src/up-migrations/sqlite.ts new file mode 100644 index 0000000000000000000000000000000000000000..2887c66bed735cd812d885953335d8f99c6dad2a --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/up-migrations/sqlite.ts @@ -0,0 +1,253 @@ +/* oxlint-disable */ +import type { TablesRelationalConfig } from "drizzle-orm/_relations" +import type { MigrationMeta } from "drizzle-orm/migrator" +import type { AnyRelations } from "drizzle-orm/relations" +import { type SQL, sql } from "drizzle-orm/sql/sql" +import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core" +import type { SQLiteSession } from "drizzle-orm/sqlite-core/session" +import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from "./utils" + +/** @internal */ +export type SQLiteMigrationTableRow = { id: number | null; hash: string; created_at: number } + +type AsyncSQLiteDatabaseWithSession = BaseSQLiteDatabase<"async", unknown, Record> & { + session: { + all(query: SQL): Promise + } + transaction(transaction: (tx: { run(query: SQL): Promise }) => Promise): Promise +} + +type SQLiteMigrationBackfillEntry = { + name: string + selector: + | { column: "id"; value: number } + | { column: "created_at"; value: number } + | { column: "hash"; value: string } +} + +function unmatchedMigrationError(unmatched: SQLiteMigrationTableRow[]) { + return new Error( + `While upgrading your database migrations table we found ${unmatched.length} (${unmatched + .map((it) => `[id: ${it.id}, created_at: ${it.created_at}]`) + .join( + ", ", + )}) migrations in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`, + ) +} + +/** @internal */ +export function prepareSQLiteMigrationBackfill( + dbRows: SQLiteMigrationTableRow[], + localMigrations: MigrationMeta[], +): SQLiteMigrationBackfillEntry[] { + const sortedLocalMigrations = [...localMigrations].sort((a, b) => + a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""), + ) + const byMillis = new Map() + const byHash = new Map() + for (const migration of sortedLocalMigrations) { + if (!byMillis.has(migration.folderMillis)) { + byMillis.set(migration.folderMillis, []) + } + byMillis.get(migration.folderMillis)!.push(migration) + byHash.set(migration.hash, migration) + } + + const toApply: SQLiteMigrationBackfillEntry[] = [] + const unmatched: SQLiteMigrationTableRow[] = [] + + for (const dbRow of dbRows) { + const stringified = String(dbRow.created_at) + const millis = Number(stringified.substring(0, stringified.length - 3) + "000") + const candidates = byMillis.get(millis) + + const matchedByMillis = candidates?.length === 1 ? candidates[0] : undefined + const matchedByCandidateHash = + candidates && candidates.length > 1 + ? candidates.find((candidate) => candidate.hash && dbRow.hash && candidate.hash === dbRow.hash) + : undefined + const matchedByHash = matchedByMillis || matchedByCandidateHash ? undefined : byHash.get(dbRow.hash) + const matched = matchedByMillis ?? matchedByCandidateHash ?? matchedByHash + + if (matched) { + toApply.push({ + name: matched.name, + selector: + dbRow.id !== null + ? { column: "id", value: dbRow.id } + : matchedByMillis + ? { column: "created_at", value: dbRow.created_at } + : { column: "hash", value: dbRow.hash }, + }) + continue + } + + unmatched.push(dbRow) + } + + if (unmatched.length > 0) { + throw unmatchedMigrationError(unmatched) + } + + return toApply +} + +/** @internal */ +export function buildSQLiteMigrationBackfillStatements( + migrationsTable: string, + backfillEntries: SQLiteMigrationBackfillEntry[], +) { + const table = sql`${sql.identifier(migrationsTable)}` + const statements: SQL[] = [ + sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier("name")} text`, + sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier("applied_at")} TEXT`, + ] + + for (const backfillEntry of backfillEntries) { + const updateQuery = sql`UPDATE ${table} SET ${sql.identifier("name")} = ${backfillEntry.name}, ${sql.identifier( + "applied_at", + )} = NULL WHERE` + + updateQuery.append(sql` ${sql.identifier(backfillEntry.selector.column)} = ${backfillEntry.selector.value}`) + + statements.push(updateQuery) + } + + return statements +} + +/** + * Detects the current version of the migrations table schema and upgrades it if needed. + * + * Version 0: Original schema (id, hash, created_at) + * Version 1: Extended schema (id, hash, created_at, name, applied_at) + */ +export function upgradeSyncIfNeeded( + migrationsTable: string, + session: SQLiteSession<"sync", unknown, Record, AnyRelations, TablesRelationalConfig>, + localMigrations: MigrationMeta[], +): UpgradeResult { + const tableExists = session.all(sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`) + + if (tableExists.length === 0) { + return { newDb: true } + } + + // Table exists, check table shape + const rows = session.all<{ column_name: string }>( + sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`, + ) + + const version = GET_VERSION_FOR.sqlite(rows.map((r) => r.column_name)) + + for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) { + const upgradeFn = upgradeSyncFunctions[v] + if (!upgradeFn) { + throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`) + } + upgradeFn(migrationsTable, session, localMigrations) + } + + return { newDb: false } +} + +const upgradeSyncFunctions: Record< + number, + ( + migrationsTable: string, + session: SQLiteSession<"sync", unknown, Record, AnyRelations, TablesRelationalConfig>, + localMigrations: MigrationMeta[], + ) => void +> = { + /** + * Upgrade from version 0 to version 1: + * 1. Read all existing DB migrations + * 2. Sort localMigrations ASC by millis and if the same - sort by name + * 3. Match each DB row to a local migration + * If multiple migrations share the same second, use hash matching as a tiebreaker + * Not implemented for now -> If hash matching fails, fall back to serial id ordering + * 5. Create extra column and backfill names for matched migrations + */ + 0: (migrationsTable, session, localMigrations) => { + const table = sql`${sql.identifier(migrationsTable)}` + const dbRows = session.all(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`) + const statements = buildSQLiteMigrationBackfillStatements( + migrationsTable, + prepareSQLiteMigrationBackfill(dbRows, localMigrations), + ) + + session.transaction((tx) => { + for (const statement of statements) { + tx.run(statement) + } + }) + }, +} + +/** + * Detects the current version of the migrations table schema and upgrades it if needed. + * + * Version 0: Original schema (id, hash, created_at) + * Version 1: Extended schema (id, hash, created_at, name, applied_at) + */ +export async function upgradeAsyncIfNeeded( + migrationsTable: string, + db: AsyncSQLiteDatabaseWithSession, + localMigrations: MigrationMeta[], +): Promise { + // Check if the table exists at all + const tableExists = await db.session.all( + sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`, + ) + + if (tableExists.length === 0) { + return { newDb: true } + } + + const rows = await db.session.all<{ column_name: string }>( + sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`, + ) + + const version = GET_VERSION_FOR.sqlite(rows.map((r) => r.column_name)) + + for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) { + const upgradeFn = upgradeAsyncFunctions[v] + if (!upgradeFn) { + throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`) + } + await upgradeFn(migrationsTable, db, localMigrations) + } + + return { newDb: false } +} + +const upgradeAsyncFunctions: Record< + number, + (migrationsTable: string, db: AsyncSQLiteDatabaseWithSession, localMigrations: MigrationMeta[]) => Promise +> = { + /** + * Upgrade from version 0 to version 1: + * 1. Read all existing DB migrations + * 2. Sort localMigrations ASC by millis and if the same - sort by name + * 3. Match each DB row to a local migration + * If multiple migrations share the same second, use hash matching as a tiebreaker + * Not implemented for now -> If hash matching fails, fall back to serial id ordering + * 5. Create extra column and backfill names for matched migrations + */ + 0: async (migrationsTable, db, localMigrations) => { + const table = sql`${sql.identifier(migrationsTable)}` + const dbRows = await db.session.all( + sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`, + ) + const statements = buildSQLiteMigrationBackfillStatements( + migrationsTable, + prepareSQLiteMigrationBackfill(dbRows, localMigrations), + ) + + await db.transaction(async (tx) => { + for (const statement of statements) { + await tx.run(statement) + } + }) + }, +} diff --git a/packages/effect-drizzle-sqlite/src/up-migrations/utils.ts b/packages/effect-drizzle-sqlite/src/up-migrations/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc71213ac401511e3bd9740be46776d8f24248bb --- /dev/null +++ b/packages/effect-drizzle-sqlite/src/up-migrations/utils.ts @@ -0,0 +1,45 @@ +/* oxlint-disable */ +export interface UpgradeResult { + newDb: boolean +} + +export const MIGRATIONS_TABLE_VERSIONS = { + sqlite: 1, + pg: 1, + effect: 1, + mysql: 1, + mssql: 1, + cockroach: 1, + singlestore: 1, +} as const + +export const GET_VERSION_FOR = { + mysql: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + pg: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + effect: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + mssql: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + cockroach: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + singlestore: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, + sqlite: (columns: string[]): number => { + if (columns.includes("name")) return 1 + return 0 + }, +} as const diff --git a/packages/effect-drizzle-sqlite/test/sqlite.test.ts b/packages/effect-drizzle-sqlite/test/sqlite.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5303ee069ac8c48c523abdcb98f4f9e9deed61df --- /dev/null +++ b/packages/effect-drizzle-sqlite/test/sqlite.test.ts @@ -0,0 +1,172 @@ +import { mkdir, mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { Database } from "bun:sqlite" +import { expect, test } from "bun:test" +import { SqliteClient } from "@effect/sql-sqlite-bun" +import { eq, sql } from "drizzle-orm" +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" +import { Effect } from "effect" +import type { SqlClient as SqlClientService } from "effect/unstable/sql/SqlClient" +import { isSqlError } from "effect/unstable/sql/SqlError" +import { EffectDrizzleSqlite } from "../src" + +const users = sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), +}) + +const run = (effect: Effect.Effect) => + Effect.runPromise( + effect.pipe(Effect.provide(SqliteClient.layer({ filename: ":memory:", disableWAL: true })), Effect.scoped), + ) + +const makeDb = Effect.gen(function* () { + const db = yield* EffectDrizzleSqlite.makeWithDefaults() + yield* db.run(sql`create table users (id integer primary key autoincrement, name text not null)`) + return db +}) + +const createMigrationsFolder = async () => { + const migrationsFolder = await mkdtemp(join(tmpdir(), "effect-drizzle-sqlite-")) + await mkdir(join(migrationsFolder, "20240101000000_create_migrated_users"), { recursive: true }) + await Bun.write( + join(migrationsFolder, "20240101000000_create_migrated_users", "migration.sql"), + "create table migrated_users (id integer primary key autoincrement, name text not null);", + ) + return migrationsFolder +} + +test("selects rows through Effect-yieldable query builders", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + yield* db.insert(users).values({ name: "Ada" }) + + expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Ada" }]) + expect(yield* db.select({ id: users.id }).from(users).where(eq(users.name, "Ada")).get()).toEqual({ id: 1 }) + }), + ) +}) + +test("commits successful transactions", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + + yield* db.transaction((tx) => tx.insert(users).values({ name: "Grace" }), { behavior: "immediate" }) + + expect(yield* db.select().from(users)).toEqual([{ id: 1, name: "Grace" }]) + }), + ) +}) + +test("rolls back failed transactions", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + + yield* db + .transaction((tx) => + tx + .insert(users) + .values({ name: "Linus" }) + .pipe(Effect.andThen(Effect.fail("boom"))), + ) + .pipe(Effect.ignore) + + expect(yield* db.select().from(users)).toEqual([]) + }), + ) +}) + +test("rolls back explicit transaction rollback", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + + yield* db + .transaction((tx) => + tx + .insert(users) + .values({ name: "Barbara" }) + .pipe(Effect.andThen(Effect.fail(tx.rollback()))), + ) + .pipe(Effect.ignore) + + expect(yield* db.select().from(users)).toEqual([]) + }), + ) +}) + +test("preserves failed transaction begin errors", async () => { + const dir = await mkdtemp(join(tmpdir(), "effect-drizzle-sqlite-")) + const filename = join(dir, "locked.db") + const holder = new Database(filename) + + try { + holder.run("create table users (id integer primary key autoincrement, name text not null)") + holder.run("pragma busy_timeout = 0") + holder.run("begin immediate") + + await Effect.runPromise( + Effect.gen(function* () { + const db = yield* EffectDrizzleSqlite.makeWithDefaults() + yield* db.run(sql`pragma busy_timeout = 0`) + + const error = yield* db + .transaction((tx) => tx.insert(users).values({ name: "Blocked" }), { behavior: "immediate" }) + .pipe(Effect.flip) + + if (!isSqlError(error)) throw new Error("Expected SqlError") + expect(error.reason._tag).toBe("LockTimeoutError") + expect(error.reason.cause instanceof Error ? error.reason.cause.message : "").toContain("database is locked") + }).pipe(Effect.provide(SqliteClient.layer({ filename, disableWAL: true })), Effect.scoped), + ) + } finally { + if (holder.inTransaction) holder.run("rollback") + holder.close() + await rm(dir, { recursive: true, force: true }) + } +}) + +test("supports returning and rejects empty update sets", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + + const inserted = yield* db.insert(users).values({ name: "Ada" }).returning({ id: users.id, name: users.name }) + expect(inserted).toEqual([{ id: 1, name: "Ada" }]) + + const updated = yield* db.update(users).set({ name: "Grace" }).where(eq(users.id, 1)).returning() + expect(updated).toEqual([{ id: 1, name: "Grace" }]) + + const deleted = yield* db.delete(users).where(eq(users.id, 1)).returning({ id: users.id }) + expect(deleted).toEqual([{ id: 1 }]) + + expect(() => db.update(users).set({ name: undefined })).toThrow("No values to set") + }), + ) +}) + +test("runs migrations once and records migration metadata", async () => { + const migrationsFolder = await createMigrationsFolder() + try { + await run( + Effect.gen(function* () { + const db = yield* EffectDrizzleSqlite.makeWithDefaults() + + yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder }) + yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder }) + yield* db.run(sql`insert into migrated_users (name) values ('Margaret')`) + + expect(yield* db.all<{ name: string }>(sql`select name from migrated_users`)).toEqual([{ name: "Margaret" }]) + expect(yield* db.all<{ name: string | null }>(sql`select name from __drizzle_migrations`)).toEqual([ + { name: "20240101000000_create_migrated_users" }, + ]) + }), + ) + } finally { + await rm(migrationsFolder, { recursive: true, force: true }) + } +}) diff --git a/packages/ui/src/assets/audio/alert-01.aac b/packages/ui/src/assets/audio/alert-01.aac new file mode 100644 index 0000000000000000000000000000000000000000..dc608d8b1cf7ba3e9b7120ae0e6e3cf34799df1e Binary files /dev/null and b/packages/ui/src/assets/audio/alert-01.aac differ diff --git a/packages/ui/src/assets/audio/alert-02.aac b/packages/ui/src/assets/audio/alert-02.aac new file mode 100644 index 0000000000000000000000000000000000000000..008bc4f700972feee14a696126b691efadae1837 Binary files /dev/null and b/packages/ui/src/assets/audio/alert-02.aac differ diff --git a/packages/ui/src/assets/audio/alert-03.aac b/packages/ui/src/assets/audio/alert-03.aac new file mode 100644 index 0000000000000000000000000000000000000000..bd977d4afb3dac2df7bd4b8c6200f67c5b767c06 Binary files /dev/null and b/packages/ui/src/assets/audio/alert-03.aac differ diff --git a/packages/ui/src/assets/audio/alert-04.aac b/packages/ui/src/assets/audio/alert-04.aac new file mode 100644 index 0000000000000000000000000000000000000000..a1d6cdcc71c1e7a69871eae0dd824aed5014b5fe Binary files /dev/null and b/packages/ui/src/assets/audio/alert-04.aac differ diff --git a/packages/ui/src/assets/audio/alert-05.aac b/packages/ui/src/assets/audio/alert-05.aac new file mode 100644 index 0000000000000000000000000000000000000000..185d7372d8f10f28fd24cf61d3be842c424b4d2c Binary files /dev/null and b/packages/ui/src/assets/audio/alert-05.aac differ diff --git a/packages/ui/src/assets/audio/alert-06.aac b/packages/ui/src/assets/audio/alert-06.aac new file mode 100644 index 0000000000000000000000000000000000000000..8da19b7d8d6bfb67cb6b414d8d992fd851039c71 Binary files /dev/null and b/packages/ui/src/assets/audio/alert-06.aac differ diff --git a/packages/ui/src/assets/audio/alert-07.aac b/packages/ui/src/assets/audio/alert-07.aac new file mode 100644 index 0000000000000000000000000000000000000000..6cfd9533e86239654d68fccacf21634168600b52 Binary files /dev/null and b/packages/ui/src/assets/audio/alert-07.aac differ diff --git a/packages/ui/src/assets/audio/alert-08.mp3 b/packages/ui/src/assets/audio/alert-08.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6474f935ca4ca7397e2728d17176de54b2a1b701 Binary files /dev/null and b/packages/ui/src/assets/audio/alert-08.mp3 differ diff --git a/packages/ui/src/assets/audio/alert-09.mp3 b/packages/ui/src/assets/audio/alert-09.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c26fb5bf634f88d7965bf4f5b84cbbd5567ba1fe Binary files /dev/null and b/packages/ui/src/assets/audio/alert-09.mp3 differ diff --git a/packages/ui/src/assets/audio/alert-10.aac b/packages/ui/src/assets/audio/alert-10.aac new file mode 100644 index 0000000000000000000000000000000000000000..9720f0b79aeec2e592d41753170bf2f3bcf7b97d Binary files /dev/null and b/packages/ui/src/assets/audio/alert-10.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-01.aac b/packages/ui/src/assets/audio/bip-bop-01.aac new file mode 100644 index 0000000000000000000000000000000000000000..d5ece46f1d1ccdc9b4c3d4b75c47584554c534db Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-01.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-01.mp3 b/packages/ui/src/assets/audio/bip-bop-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f518059c54c1c9ca4f14ae0d4149fedc61d7d015 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-01.mp3 differ diff --git a/packages/ui/src/assets/audio/bip-bop-02.aac b/packages/ui/src/assets/audio/bip-bop-02.aac new file mode 100644 index 0000000000000000000000000000000000000000..2bf272c1ac777baeb2b0130501c47ea4050b3f30 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-02.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-02.mp3 b/packages/ui/src/assets/audio/bip-bop-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b25f80ada6f8f354be124386cd83e9562cc07871 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-02.mp3 differ diff --git a/packages/ui/src/assets/audio/bip-bop-03.aac b/packages/ui/src/assets/audio/bip-bop-03.aac new file mode 100644 index 0000000000000000000000000000000000000000..5011f12d1034394a1a2e4c47e2e01656defcb001 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-03.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-03.mp3 b/packages/ui/src/assets/audio/bip-bop-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..adc68c91dc29e0023efbc142aa2f06dc33c6c6ff Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-03.mp3 differ diff --git a/packages/ui/src/assets/audio/bip-bop-04.aac b/packages/ui/src/assets/audio/bip-bop-04.aac new file mode 100644 index 0000000000000000000000000000000000000000..af6fd3c0a3f290cdb0ffc296dc0b08968fd6068a Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-04.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-05.aac b/packages/ui/src/assets/audio/bip-bop-05.aac new file mode 100644 index 0000000000000000000000000000000000000000..75ce28a8c418819ed61b0f3bb57f10d794c10023 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-05.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-06.aac b/packages/ui/src/assets/audio/bip-bop-06.aac new file mode 100644 index 0000000000000000000000000000000000000000..1aa68df769f223696a761b00aab786e1141a116d Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-06.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-07.aac b/packages/ui/src/assets/audio/bip-bop-07.aac new file mode 100644 index 0000000000000000000000000000000000000000..47d0e602a1a1d51308ef3fa093dddc42662bf2f1 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-07.aac differ diff --git a/packages/ui/src/assets/audio/bip-bop-08.mp3 b/packages/ui/src/assets/audio/bip-bop-08.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..24af5b0a23800318973d625867aa44933dff1736 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-08.mp3 differ diff --git a/packages/ui/src/assets/audio/bip-bop-09.mp3 b/packages/ui/src/assets/audio/bip-bop-09.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8eca42c0ebdb6655a7a55452e5018f4208eb0fdf Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-09.mp3 differ diff --git a/packages/ui/src/assets/audio/bip-bop-10.aac b/packages/ui/src/assets/audio/bip-bop-10.aac new file mode 100644 index 0000000000000000000000000000000000000000..50076b3c91d40629b79e6f759d389361505185e2 Binary files /dev/null and b/packages/ui/src/assets/audio/bip-bop-10.aac differ diff --git a/packages/ui/src/assets/audio/nope-01.aac b/packages/ui/src/assets/audio/nope-01.aac new file mode 100644 index 0000000000000000000000000000000000000000..b55a4dc4e3d324e6aa0df3dad5c8c0417e766bfc Binary files /dev/null and b/packages/ui/src/assets/audio/nope-01.aac differ diff --git a/packages/ui/src/assets/audio/nope-02.aac b/packages/ui/src/assets/audio/nope-02.aac new file mode 100644 index 0000000000000000000000000000000000000000..efa349ec1c6ccf428f563e1e7330f341823993f2 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-02.aac differ diff --git a/packages/ui/src/assets/audio/nope-03.aac b/packages/ui/src/assets/audio/nope-03.aac new file mode 100644 index 0000000000000000000000000000000000000000..f9650c8e49518feed23d69fdb7b433db813415b9 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-03.aac differ diff --git a/packages/ui/src/assets/audio/nope-03.mp3 b/packages/ui/src/assets/audio/nope-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..42442317c372845d0f0f3dd82f35c567ed0beff5 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-03.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-04.aac b/packages/ui/src/assets/audio/nope-04.aac new file mode 100644 index 0000000000000000000000000000000000000000..0abd12f29790f971d005fc72093e94cbe8a928d3 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-04.aac differ diff --git a/packages/ui/src/assets/audio/nope-04.mp3 b/packages/ui/src/assets/audio/nope-04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8ac496a5c4c850e8a97b96b214b398d77e135534 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-04.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-05.aac b/packages/ui/src/assets/audio/nope-05.aac new file mode 100644 index 0000000000000000000000000000000000000000..2352663343b8f4336eda09257d4b056cf6376877 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-05.aac differ diff --git a/packages/ui/src/assets/audio/nope-05.mp3 b/packages/ui/src/assets/audio/nope-05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..087efb6394fd50e1c18ed28cdd79eafa42a63a71 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-05.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-06.aac b/packages/ui/src/assets/audio/nope-06.aac new file mode 100644 index 0000000000000000000000000000000000000000..7e5b66ecd900de06c58ee27f0f6d57f7950857b9 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-06.aac differ diff --git a/packages/ui/src/assets/audio/nope-06.mp3 b/packages/ui/src/assets/audio/nope-06.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ed22c1cba2c66c97e76444cc66cd7b06f3bfd687 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-06.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-07.aac b/packages/ui/src/assets/audio/nope-07.aac new file mode 100644 index 0000000000000000000000000000000000000000..80b5b02c01c7dd3cdf3f22812222205d5acae7c5 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-07.aac differ diff --git a/packages/ui/src/assets/audio/nope-07.mp3 b/packages/ui/src/assets/audio/nope-07.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4b6dd462c80ca49e6c25015c28c0db2c74fc9e78 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-07.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-08.mp3 b/packages/ui/src/assets/audio/nope-08.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e54577e43576977e1db835f5d61fe6bedd804e7e Binary files /dev/null and b/packages/ui/src/assets/audio/nope-08.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-09.mp3 b/packages/ui/src/assets/audio/nope-09.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..26a4ac806d08b18d5e5b29b8beb2e55838de1435 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-09.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-10.aac b/packages/ui/src/assets/audio/nope-10.aac new file mode 100644 index 0000000000000000000000000000000000000000..798291aadbe013f11543c76659476d7192feaf9f Binary files /dev/null and b/packages/ui/src/assets/audio/nope-10.aac differ diff --git a/packages/ui/src/assets/audio/nope-10.mp3 b/packages/ui/src/assets/audio/nope-10.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0720993519ca211892aa69fd91d550a01b27b936 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-10.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-11.aac b/packages/ui/src/assets/audio/nope-11.aac new file mode 100644 index 0000000000000000000000000000000000000000..64caccf36a6643a93f9b50554fcca2deed7442b0 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-11.aac differ diff --git a/packages/ui/src/assets/audio/nope-11.mp3 b/packages/ui/src/assets/audio/nope-11.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..483deefd606d7cb16aff86ff2946885b612ad67a Binary files /dev/null and b/packages/ui/src/assets/audio/nope-11.mp3 differ diff --git a/packages/ui/src/assets/audio/nope-12.aac b/packages/ui/src/assets/audio/nope-12.aac new file mode 100644 index 0000000000000000000000000000000000000000..39ec243b551e925b238f5fd49a4f3a30f8a04a23 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-12.aac differ diff --git a/packages/ui/src/assets/audio/nope-12.mp3 b/packages/ui/src/assets/audio/nope-12.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4b62e62c174c10ddb7792b6a7759aa21d0846344 Binary files /dev/null and b/packages/ui/src/assets/audio/nope-12.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-01.mp3 b/packages/ui/src/assets/audio/staplebops-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b3b34a32fe8684b535ffb0d76255e62ef873da95 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-01.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-02.mp3 b/packages/ui/src/assets/audio/staplebops-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..276429104efe17697a15e514fb0fe0ce8111f777 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-02.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-03.mp3 b/packages/ui/src/assets/audio/staplebops-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..465017e1058f7a2176f77b40f7fcab5ccfc32e64 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-03.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-04.mp3 b/packages/ui/src/assets/audio/staplebops-04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..18181770ff254e7ac5f97082b287275756d54745 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-04.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-05.mp3 b/packages/ui/src/assets/audio/staplebops-05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8046720b0cfa0559e86ee8bafa41245281e024ef Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-05.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-06.mp3 b/packages/ui/src/assets/audio/staplebops-06.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..93a71695a7e6e25b175ed6f1d01d95f08a8897d0 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-06.mp3 differ diff --git a/packages/ui/src/assets/audio/staplebops-07.mp3 b/packages/ui/src/assets/audio/staplebops-07.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e6be5b5c8a7a2eeb55352af56580c3f340e4cc27 Binary files /dev/null and b/packages/ui/src/assets/audio/staplebops-07.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-01.aac b/packages/ui/src/assets/audio/yup-01.aac new file mode 100644 index 0000000000000000000000000000000000000000..fd300404f7f09b3bd2821f949c77bed83159f38a Binary files /dev/null and b/packages/ui/src/assets/audio/yup-01.aac differ diff --git a/packages/ui/src/assets/audio/yup-01.mp3 b/packages/ui/src/assets/audio/yup-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d4c8dcbee5370541aa2705f38f6155875cbadaae Binary files /dev/null and b/packages/ui/src/assets/audio/yup-01.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-02.aac b/packages/ui/src/assets/audio/yup-02.aac new file mode 100644 index 0000000000000000000000000000000000000000..68ae153a6e95dad9b0c6f649b4412c577d2f2bdb Binary files /dev/null and b/packages/ui/src/assets/audio/yup-02.aac differ diff --git a/packages/ui/src/assets/audio/yup-02.mp3 b/packages/ui/src/assets/audio/yup-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..09d4dc6c8be50af84ae50d82f665176f44300e2a Binary files /dev/null and b/packages/ui/src/assets/audio/yup-02.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-03.aac b/packages/ui/src/assets/audio/yup-03.aac new file mode 100644 index 0000000000000000000000000000000000000000..6d5d968f80bd21aac7c4c85146030fbfcf497810 Binary files /dev/null and b/packages/ui/src/assets/audio/yup-03.aac differ diff --git a/packages/ui/src/assets/audio/yup-03.mp3 b/packages/ui/src/assets/audio/yup-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0406e15edadccfbb9247ef8fcfd4111acd514b5c Binary files /dev/null and b/packages/ui/src/assets/audio/yup-03.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-04.mp3 b/packages/ui/src/assets/audio/yup-04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..533ead9136761eb8ffcee76a9cef4651b43f0986 Binary files /dev/null and b/packages/ui/src/assets/audio/yup-04.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-05.aac b/packages/ui/src/assets/audio/yup-05.aac new file mode 100644 index 0000000000000000000000000000000000000000..3613c5d782e2e6c5b3fc1c551a8e097011a6cb74 Binary files /dev/null and b/packages/ui/src/assets/audio/yup-05.aac differ diff --git a/packages/ui/src/assets/audio/yup-05.mp3 b/packages/ui/src/assets/audio/yup-05.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ec7c13d00516bb963406855ca38579245aefd6a2 Binary files /dev/null and b/packages/ui/src/assets/audio/yup-05.mp3 differ diff --git a/packages/ui/src/assets/audio/yup-06.mp3 b/packages/ui/src/assets/audio/yup-06.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f611da8e82d87d5d2de8edfa06e983a584803c3f Binary files /dev/null and b/packages/ui/src/assets/audio/yup-06.mp3 differ diff --git a/packages/ui/src/assets/icons/app/android-studio.svg b/packages/ui/src/assets/icons/app/android-studio.svg new file mode 100644 index 0000000000000000000000000000000000000000..8d87619f754e2aa273dab23800825e4034ceedfa --- /dev/null +++ b/packages/ui/src/assets/icons/app/android-studio.svg @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/antigravity.svg b/packages/ui/src/assets/icons/app/antigravity.svg new file mode 100644 index 0000000000000000000000000000000000000000..52f235068f0b0722196738ed64ed7bdb8a7e6b8a --- /dev/null +++ b/packages/ui/src/assets/icons/app/antigravity.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/cursor.svg b/packages/ui/src/assets/icons/app/cursor.svg new file mode 100644 index 0000000000000000000000000000000000000000..a778608649d06d3f5bb3ca50521e93081cfadc31 --- /dev/null +++ b/packages/ui/src/assets/icons/app/cursor.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/file-explorer.svg b/packages/ui/src/assets/icons/app/file-explorer.svg new file mode 100644 index 0000000000000000000000000000000000000000..e2166579688ce137845f2645cde0111a889f518e --- /dev/null +++ b/packages/ui/src/assets/icons/app/file-explorer.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/finder.png b/packages/ui/src/assets/icons/app/finder.png new file mode 100644 index 0000000000000000000000000000000000000000..2315b62b4fd405c9094faedeb5274290726ee35b Binary files /dev/null and b/packages/ui/src/assets/icons/app/finder.png differ diff --git a/packages/ui/src/assets/icons/app/ghostty.svg b/packages/ui/src/assets/icons/app/ghostty.svg new file mode 100644 index 0000000000000000000000000000000000000000..d646bd32b38ba63534d0b5c05b98a69bd678eba0 --- /dev/null +++ b/packages/ui/src/assets/icons/app/ghostty.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/iterm2.svg b/packages/ui/src/assets/icons/app/iterm2.svg new file mode 100644 index 0000000000000000000000000000000000000000..48c1aac51f2778b00f52d341551b7596e70c0c9c --- /dev/null +++ b/packages/ui/src/assets/icons/app/iterm2.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/powershell.svg b/packages/ui/src/assets/icons/app/powershell.svg new file mode 100644 index 0000000000000000000000000000000000000000..46570203c543621a453e7153291a1788fd4efa9d --- /dev/null +++ b/packages/ui/src/assets/icons/app/powershell.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/sublimetext.svg b/packages/ui/src/assets/icons/app/sublimetext.svg new file mode 100644 index 0000000000000000000000000000000000000000..7e625b559ed2b8a63fc7cf17b4cebf79d446de34 --- /dev/null +++ b/packages/ui/src/assets/icons/app/sublimetext.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/terminal.png b/packages/ui/src/assets/icons/app/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..1072b7debf7484ed227be56e8b6586e2cc19dc79 Binary files /dev/null and b/packages/ui/src/assets/icons/app/terminal.png differ diff --git a/packages/ui/src/assets/icons/app/textmate.png b/packages/ui/src/assets/icons/app/textmate.png new file mode 100644 index 0000000000000000000000000000000000000000..4545bfd2b895ae82d9e34d69eda0e3bfe28f31a0 Binary files /dev/null and b/packages/ui/src/assets/icons/app/textmate.png differ diff --git a/packages/ui/src/assets/icons/app/vscode.svg b/packages/ui/src/assets/icons/app/vscode.svg new file mode 100644 index 0000000000000000000000000000000000000000..6b1827a6a2e96202c31c1f3a27bac076279d627e --- /dev/null +++ b/packages/ui/src/assets/icons/app/vscode.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/warp.png b/packages/ui/src/assets/icons/app/warp.png new file mode 100644 index 0000000000000000000000000000000000000000..5634adfe2b6d986a920af1de1aef2324c50e8ec7 Binary files /dev/null and b/packages/ui/src/assets/icons/app/warp.png differ diff --git a/packages/ui/src/assets/icons/app/xcode.png b/packages/ui/src/assets/icons/app/xcode.png new file mode 100644 index 0000000000000000000000000000000000000000..59da8774927ae6b08026ef3dc8a4d54e611809ff Binary files /dev/null and b/packages/ui/src/assets/icons/app/xcode.png differ diff --git a/packages/ui/src/assets/icons/app/zed-dark.svg b/packages/ui/src/assets/icons/app/zed-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..0f9d12d05f129fea0d9a361c1ce29fde45b7abf8 --- /dev/null +++ b/packages/ui/src/assets/icons/app/zed-dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/app/zed.svg b/packages/ui/src/assets/icons/app/zed.svg new file mode 100644 index 0000000000000000000000000000000000000000..37aa2d6c09bebb1da9a63b3fb95da6ff47e6a119 --- /dev/null +++ b/packages/ui/src/assets/icons/app/zed.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/actionscript.svg b/packages/ui/src/assets/icons/file-types/actionscript.svg new file mode 100644 index 0000000000000000000000000000000000000000..31d91f2db8d9e6f3e2d7739ae774a4b80c925783 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/actionscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/adobe-illustrator_light.svg b/packages/ui/src/assets/icons/file-types/adobe-illustrator_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..326d2315a1796d9605bc61a87818b2469caafcfc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/adobe-illustrator_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/adobe-photoshop.svg b/packages/ui/src/assets/icons/file-types/adobe-photoshop.svg new file mode 100644 index 0000000000000000000000000000000000000000..27033d9afd42b7b3588674c650c2ee57c4ca87c7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/adobe-photoshop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/adobe-photoshop_light.svg b/packages/ui/src/assets/icons/file-types/adobe-photoshop_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..d2bfb4d25e666a198af42b556b8132cc9a1210a0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/adobe-photoshop_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/advpl.svg b/packages/ui/src/assets/icons/file-types/advpl.svg new file mode 100644 index 0000000000000000000000000000000000000000..54e493b0f965beab7ea6d0fc673f52fe8cfc26db --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/advpl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/amplify.svg b/packages/ui/src/assets/icons/file-types/amplify.svg new file mode 100644 index 0000000000000000000000000000000000000000..89f42120bae49add1f25f3672a198f26345d3ff6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/amplify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/android.svg b/packages/ui/src/assets/icons/file-types/android.svg new file mode 100644 index 0000000000000000000000000000000000000000..c44608d4b2eb0a54c75e18a8ab80c37ab4725787 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/android.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/antlr.svg b/packages/ui/src/assets/icons/file-types/antlr.svg new file mode 100644 index 0000000000000000000000000000000000000000..42f43bb308e5f70b3a2366d4853baf585cc0445b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/antlr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/apollo.svg b/packages/ui/src/assets/icons/file-types/apollo.svg new file mode 100644 index 0000000000000000000000000000000000000000..6de6aa26bf14e40ae34228c33d99ff824cad6708 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/apollo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/apps-script.svg b/packages/ui/src/assets/icons/file-types/apps-script.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed20f1f13a85da3335d5d6505b203bc93fe51bd7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/apps-script.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/arduino.svg b/packages/ui/src/assets/icons/file-types/arduino.svg new file mode 100644 index 0000000000000000000000000000000000000000..053dc12672a5ec50e37c497a20c30fab4a8a5e94 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/arduino.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/asciidoc.svg b/packages/ui/src/assets/icons/file-types/asciidoc.svg new file mode 100644 index 0000000000000000000000000000000000000000..82215c7d41ea3530c05fe134785246ac050e4281 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/asciidoc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/assembly.svg b/packages/ui/src/assets/icons/file-types/assembly.svg new file mode 100644 index 0000000000000000000000000000000000000000..33a056643fe01b83977163ee3e5919d8fc75e94e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/assembly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/astro.svg b/packages/ui/src/assets/icons/file-types/astro.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa67feeff5e2b27df85c3c73347844f5c0fc0dc7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/astro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/audio.svg b/packages/ui/src/assets/icons/file-types/audio.svg new file mode 100644 index 0000000000000000000000000000000000000000..74f43c461753f6e9c496d981823ec6e75d52e0d2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/audio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/aurelia.svg b/packages/ui/src/assets/icons/file-types/aurelia.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7b67f02354188a057681c9739658b3fe690c819 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/aurelia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/authors.svg b/packages/ui/src/assets/icons/file-types/authors.svg new file mode 100644 index 0000000000000000000000000000000000000000..88618a7014d7d6addfc841c4137b6c4dab4630a6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/authors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/auto.svg b/packages/ui/src/assets/icons/file-types/auto.svg new file mode 100644 index 0000000000000000000000000000000000000000..41bd15de4d2343bea770e8e5162b17b64cdd6364 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/auto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/auto_light.svg b/packages/ui/src/assets/icons/file-types/auto_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..5f2451b2384fac7780cf96829e3fc8940814c192 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/auto_light.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/autohotkey.svg b/packages/ui/src/assets/icons/file-types/autohotkey.svg new file mode 100644 index 0000000000000000000000000000000000000000..4ecd7a3ef4441f3ca4fb2baeca5ed83f861b6e52 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/autohotkey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/azure.svg b/packages/ui/src/assets/icons/file-types/azure.svg new file mode 100644 index 0000000000000000000000000000000000000000..2330f87bbe296c9e45f2dc52a5d4c6cc08384d01 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/babel.svg b/packages/ui/src/assets/icons/file-types/babel.svg new file mode 100644 index 0000000000000000000000000000000000000000..244ae36a1a931adc3d585c42b742fa7cf5154b2c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/babel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ballerina.svg b/packages/ui/src/assets/icons/file-types/ballerina.svg new file mode 100644 index 0000000000000000000000000000000000000000..3c1341d77a2a2beff64a1855c0e3fc16c3b50551 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ballerina.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bazel.svg b/packages/ui/src/assets/icons/file-types/bazel.svg new file mode 100644 index 0000000000000000000000000000000000000000..b38a90c59a5127004d7f94a99b4fee5da271e943 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bazel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bbx.svg b/packages/ui/src/assets/icons/file-types/bbx.svg new file mode 100644 index 0000000000000000000000000000000000000000..002d260477b38dccfd9fae60fb091fa113a26d93 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bbx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/beancount.svg b/packages/ui/src/assets/icons/file-types/beancount.svg new file mode 100644 index 0000000000000000000000000000000000000000..905ff22b044ecdd0dbf1539247e77d63b751836e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/beancount.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bench-jsx.svg b/packages/ui/src/assets/icons/file-types/bench-jsx.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed2b9d434c2672991711702af522fa0579048c3a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bench-jsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bibliography.svg b/packages/ui/src/assets/icons/file-types/bibliography.svg new file mode 100644 index 0000000000000000000000000000000000000000..ad6baa6ef8073e7b9fa74bf13962d828d3f5285c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bibliography.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bibtex-style.svg b/packages/ui/src/assets/icons/file-types/bibtex-style.svg new file mode 100644 index 0000000000000000000000000000000000000000..24d121d727949b7f4c7ab3660ce424b8b09ad16e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bibtex-style.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bithound.svg b/packages/ui/src/assets/icons/file-types/bithound.svg new file mode 100644 index 0000000000000000000000000000000000000000..1eea4deaebaf7176664c7238c92a3825d78a06b4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bithound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/blender.svg b/packages/ui/src/assets/icons/file-types/blender.svg new file mode 100644 index 0000000000000000000000000000000000000000..f55d6bc7dccf18e5b83f86e391693de2da87fe60 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/blender.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/blink.svg b/packages/ui/src/assets/icons/file-types/blink.svg new file mode 100644 index 0000000000000000000000000000000000000000..441228857a47edfc70cc1c885662b908881cec66 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/blink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bower.svg b/packages/ui/src/assets/icons/file-types/bower.svg new file mode 100644 index 0000000000000000000000000000000000000000..9ffb06acc1bb1924a6af699cec8a5cc1cdf1ca42 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/browserlist_light.svg b/packages/ui/src/assets/icons/file-types/browserlist_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa34de6a19032f8bc0f8e444a20bd7c4bf5ab695 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/browserlist_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bruno.svg b/packages/ui/src/assets/icons/file-types/bruno.svg new file mode 100644 index 0000000000000000000000000000000000000000..88bebeae4d1cccf78ef5e7375a2debf442581e78 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bruno.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/bun.svg b/packages/ui/src/assets/icons/file-types/bun.svg new file mode 100644 index 0000000000000000000000000000000000000000..cc3620479b8ed87491884cd28ac8878982a86854 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/bun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/c.svg b/packages/ui/src/assets/icons/file-types/c.svg new file mode 100644 index 0000000000000000000000000000000000000000..5bb84b6a97b3a374a3b21ab24bdb108e1cd30c1e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/c.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/c3.svg b/packages/ui/src/assets/icons/file-types/c3.svg new file mode 100644 index 0000000000000000000000000000000000000000..ff30caab272cd24c814e6fd37b54d5c8cb857f30 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/c3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cabal.svg b/packages/ui/src/assets/icons/file-types/cabal.svg new file mode 100644 index 0000000000000000000000000000000000000000..014335bf726e1372ea117ce520019767cf1061cb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cabal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cadence.svg b/packages/ui/src/assets/icons/file-types/cadence.svg new file mode 100644 index 0000000000000000000000000000000000000000..25338baaeafb6e5a8f1740d214f4e2714b7000b4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cadence.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cake.svg b/packages/ui/src/assets/icons/file-types/cake.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed6b09f4da1082b9a2e9fcf1bd4455c355bf49d3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/capacitor.svg b/packages/ui/src/assets/icons/file-types/capacitor.svg new file mode 100644 index 0000000000000000000000000000000000000000..2a48c5843f6e90f634f584bc23f95201610597e6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/capacitor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/capnp.svg b/packages/ui/src/assets/icons/file-types/capnp.svg new file mode 100644 index 0000000000000000000000000000000000000000..c74aa9f0186842e9c367f83c46ed021c751c3ba5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/capnp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cbx.svg b/packages/ui/src/assets/icons/file-types/cbx.svg new file mode 100644 index 0000000000000000000000000000000000000000..716426ad7bb768c4fe74e6e531f5680780313e9d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cbx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cds.svg b/packages/ui/src/assets/icons/file-types/cds.svg new file mode 100644 index 0000000000000000000000000000000000000000..3c7fed81e1f4c8971240f03aff979b64b9eaa955 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/chess_light.svg b/packages/ui/src/assets/icons/file-types/chess_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..250fb8cbf11bdf3fb618232aff92b6739a7496c8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/chess_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/chrome.svg b/packages/ui/src/assets/icons/file-types/chrome.svg new file mode 100644 index 0000000000000000000000000000000000000000..0208e2704c047a13f046383c956168d4b5c5a8dc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/chrome.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/circleci.svg b/packages/ui/src/assets/icons/file-types/circleci.svg new file mode 100644 index 0000000000000000000000000000000000000000..464dace08315e484a2832e64aea58624b6e055c5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/circleci.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/clangd.svg b/packages/ui/src/assets/icons/file-types/clangd.svg new file mode 100644 index 0000000000000000000000000000000000000000..f6742e9aeffca4d153812307adf2fa17d46bf177 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/clangd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/claude.svg b/packages/ui/src/assets/icons/file-types/claude.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa01860fdd3be606785f90178b95f9cbd6850861 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cloudfoundry.svg b/packages/ui/src/assets/icons/file-types/cloudfoundry.svg new file mode 100644 index 0000000000000000000000000000000000000000..3251ca469b4634f1338c1d22eac6457470696a36 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cloudfoundry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cmake.svg b/packages/ui/src/assets/icons/file-types/cmake.svg new file mode 100644 index 0000000000000000000000000000000000000000..aa217964f8c82d90656fb37def5ba2cfa39b5dd7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cmake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/coala.svg b/packages/ui/src/assets/icons/file-types/coala.svg new file mode 100644 index 0000000000000000000000000000000000000000..1e84b8f5ef909ca1a0cf260d9c7948b045aa79ae --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/coala.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cobol.svg b/packages/ui/src/assets/icons/file-types/cobol.svg new file mode 100644 index 0000000000000000000000000000000000000000..220b0ab48ed0b51c5ad9b9690582cb86d7881afa --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cobol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/codecov.svg b/packages/ui/src/assets/icons/file-types/codecov.svg new file mode 100644 index 0000000000000000000000000000000000000000..9a8d4eb7b30c08e5d2d828c859d2fe041a1b96a8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/codecov.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/coffee.svg b/packages/ui/src/assets/icons/file-types/coffee.svg new file mode 100644 index 0000000000000000000000000000000000000000..f81b65c796e11dfa885585200dc4e2323acd254c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/coldfusion.svg b/packages/ui/src/assets/icons/file-types/coldfusion.svg new file mode 100644 index 0000000000000000000000000000000000000000..d018b6652a4d098c068086a4f850a1de259d3617 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/coldfusion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/command.svg b/packages/ui/src/assets/icons/file-types/command.svg new file mode 100644 index 0000000000000000000000000000000000000000..b5a7913d26075ed1a24ada8955eaf62b291c1550 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/command.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/console.svg b/packages/ui/src/assets/icons/file-types/console.svg new file mode 100644 index 0000000000000000000000000000000000000000..75f90b733cba0f27753bf106d7380f3407611888 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/console.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/context.svg b/packages/ui/src/assets/icons/file-types/context.svg new file mode 100644 index 0000000000000000000000000000000000000000..1b8200ed4bab71037dd405ccd41e48f0edf31aa2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/context.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/controller.svg b/packages/ui/src/assets/icons/file-types/controller.svg new file mode 100644 index 0000000000000000000000000000000000000000..9f99264b9f10c76a4be73c84daa9d52029064603 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/controller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/credits.svg b/packages/ui/src/assets/icons/file-types/credits.svg new file mode 100644 index 0000000000000000000000000000000000000000..b67c55a8c3a2e595022df3c02e383d3767681ca9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/credits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/crystal.svg b/packages/ui/src/assets/icons/file-types/crystal.svg new file mode 100644 index 0000000000000000000000000000000000000000..e3796bfa578bf28e5bd8f779ce225da8c922a3ff --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/crystal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/csharp.svg b/packages/ui/src/assets/icons/file-types/csharp.svg new file mode 100644 index 0000000000000000000000000000000000000000..02b1be3e1ab138040e8f195180f2529796c65215 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/csharp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/css.svg b/packages/ui/src/assets/icons/file-types/css.svg new file mode 100644 index 0000000000000000000000000000000000000000..1acad1be2dcc23ae3347b39c4a574a4d2210e64e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/css.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cucumber.svg b/packages/ui/src/assets/icons/file-types/cucumber.svg new file mode 100644 index 0000000000000000000000000000000000000000..052fd2955ac0659c1fe3557311d26bf2c9209158 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cucumber.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cursor.svg b/packages/ui/src/assets/icons/file-types/cursor.svg new file mode 100644 index 0000000000000000000000000000000000000000..b754147ff0b2366dc10d2729548668247eebaa1a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cursor.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/file-types/cursor_light.svg b/packages/ui/src/assets/icons/file-types/cursor_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..f65b6461ae41286fbea111cc19f4d9f99e0e2ec1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cursor_light.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/file-types/cypress.svg b/packages/ui/src/assets/icons/file-types/cypress.svg new file mode 100644 index 0000000000000000000000000000000000000000..35274d3a05121881268a636642e34e0a7aac8354 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/cypress.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/d.svg b/packages/ui/src/assets/icons/file-types/d.svg new file mode 100644 index 0000000000000000000000000000000000000000..3207725dadf99bb5cc532e1a27c8ae1753fcf03a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/dart_generated.svg b/packages/ui/src/assets/icons/file-types/dart_generated.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f64f5f0277c23b0747514012c4ec08645c879cc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/dart_generated.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/denizenscript.svg b/packages/ui/src/assets/icons/file-types/denizenscript.svg new file mode 100644 index 0000000000000000000000000000000000000000..2debb9da21fe26f5a46a2b87edbf11289e819336 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/denizenscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/deno.svg b/packages/ui/src/assets/icons/file-types/deno.svg new file mode 100644 index 0000000000000000000000000000000000000000..344a12e407f08be133c672a64a2a55b1d157316d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/deno.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/deno_light.svg b/packages/ui/src/assets/icons/file-types/deno_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..ee82ea7c7be76a0a327fb13ac2e6b14d4d248cef --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/deno_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/dependencies-update.svg b/packages/ui/src/assets/icons/file-types/dependencies-update.svg new file mode 100644 index 0000000000000000000000000000000000000000..b85ad9e5fe9b3dc2fc7cbd7407b55f7d73ca3076 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/dependencies-update.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/disc.svg b/packages/ui/src/assets/icons/file-types/disc.svg new file mode 100644 index 0000000000000000000000000000000000000000..b0d74dc258235256f0c777aba42947473d77e337 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/disc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/django.svg b/packages/ui/src/assets/icons/file-types/django.svg new file mode 100644 index 0000000000000000000000000000000000000000..64c9ee339bf8e49eb302df43f10a2415cb3f9dc5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/django.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/document.svg b/packages/ui/src/assets/icons/file-types/document.svg new file mode 100644 index 0000000000000000000000000000000000000000..a717956b7d28f75befe20aacf227f9088fa315f4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/document.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/dotjs.svg b/packages/ui/src/assets/icons/file-types/dotjs.svg new file mode 100644 index 0000000000000000000000000000000000000000..5ac893c6a14aa0362b195790652a5b046e2f7a87 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/dotjs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/drawio.svg b/packages/ui/src/assets/icons/file-types/drawio.svg new file mode 100644 index 0000000000000000000000000000000000000000..8ef1bcb728d43942edd8db8a4874c11eb6cbc903 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/drawio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/drone_light.svg b/packages/ui/src/assets/icons/file-types/drone_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..ce3ad253b47c959f0545531031c456c38be02e31 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/drone_light.svg @@ -0,0 +1,4 @@ + + + diff --git a/packages/ui/src/assets/icons/file-types/duc.svg b/packages/ui/src/assets/icons/file-types/duc.svg new file mode 100644 index 0000000000000000000000000000000000000000..1d85b34adeab6681f035343111308d4643a99f86 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/duc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/dune.svg b/packages/ui/src/assets/icons/file-types/dune.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a35e70010630e264d26fee3da6e79817c050f9d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/dune.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/editorconfig.svg b/packages/ui/src/assets/icons/file-types/editorconfig.svg new file mode 100644 index 0000000000000000000000000000000000000000..ba528993d4e17a256a7abe66633ee75b9e57864b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/editorconfig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/elm.svg b/packages/ui/src/assets/icons/file-types/elm.svg new file mode 100644 index 0000000000000000000000000000000000000000..c17b74d7de494c4277aa96b2b4ed039f36f2d190 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/elm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ember.svg b/packages/ui/src/assets/icons/file-types/ember.svg new file mode 100644 index 0000000000000000000000000000000000000000..c16cef13c5348c239fbbd86e4944f7c1cab0f6a4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ember.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/epub.svg b/packages/ui/src/assets/icons/file-types/epub.svg new file mode 100644 index 0000000000000000000000000000000000000000..98f11d490b9c9aaa48ffe0717574d6c5ac01b510 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/epub.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/erlang.svg b/packages/ui/src/assets/icons/file-types/erlang.svg new file mode 100644 index 0000000000000000000000000000000000000000..41025d6dc29c0ca91968a736249a74acb3e153c2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/erlang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/esbuild.svg b/packages/ui/src/assets/icons/file-types/esbuild.svg new file mode 100644 index 0000000000000000000000000000000000000000..e682d6b10f1a5d3e6f042aea858196f77965fa7d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/esbuild.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/eslint.svg b/packages/ui/src/assets/icons/file-types/eslint.svg new file mode 100644 index 0000000000000000000000000000000000000000..54fe8cc22a64416574dc8b084b6995e3ca26fc06 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/eslint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/exe.svg b/packages/ui/src/assets/icons/file-types/exe.svg new file mode 100644 index 0000000000000000000000000000000000000000..dde947d804deaaf5028537ff9611f74d5d2590e5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/exe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/fastlane.svg b/packages/ui/src/assets/icons/file-types/fastlane.svg new file mode 100644 index 0000000000000000000000000000000000000000..44d042fbd372164b62b0819f929f3531a9fc3858 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/fastlane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/favicon.svg b/packages/ui/src/assets/icons/file-types/favicon.svg new file mode 100644 index 0000000000000000000000000000000000000000..21abf661be1642f4c68a7be75e1b1b016cbd6719 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/figma.svg b/packages/ui/src/assets/icons/file-types/figma.svg new file mode 100644 index 0000000000000000000000000000000000000000..db4522bec98152c5a87e1a70dc32a7382543acaa --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/figma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/flash.svg b/packages/ui/src/assets/icons/file-types/flash.svg new file mode 100644 index 0000000000000000000000000000000000000000..abd6e01e9a23da78b41823826e7ebd94f3e991db --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/flash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-admin-open.svg b/packages/ui/src/assets/icons/file-types/folder-admin-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5e77464ff8e004a260014c310c90fecd5814efa1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-admin-open.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-angular.svg b/packages/ui/src/assets/icons/file-types/folder-angular.svg new file mode 100644 index 0000000000000000000000000000000000000000..3d8c87d329bba781c4fbcbbcd3c9f69a77d517a7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-angular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-animation.svg b/packages/ui/src/assets/icons/file-types/folder-animation.svg new file mode 100644 index 0000000000000000000000000000000000000000..6b5bb691fa29afd029265ad6b4f2b0a3bbc5095d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-animation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ansible.svg b/packages/ui/src/assets/icons/file-types/folder-ansible.svg new file mode 100644 index 0000000000000000000000000000000000000000..f4303155ba70367e13799cd6ef31ec809491b156 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ansible.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-api-open.svg b/packages/ui/src/assets/icons/file-types/folder-api-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac3edb9765606746802af5beb5d7fe721def5272 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-api-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-api.svg b/packages/ui/src/assets/icons/file-types/folder-api.svg new file mode 100644 index 0000000000000000000000000000000000000000..bf1d64c548293b23dbeef66f705d02c33ad4da09 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-api.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-apollo-open.svg b/packages/ui/src/assets/icons/file-types/folder-apollo-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..f0febaf95e605beeef804c6c84c2f4433e1d7d96 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-apollo-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-app-open.svg b/packages/ui/src/assets/icons/file-types/folder-app-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c9da6a7bbe1126a61010b58ae285a6d296e097a2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-app-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-app.svg b/packages/ui/src/assets/icons/file-types/folder-app.svg new file mode 100644 index 0000000000000000000000000000000000000000..d0e37f19d507114af0845fa80bd3de85f9e32f4b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-archive.svg b/packages/ui/src/assets/icons/file-types/folder-archive.svg new file mode 100644 index 0000000000000000000000000000000000000000..b018654b23d47f8af26e77e1fd0c85fa4100229f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-astro-open.svg b/packages/ui/src/assets/icons/file-types/folder-astro-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..282a3ce0aa9823d07ed59ebae3349796d00dc7fe --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-astro-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-astro.svg b/packages/ui/src/assets/icons/file-types/folder-astro.svg new file mode 100644 index 0000000000000000000000000000000000000000..b324019e7b958fc77a7b0ce1378eebdf8f4cebe0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-astro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-atom-open.svg b/packages/ui/src/assets/icons/file-types/folder-atom-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5558d1841b6f53dfe74739e5bc125b04ebfbdab2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-atom-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-atom.svg b/packages/ui/src/assets/icons/file-types/folder-atom.svg new file mode 100644 index 0000000000000000000000000000000000000000..c272f6ecdd53541400f1a8d72ba727ad84307dc3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-atom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-audio-open.svg b/packages/ui/src/assets/icons/file-types/folder-audio-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..6d9b23836a3070544e873e05abd93febb2a48cb4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-audio-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-audio.svg b/packages/ui/src/assets/icons/file-types/folder-audio.svg new file mode 100644 index 0000000000000000000000000000000000000000..e3d0db3d0bad2f92d55e7af72df25985b340d1bf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-audio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-backup-open.svg b/packages/ui/src/assets/icons/file-types/folder-backup-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2914ee227c38399982acc6497c25dd6b549465c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-backup-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-backup.svg b/packages/ui/src/assets/icons/file-types/folder-backup.svg new file mode 100644 index 0000000000000000000000000000000000000000..aa9a6c94213a845387b9746cee257f1ab0fdd75c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-backup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-batch.svg b/packages/ui/src/assets/icons/file-types/folder-batch.svg new file mode 100644 index 0000000000000000000000000000000000000000..c44a66bc20c4e7a8e0d7218b26ead259b7b988bf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-batch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-benchmark-open.svg b/packages/ui/src/assets/icons/file-types/folder-benchmark-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa7b3ea60e8f8113b0cbd29ee7dfebcd7e6179f4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-benchmark-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-bicep-open.svg b/packages/ui/src/assets/icons/file-types/folder-bicep-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..72519ce9d652de85a86f0a072dd1093d92b7f4d0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-bicep-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-bloc.svg b/packages/ui/src/assets/icons/file-types/folder-bloc.svg new file mode 100644 index 0000000000000000000000000000000000000000..cf08363c203b935024563c3019dafb112f22a180 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-bloc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-bower-open.svg b/packages/ui/src/assets/icons/file-types/folder-bower-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..659f87c98119c54067a786545d04ecf5a8d286b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-bower-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-bower.svg b/packages/ui/src/assets/icons/file-types/folder-bower.svg new file mode 100644 index 0000000000000000000000000000000000000000..6bfd65491e977bf234d82cc6b075d1da8acc36f7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-bower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-buildkite.svg b/packages/ui/src/assets/icons/file-types/folder-buildkite.svg new file mode 100644 index 0000000000000000000000000000000000000000..9512b40a8977da5304515c53982ca49db5c626d0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-buildkite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cart.svg b/packages/ui/src/assets/icons/file-types/folder-cart.svg new file mode 100644 index 0000000000000000000000000000000000000000..d19a627908f7f1c1dfeb3aa80512de1b298e55fd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-changesets.svg b/packages/ui/src/assets/icons/file-types/folder-changesets.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc071f48d2c3771f6e53b2ca9830644dd21e5c07 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-changesets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg b/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9e323ff9c2898386f15410bb4c0407ee10f78bd6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-class.svg b/packages/ui/src/assets/icons/file-types/folder-class.svg new file mode 100644 index 0000000000000000000000000000000000000000..8225cf14b536d70511351c08f30915d276da98a5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-class.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-claude-open.svg b/packages/ui/src/assets/icons/file-types/folder-claude-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a52afdcc5b73f04bf42d3f06b0823ef02e949c4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-claude-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-claude.svg b/packages/ui/src/assets/icons/file-types/folder-claude.svg new file mode 100644 index 0000000000000000000000000000000000000000..0c85a134af0cd3bfaa0ec2f0e2efd1a292a680cd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-client.svg b/packages/ui/src/assets/icons/file-types/folder-client.svg new file mode 100644 index 0000000000000000000000000000000000000000..fbfaee77bf94e9e2c8e7e1986bd58ca1aa81c36f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-client.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cline-open.svg b/packages/ui/src/assets/icons/file-types/folder-cline-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..67ef7a2618e2d90619849e29ba898b41c3f266b0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cline-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cloud-functions.svg b/packages/ui/src/assets/icons/file-types/folder-cloud-functions.svg new file mode 100644 index 0000000000000000000000000000000000000000..8dac84a48c0a504d3d54591ebde15bbec3643e8e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cloud-functions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cloudflare.svg b/packages/ui/src/assets/icons/file-types/folder-cloudflare.svg new file mode 100644 index 0000000000000000000000000000000000000000..0cc444ea8c05273d7dcc6e7fd335e52f6acdc92e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cloudflare.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-command.svg b/packages/ui/src/assets/icons/file-types/folder-command.svg new file mode 100644 index 0000000000000000000000000000000000000000..4015207b52a9879c3ca0819c14ece81142112eed --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-command.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-components-open.svg b/packages/ui/src/assets/icons/file-types/folder-components-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..2f55b72fe3273c7cf68fc9d18008e9e6c72e1bc0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-components-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-components.svg b/packages/ui/src/assets/icons/file-types/folder-components.svg new file mode 100644 index 0000000000000000000000000000000000000000..983833e56f2ca632bbce1909a125e4927f247897 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-components.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-config.svg b/packages/ui/src/assets/icons/file-types/folder-config.svg new file mode 100644 index 0000000000000000000000000000000000000000..8519910c2de59bf11ca7ac739d7d6aded6e7abf6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-config.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-constant-open.svg b/packages/ui/src/assets/icons/file-types/folder-constant-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9e8791d45d81335d95e70820bcba972ee4effacc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-constant-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-container-open.svg b/packages/ui/src/assets/icons/file-types/folder-container-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9db833471350bf39e9b11a2430b3f5d181f2d921 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-container-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-container.svg b/packages/ui/src/assets/icons/file-types/folder-container.svg new file mode 100644 index 0000000000000000000000000000000000000000..3ea03c165c52ee9e1dfb709f9df33e7b8d36182f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-container.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-content-open.svg b/packages/ui/src/assets/icons/file-types/folder-content-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a924b27aad3848dd538cbb271a6b95b24aa4c3a5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-content-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-context-open.svg b/packages/ui/src/assets/icons/file-types/folder-context-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a631e02e9ae82953e372f06b5c30b15ac0b1464e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-context-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-context.svg b/packages/ui/src/assets/icons/file-types/folder-context.svg new file mode 100644 index 0000000000000000000000000000000000000000..bee74c1963e3cf32553fda2721ec67da0228f019 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-context.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-core.svg b/packages/ui/src/assets/icons/file-types/folder-core.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7cfae6ef67590f7a2488d4ba75973d6f392506f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-core.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-coverage-open.svg b/packages/ui/src/assets/icons/file-types/folder-coverage-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5d47b2f0b4d45596a68b092fed7c5078664184e9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-coverage-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-css-open.svg b/packages/ui/src/assets/icons/file-types/folder-css-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ef79791fc0b4c8e2a1f06e6f04474d6b7ccb2947 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-css-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-css.svg b/packages/ui/src/assets/icons/file-types/folder-css.svg new file mode 100644 index 0000000000000000000000000000000000000000..4ff433e92a9ecf3d478334b8bf2b972879226de1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-css.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cursor-open.svg b/packages/ui/src/assets/icons/file-types/folder-cursor-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..b6e1068480e34266086574cf80eadab48fd4baf7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cursor-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-custom.svg b/packages/ui/src/assets/icons/file-types/folder-custom.svg new file mode 100644 index 0000000000000000000000000000000000000000..02ac61100fd529fe1ee9778b076a3c05baa8e335 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-cypress-open.svg b/packages/ui/src/assets/icons/file-types/folder-cypress-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..2a18521a2d6339eb6501e329af094993bd04aaa1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-cypress-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-dart-open.svg b/packages/ui/src/assets/icons/file-types/folder-dart-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..8eadca02e131ccd48191e13aced1df9dddf56142 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-dart-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-dart.svg b/packages/ui/src/assets/icons/file-types/folder-dart.svg new file mode 100644 index 0000000000000000000000000000000000000000..0de15182da7bca67d4979df9d00fe53cbb1061fc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-dart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-database-open.svg b/packages/ui/src/assets/icons/file-types/folder-database-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5bde146074c4deefd296b9fef838fe884c835594 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-database-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-database.svg b/packages/ui/src/assets/icons/file-types/folder-database.svg new file mode 100644 index 0000000000000000000000000000000000000000..b256e645c35e7bc527a5aa73146c120a37e4f48c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-debug-open.svg b/packages/ui/src/assets/icons/file-types/folder-debug-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a0c16a74129312af4596c180ab4d1dce878e44b8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-debug-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-debug.svg b/packages/ui/src/assets/icons/file-types/folder-debug.svg new file mode 100644 index 0000000000000000000000000000000000000000..1099873ff266fa23e5a411a8fb68e35c80d82538 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-debug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-delta-open.svg b/packages/ui/src/assets/icons/file-types/folder-delta-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2b56636eddec83b44766a7bf475c2f679abee83 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-delta-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-delta.svg b/packages/ui/src/assets/icons/file-types/folder-delta.svg new file mode 100644 index 0000000000000000000000000000000000000000..cdda479a12ad4921192bd18ed807e433803a72ab --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-delta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-desktop.svg b/packages/ui/src/assets/icons/file-types/folder-desktop.svg new file mode 100644 index 0000000000000000000000000000000000000000..5a20b4960a5fd7193605d0db649e601cad12fb77 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-directive-open.svg b/packages/ui/src/assets/icons/file-types/folder-directive-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..71946e51087d2ac41a3da86eb9f951739f65ce15 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-directive-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-dist-open.svg b/packages/ui/src/assets/icons/file-types/folder-dist-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..553cef1cba7c78600ee1823b53db351f3234a073 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-dist-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-docker-open.svg b/packages/ui/src/assets/icons/file-types/folder-docker-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a76e97b9446d02fbf443f3a502229588fe061dfd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-docker-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-docker.svg b/packages/ui/src/assets/icons/file-types/folder-docker.svg new file mode 100644 index 0000000000000000000000000000000000000000..c5b0949993bffcbdc77f7a121eef290dd36692f7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-download-open.svg b/packages/ui/src/assets/icons/file-types/folder-download-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ddb9c2416463b1eaaf45fb040e7366d7a366b731 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-download-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-download.svg b/packages/ui/src/assets/icons/file-types/folder-download.svg new file mode 100644 index 0000000000000000000000000000000000000000..34105b932fc203e394dbbb853de5828271b20b02 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-drizzle.svg b/packages/ui/src/assets/icons/file-types/folder-drizzle.svg new file mode 100644 index 0000000000000000000000000000000000000000..d01a18618b346a8b6238c2dc912dd7d5fa4af321 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-dump.svg b/packages/ui/src/assets/icons/file-types/folder-dump.svg new file mode 100644 index 0000000000000000000000000000000000000000..8178fcc03a26d34a95f406e06c80ffdfaa3971fe --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-dump.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-element-open.svg b/packages/ui/src/assets/icons/file-types/folder-element-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..32dc7cd8723ac756bef9e7eed530999e71f07c13 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-element-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-enum-open.svg b/packages/ui/src/assets/icons/file-types/folder-enum-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..92782b157427aa14e29b6a60e1fe5f4fe620cd8a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-enum-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-environment.svg b/packages/ui/src/assets/icons/file-types/folder-environment.svg new file mode 100644 index 0000000000000000000000000000000000000000..9cc1f2e00bfe22cf309b2e25b948616c005372cf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-environment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-error-open.svg b/packages/ui/src/assets/icons/file-types/folder-error-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..81f0ffc202e2a171ac8de2a54d442f4d31d2bb0e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-error-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-event.svg b/packages/ui/src/assets/icons/file-types/folder-event.svg new file mode 100644 index 0000000000000000000000000000000000000000..f54dea6d8e8da8a39700a17764621564b05b36de --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-event.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-examples.svg b/packages/ui/src/assets/icons/file-types/folder-examples.svg new file mode 100644 index 0000000000000000000000000000000000000000..fba8885ab6e0e174f29f49dc6e11ac1bc535bc0c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-examples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-expo-open.svg b/packages/ui/src/assets/icons/file-types/folder-expo-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..614435abab0a08053406efeba1291f54df589032 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-expo-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-expo.svg b/packages/ui/src/assets/icons/file-types/folder-expo.svg new file mode 100644 index 0000000000000000000000000000000000000000..820a998b66544460cf6caeed9a254da6df9cf00b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-expo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-fastlane-open.svg b/packages/ui/src/assets/icons/file-types/folder-fastlane-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5efb2acad07cbeea56a92f5a90b07a0ac6d7fc14 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-fastlane-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-favicon-open.svg b/packages/ui/src/assets/icons/file-types/folder-favicon-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..b716525ab1fffe13aec0ee061b72b6921380d36e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-favicon-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-favicon.svg b/packages/ui/src/assets/icons/file-types/folder-favicon.svg new file mode 100644 index 0000000000000000000000000000000000000000..6ef90d91c5cd1edf348490efa96d7271fd2ec339 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-firestore-open.svg b/packages/ui/src/assets/icons/file-types/folder-firestore-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a3e6edac7deb00fbc8c27def375b0394e53ad9d9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-firestore-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-firestore.svg b/packages/ui/src/assets/icons/file-types/folder-firestore.svg new file mode 100644 index 0000000000000000000000000000000000000000..cb1249af015888be7a5285df06dba34fe725f5c9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-firestore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-flow.svg b/packages/ui/src/assets/icons/file-types/folder-flow.svg new file mode 100644 index 0000000000000000000000000000000000000000..015518922de3c243f76bafb0681d2cffaea9cf9e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-flow.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-font.svg b/packages/ui/src/assets/icons/file-types/folder-font.svg new file mode 100644 index 0000000000000000000000000000000000000000..0115b730194dc06c2229b2b0cebcbe9a666cd7be --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-forgejo-open.svg b/packages/ui/src/assets/icons/file-types/folder-forgejo-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a9762228eea90149cccc1d3d5cfae862a9a88bce --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-forgejo-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-forgejo.svg b/packages/ui/src/assets/icons/file-types/folder-forgejo.svg new file mode 100644 index 0000000000000000000000000000000000000000..0eaccff60fd401915b737b7432d4a265e325530a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-forgejo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-functions.svg b/packages/ui/src/assets/icons/file-types/folder-functions.svg new file mode 100644 index 0000000000000000000000000000000000000000..01a938514381ecdb6dfd4f6b39173e128985e8b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-functions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg b/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..3ae400ed9f378dc1df3059f45814891234b79572 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg b/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg new file mode 100644 index 0000000000000000000000000000000000000000..3a868cca97c51c793e7113883599bde4762131d8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-git-open.svg b/packages/ui/src/assets/icons/file-types/folder-git-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..90be1c11835c78e50e299d36632e5a3528e5ed8f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-git-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-git.svg b/packages/ui/src/assets/icons/file-types/folder-git.svg new file mode 100644 index 0000000000000000000000000000000000000000..2ca4db551c45107f3a89e872d87e0b91b32b7577 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gitea-open.svg b/packages/ui/src/assets/icons/file-types/folder-gitea-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..239800c479c26b50c75ddba28ead9a66fcb47cca --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gitea-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gitea.svg b/packages/ui/src/assets/icons/file-types/folder-gitea.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac041b325121a6662ccb2937e869365d0b2ebe16 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gitea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-github-open.svg b/packages/ui/src/assets/icons/file-types/folder-github-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..84e5bee8497c919e1d7d87a5e0c5bf03658a5457 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-github-open.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-gitlab-open.svg b/packages/ui/src/assets/icons/file-types/folder-gitlab-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc4deb29587dbea31de1ffcc985d41b13f137019 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gitlab-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gitlab.svg b/packages/ui/src/assets/icons/file-types/folder-gitlab.svg new file mode 100644 index 0000000000000000000000000000000000000000..55db99e0d757689d1478338ebeea57011230cd9e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-godot-open.svg b/packages/ui/src/assets/icons/file-types/folder-godot-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fd785504aa14623f38c66c2b17fceedd7969d5b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-godot-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-godot.svg b/packages/ui/src/assets/icons/file-types/folder-godot.svg new file mode 100644 index 0000000000000000000000000000000000000000..dc4b5d10bcaec937f86db99896c0c990a6054f45 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-godot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-graphql-open.svg b/packages/ui/src/assets/icons/file-types/folder-graphql-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac236509244580656812f6011d8ecc0a5f897d5f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-graphql-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-graphql.svg b/packages/ui/src/assets/icons/file-types/folder-graphql.svg new file mode 100644 index 0000000000000000000000000000000000000000..1d7b1cc68466253cc13548abf760f01ee9c37f03 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-graphql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-guard-open.svg b/packages/ui/src/assets/icons/file-types/folder-guard-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7031e257b0a4bf2102771091c2b8df57c18902e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-guard-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-guard.svg b/packages/ui/src/assets/icons/file-types/folder-guard.svg new file mode 100644 index 0000000000000000000000000000000000000000..b4269ed2297770c8029d51fbb4e91999962c0662 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-guard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-helm-open.svg b/packages/ui/src/assets/icons/file-types/folder-helm-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..6bbf0cc6ded4a8292f0f75dde66dec419e2f1f24 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-helm-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-helm.svg b/packages/ui/src/assets/icons/file-types/folder-helm.svg new file mode 100644 index 0000000000000000000000000000000000000000..7b7d7a7a4724c52d5695ac967dc65e5e5d28d2bb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-helm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-home-open.svg b/packages/ui/src/assets/icons/file-types/folder-home-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..8b0f0ca01765aeac2a374327496eff386fc2c5fd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-home-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-home.svg b/packages/ui/src/assets/icons/file-types/folder-home.svg new file mode 100644 index 0000000000000000000000000000000000000000..a4deeef4abd488718814b687459f5f1f6812ca83 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-hook-open.svg b/packages/ui/src/assets/icons/file-types/folder-hook-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..17d62310d24ac41c77869a01285ea911a5cd23ab --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-hook-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-husky-open.svg b/packages/ui/src/assets/icons/file-types/folder-husky-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..88c19e895e57defcb41223d79e2f1827bf1c7b57 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-husky-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-i18n-open.svg b/packages/ui/src/assets/icons/file-types/folder-i18n-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..bc1a53c0fb437dbd86da93ee5984d6106b1538d7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-i18n-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-images.svg b/packages/ui/src/assets/icons/file-types/folder-images.svg new file mode 100644 index 0000000000000000000000000000000000000000..5b63a6c3a5c10f5c5dfcb5e40b4ee643d0bc44e6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-images.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-import-open.svg b/packages/ui/src/assets/icons/file-types/folder-import-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a58a7e64f10492c5ed80211a1058249384dbf6a6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-import-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-import.svg b/packages/ui/src/assets/icons/file-types/folder-import.svg new file mode 100644 index 0000000000000000000000000000000000000000..0c0f42e57e936bb97b452b4ea28068b0d96e04d7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-import.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-include-open.svg b/packages/ui/src/assets/icons/file-types/folder-include-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc2c011ba095654286e4f284988de607a48f5bc6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-include-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-include.svg b/packages/ui/src/assets/icons/file-types/folder-include.svg new file mode 100644 index 0000000000000000000000000000000000000000..117b91a2c2320c6dac44e401956166fc23e564a9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-include.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-intellij.svg b/packages/ui/src/assets/icons/file-types/folder-intellij.svg new file mode 100644 index 0000000000000000000000000000000000000000..c655f37ef1aaf0d172fc5a27ef8862a27096108a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-intellij.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-intellij_light.svg b/packages/ui/src/assets/icons/file-types/folder-intellij_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..97bc8c7bd120d7e4830e2b8e37abdefc641fbf66 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-intellij_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-interceptor-open.svg b/packages/ui/src/assets/icons/file-types/folder-interceptor-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c91c42ad965f48d07adfb2b13179ada32c1bf668 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-interceptor-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-interceptor.svg b/packages/ui/src/assets/icons/file-types/folder-interceptor.svg new file mode 100644 index 0000000000000000000000000000000000000000..e6cbf9f5cfe43a982aa5c331ec882053fa67eb72 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-interceptor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-interface-open.svg b/packages/ui/src/assets/icons/file-types/folder-interface-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ba54b0ec8b3f68460fe17728a94fe4d32964b7c0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-interface-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-interface.svg b/packages/ui/src/assets/icons/file-types/folder-interface.svg new file mode 100644 index 0000000000000000000000000000000000000000..993ce7258b35cd394f2acae1d44f974acb9b77e8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-interface.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ios-open.svg b/packages/ui/src/assets/icons/file-types/folder-ios-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..112fee6a45e9ebe0a5db660f06295e1e92189c7a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ios-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ios.svg b/packages/ui/src/assets/icons/file-types/folder-ios.svg new file mode 100644 index 0000000000000000000000000000000000000000..7af3b85dadb011f3b5e970eec8d5721601d62219 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ios.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-javascript.svg b/packages/ui/src/assets/icons/file-types/folder-javascript.svg new file mode 100644 index 0000000000000000000000000000000000000000..97cf04cc2690f787ca3ce5d591fd55661612d4b6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-jinja_light.svg b/packages/ui/src/assets/icons/file-types/folder-jinja_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2b08bb7f9d9afbffc039ae38187f990941c9fb0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-jinja_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-job-open.svg b/packages/ui/src/assets/icons/file-types/folder-job-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..efd7cdfbf3e8a04dc6e8d1de39421ef68c3dffaf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-job-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-json-open.svg b/packages/ui/src/assets/icons/file-types/folder-json-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..29cdf2f669685cb2d111c8c4372bdb0ad67ebe60 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-json-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-json.svg b/packages/ui/src/assets/icons/file-types/folder-json.svg new file mode 100644 index 0000000000000000000000000000000000000000..34085f6a9741ad8e30bc8afd3aecd7c227ad7f5c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-json.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-jupyter.svg b/packages/ui/src/assets/icons/file-types/folder-jupyter.svg new file mode 100644 index 0000000000000000000000000000000000000000..d4d3eb35ecb11b89f503966b3e29d9a81dedded7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-jupyter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-keys.svg b/packages/ui/src/assets/icons/file-types/folder-keys.svg new file mode 100644 index 0000000000000000000000000000000000000000..3527f6220c71554595be2f7da8d9261ecbabba13 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-keys.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-kubernetes.svg b/packages/ui/src/assets/icons/file-types/folder-kubernetes.svg new file mode 100644 index 0000000000000000000000000000000000000000..b60d83d85507acd50da9e4a6845ea5196f5c3de7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-kubernetes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-lefthook.svg b/packages/ui/src/assets/icons/file-types/folder-lefthook.svg new file mode 100644 index 0000000000000000000000000000000000000000..0c7eb274c3b3fe27f5a533ec3f8e8e672b3db633 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-lefthook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-less-open.svg b/packages/ui/src/assets/icons/file-types/folder-less-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..3419b0a9d98b78cdec973b6c04029cf8011ca4c8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-less-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-lib-open.svg b/packages/ui/src/assets/icons/file-types/folder-lib-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..8c4443168103e8771ab5be6686c6f2ef6deffe8c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-lib-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-lib.svg b/packages/ui/src/assets/icons/file-types/folder-lib.svg new file mode 100644 index 0000000000000000000000000000000000000000..4e752857aa7b4e745d840f83363e40a970d32b40 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-lib.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-link-open.svg b/packages/ui/src/assets/icons/file-types/folder-link-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..817d0d5880d315661ab8908fe243c51d84216811 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-link-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-link.svg b/packages/ui/src/assets/icons/file-types/folder-link.svg new file mode 100644 index 0000000000000000000000000000000000000000..48a8bbe84499ff22ab6c8dda18ca3f37e9e54e2e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-linux.svg b/packages/ui/src/assets/icons/file-types/folder-linux.svg new file mode 100644 index 0000000000000000000000000000000000000000..df4d2293ef5316f1c46cd4b8ea9c521ca1c187c5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-liquibase-open.svg b/packages/ui/src/assets/icons/file-types/folder-liquibase-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..2fe7ba65f41c978359dfe6063fc09e0b1b70294a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-liquibase-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-liquibase.svg b/packages/ui/src/assets/icons/file-types/folder-liquibase.svg new file mode 100644 index 0000000000000000000000000000000000000000..aea076ac11d10900453b9ebc2bbafb4af013dab0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-liquibase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-log-open.svg b/packages/ui/src/assets/icons/file-types/folder-log-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a78771ea117b7de6e6579fb5c146fabb01a8af7e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-log-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-log.svg b/packages/ui/src/assets/icons/file-types/folder-log.svg new file mode 100644 index 0000000000000000000000000000000000000000..b2ba6a58225d247f37e179be59efa707d0567118 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-lua-open.svg b/packages/ui/src/assets/icons/file-types/folder-lua-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..cb2ea6efb873dd5ca436046decfc56341a9889d6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-lua-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-lua.svg b/packages/ui/src/assets/icons/file-types/folder-lua.svg new file mode 100644 index 0000000000000000000000000000000000000000..e32819b9f51fa6c93ec703aa1bdc9b75a5beb809 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-lua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-luau.svg b/packages/ui/src/assets/icons/file-types/folder-luau.svg new file mode 100644 index 0000000000000000000000000000000000000000..a6b455176032106752ce1ec329ec0d41a089b425 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-luau.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-macos.svg b/packages/ui/src/assets/icons/file-types/folder-macos.svg new file mode 100644 index 0000000000000000000000000000000000000000..6afe2ed2ae10aeda0519d6206740278af0634c36 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-macos.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-mail.svg b/packages/ui/src/assets/icons/file-types/folder-mail.svg new file mode 100644 index 0000000000000000000000000000000000000000..513e4b1beebad9125f928c9d3a25e2f62eb28437 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mappings.svg b/packages/ui/src/assets/icons/file-types/folder-mappings.svg new file mode 100644 index 0000000000000000000000000000000000000000..53b58e05f8cee2905f101281b14f6648462ead75 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mappings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-markdown-open.svg b/packages/ui/src/assets/icons/file-types/folder-markdown-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..75ef9044e397d93f5c8a4b504e5eef4dc8944d3a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-markdown-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mercurial.svg b/packages/ui/src/assets/icons/file-types/folder-mercurial.svg new file mode 100644 index 0000000000000000000000000000000000000000..5175b8ea6ee3518cfb7a4cb57cf3509b4fa9e232 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mercurial.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-meta.svg b/packages/ui/src/assets/icons/file-types/folder-meta.svg new file mode 100644 index 0000000000000000000000000000000000000000..3a1b90addb8e5b9bd1aeabba0167357ae476ce73 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-meta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-middleware-open.svg b/packages/ui/src/assets/icons/file-types/folder-middleware-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..346954c3b26566c1885ee1e61391220bdeb4ad4e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-middleware-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mjml.svg b/packages/ui/src/assets/icons/file-types/folder-mjml.svg new file mode 100644 index 0000000000000000000000000000000000000000..8d7f06703fbb1bb97bde149407f5286d26e93b64 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mjml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mobile-open.svg b/packages/ui/src/assets/icons/file-types/folder-mobile-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..6a5a39b6b2cb481d9798f685fa2b4d13598e67dd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mobile-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mock-open.svg b/packages/ui/src/assets/icons/file-types/folder-mock-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c92929c6a7caac4d4ee28e30c7b04fa4f868c999 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mock-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mojo-open.svg b/packages/ui/src/assets/icons/file-types/folder-mojo-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ce5b9be2d444bf9b56d0f14fb5f9055d1c1b87fa --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mojo-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-mojo.svg b/packages/ui/src/assets/icons/file-types/folder-mojo.svg new file mode 100644 index 0000000000000000000000000000000000000000..67f75375583fd2023a7d429a1189857845a63e81 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-mojo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-molecule-open.svg b/packages/ui/src/assets/icons/file-types/folder-molecule-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..846e2f9deb18fd74813cb262b67b7fd730852e08 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-molecule-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-molecule.svg b/packages/ui/src/assets/icons/file-types/folder-molecule.svg new file mode 100644 index 0000000000000000000000000000000000000000..9c7905ecacf2f1be0dba16d99924bf0e71683ca9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-molecule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-moon.svg b/packages/ui/src/assets/icons/file-types/folder-moon.svg new file mode 100644 index 0000000000000000000000000000000000000000..06613deb5604c0020d9cae023e980c0ca7e4b3b1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-netlify-open.svg b/packages/ui/src/assets/icons/file-types/folder-netlify-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..d6f63b7766df43fb54302d3ebfc358b66922fc99 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-netlify-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-netlify.svg b/packages/ui/src/assets/icons/file-types/folder-netlify.svg new file mode 100644 index 0000000000000000000000000000000000000000..5473f42cd998c4a58015617f6fff26349025c852 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-netlify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ngrx-store-open.svg b/packages/ui/src/assets/icons/file-types/folder-ngrx-store-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..2c8514ef29634f731611ba81a51c9d77368af359 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ngrx-store-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ngrx-store.svg b/packages/ui/src/assets/icons/file-types/folder-ngrx-store.svg new file mode 100644 index 0000000000000000000000000000000000000000..1f9cb2d5992f14efaed9e5fb3f732fa980f728b5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ngrx-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-node-open.svg b/packages/ui/src/assets/icons/file-types/folder-node-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..a785ed3d61eca67dec3e6b2355a386f40cff2803 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-node-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-obsidian.svg b/packages/ui/src/assets/icons/file-types/folder-obsidian.svg new file mode 100644 index 0000000000000000000000000000000000000000..cd16a528a5e305082ea710a8fae616fc18f3d966 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-obsidian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-organism-open.svg b/packages/ui/src/assets/icons/file-types/folder-organism-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..6be44d2b00a16104777a23c1d5f44979d371665c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-organism-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-organism.svg b/packages/ui/src/assets/icons/file-types/folder-organism.svg new file mode 100644 index 0000000000000000000000000000000000000000..50092a098f397b5b76e927e9ff41732765fdbb3e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-organism.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-other-open.svg b/packages/ui/src/assets/icons/file-types/folder-other-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea4144f48e9f4232bad3a525fa62d452c16301ed --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-other-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-packages.svg b/packages/ui/src/assets/icons/file-types/folder-packages.svg new file mode 100644 index 0000000000000000000000000000000000000000..9ba67cb9afcf8f102e97f6108cec0695691a4eac --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-packages.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-pdf-open.svg b/packages/ui/src/assets/icons/file-types/folder-pdf-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fdeccb04b73bb92e299b5644b7acc2240f1421f6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-pdf-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-pdf.svg b/packages/ui/src/assets/icons/file-types/folder-pdf.svg new file mode 100644 index 0000000000000000000000000000000000000000..db0ace7e2b35dc54b9e0c551f5cb9bef429f5593 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-php-open.svg b/packages/ui/src/assets/icons/file-types/folder-php-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..2059a9b90d37bd6ad4e147d27b34d4bb10056dcf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-php-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-php.svg b/packages/ui/src/assets/icons/file-types/folder-php.svg new file mode 100644 index 0000000000000000000000000000000000000000..4304e179d4f72fd796af1411eab9628a969fc08a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-php.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-phpmailer-open.svg b/packages/ui/src/assets/icons/file-types/folder-phpmailer-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..26388bb3fe537cc19253359207c2e196e84af6b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-phpmailer-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-phpmailer.svg b/packages/ui/src/assets/icons/file-types/folder-phpmailer.svg new file mode 100644 index 0000000000000000000000000000000000000000..18f696c1cacc5652a50e4324f34593b075b50e5c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-phpmailer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-plugin-open.svg b/packages/ui/src/assets/icons/file-types/folder-plugin-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5a7f03a4d29413c55cadd1c69ed0c1a44bb23a52 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-plugin-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-policy.svg b/packages/ui/src/assets/icons/file-types/folder-policy.svg new file mode 100644 index 0000000000000000000000000000000000000000..1b1781d59309bb403cb45e2fbc956ed03e5b2f1a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-policy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-powershell-open.svg b/packages/ui/src/assets/icons/file-types/folder-powershell-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..be4b458bec32e74dbc5e08539567a141c4887430 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-powershell-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-prisma-open.svg b/packages/ui/src/assets/icons/file-types/folder-prisma-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..95df8ba0e00e322ce08b24a156bd65275ef33803 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-prisma-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-prisma.svg b/packages/ui/src/assets/icons/file-types/folder-prisma.svg new file mode 100644 index 0000000000000000000000000000000000000000..a166ebd1e889a01a139eda852fc665006be371f6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-prisma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-private-open.svg b/packages/ui/src/assets/icons/file-types/folder-private-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..19094be8940a7c264cf223b66017d9fd306433cb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-private-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-prompts.svg b/packages/ui/src/assets/icons/file-types/folder-prompts.svg new file mode 100644 index 0000000000000000000000000000000000000000..969535bf51924987ed9aa3b6645490ae62a8f79f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-prompts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-proto-open.svg b/packages/ui/src/assets/icons/file-types/folder-proto-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..710de39b5a16924db84a66e25b342414c7cafb4f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-proto-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-proto.svg b/packages/ui/src/assets/icons/file-types/folder-proto.svg new file mode 100644 index 0000000000000000000000000000000000000000..935fcbc5d9e1d81cb5339f015e7f3d0f8e837b55 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-proto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-public.svg b/packages/ui/src/assets/icons/file-types/folder-public.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea599391d6665fd29ed62ef1e9601dbb3a8aac70 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-public.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-python-open.svg b/packages/ui/src/assets/icons/file-types/folder-python-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..dbfc367aaff9cd13b068e09959771a747d471433 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-python-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-python.svg b/packages/ui/src/assets/icons/file-types/folder-python.svg new file mode 100644 index 0000000000000000000000000000000000000000..aae07362e9e72f01f1c9479627a78cacd8cac008 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-python.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-quasar-open.svg b/packages/ui/src/assets/icons/file-types/folder-quasar-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5fb6b928881b44f4a0e6512ab57213b96ea786b2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-quasar-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-queue.svg b/packages/ui/src/assets/icons/file-types/folder-queue.svg new file mode 100644 index 0000000000000000000000000000000000000000..24453040640899db90b0208fd527d2b27dd25099 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-queue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-redux-reducer-open.svg b/packages/ui/src/assets/icons/file-types/folder-redux-reducer-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..838bf52a28b9f241562ccd97e1b614065e7304a0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-redux-reducer-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-redux-reducer.svg b/packages/ui/src/assets/icons/file-types/folder-redux-reducer.svg new file mode 100644 index 0000000000000000000000000000000000000000..a3b441f0147631cd57e523e7fe4fde5e047b71ec --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-redux-reducer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-repository-open.svg b/packages/ui/src/assets/icons/file-types/folder-repository-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9c6275dbbe473a5d2f081a995c7553da893c9d1c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-repository-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-repository.svg b/packages/ui/src/assets/icons/file-types/folder-repository.svg new file mode 100644 index 0000000000000000000000000000000000000000..4f7520651d0bca7f6ab726e068bceb58335e3197 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-repository.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-resolver-open.svg b/packages/ui/src/assets/icons/file-types/folder-resolver-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5a4b752e0c58daea77935395db6343a3af994604 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-resolver-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-resource-open.svg b/packages/ui/src/assets/icons/file-types/folder-resource-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..0f534e10d2ea66741612c7485cce138b5935eb4a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-resource-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-resource.svg b/packages/ui/src/assets/icons/file-types/folder-resource.svg new file mode 100644 index 0000000000000000000000000000000000000000..24a053af6f6c09711898db5752eaf86bffca9fdb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-resource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-review.svg b/packages/ui/src/assets/icons/file-types/folder-review.svg new file mode 100644 index 0000000000000000000000000000000000000000..c7b138caafa8d4c8639781740aa6d325dfdb38ae --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-review.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-robot-open.svg b/packages/ui/src/assets/icons/file-types/folder-robot-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..cd501c4117ffbbe1dfdeb99fa748b60880cb7737 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-robot-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-robot.svg b/packages/ui/src/assets/icons/file-types/folder-robot.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa582f494464897831b08e4b381846375c9e9628 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-robot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-routes-open.svg b/packages/ui/src/assets/icons/file-types/folder-routes-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..c9c875e440649780f76f9ea7ff5c25d2b9b0e6ed --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-routes-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-rules.svg b/packages/ui/src/assets/icons/file-types/folder-rules.svg new file mode 100644 index 0000000000000000000000000000000000000000..baa5b615ead1c5664c79ab3ebbcd0d9a0590bcbc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-rules.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-sandbox-open.svg b/packages/ui/src/assets/icons/file-types/folder-sandbox-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..e0c7a064f0fc8745ef793ac871ead5ba665f93e6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-sandbox-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-scala-open.svg b/packages/ui/src/assets/icons/file-types/folder-scala-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fb4aee7f6622ce3c862859cf4c02b880edd75654 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-scala-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg b/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..981a43f783c1ba74745712aab660f27927a2e437 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-scripts.svg b/packages/ui/src/assets/icons/file-types/folder-scripts.svg new file mode 100644 index 0000000000000000000000000000000000000000..4b755acb9f88d661cb2bd31190d42bd730927ec6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-scripts.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-seeders-open.svg b/packages/ui/src/assets/icons/file-types/folder-seeders-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..b93194091491d6fb7616295890d730951577ffbb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-seeders-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-server.svg b/packages/ui/src/assets/icons/file-types/folder-server.svg new file mode 100644 index 0000000000000000000000000000000000000000..4f03f47265edb3601f6b289d7ded47011401584e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-serverless-open.svg b/packages/ui/src/assets/icons/file-types/folder-serverless-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..113f73c9dfa818d8c4fa6114e38a66e2bfb89dc9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-serverless-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-shader-open.svg b/packages/ui/src/assets/icons/file-types/folder-shader-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..03e00ed5fc322fde37d62aa1891ddaebb9de808e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-shader-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-shared-open.svg b/packages/ui/src/assets/icons/file-types/folder-shared-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..6542e7fe0f4f07a9e6ee2812c8729887714afe1f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-shared-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-snapcraft-open.svg b/packages/ui/src/assets/icons/file-types/folder-snapcraft-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a0306828291524561b146f18155173bbd03ed26 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-snapcraft-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-snapcraft.svg b/packages/ui/src/assets/icons/file-types/folder-snapcraft.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc77b789a03e61c3fc3a5a7fe9bc1c4d1f48e38f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-snapcraft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-snippet-open.svg b/packages/ui/src/assets/icons/file-types/folder-snippet-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..451c291f8719066ba573cf86ad2e30585f728b47 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-snippet-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-snippet.svg b/packages/ui/src/assets/icons/file-types/folder-snippet.svg new file mode 100644 index 0000000000000000000000000000000000000000..991f5c444f0bdd01399da30f169f2fea84ede08d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-snippet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-src-tauri-open.svg b/packages/ui/src/assets/icons/file-types/folder-src-tauri-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..969c5778d312716258176bb057938039e0f3d852 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-src-tauri-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-stack.svg b/packages/ui/src/assets/icons/file-types/folder-stack.svg new file mode 100644 index 0000000000000000000000000000000000000000..9c0b10d52e85eb2ce3e474495d1ffec38809046b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-stack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-stencil.svg b/packages/ui/src/assets/icons/file-types/folder-stencil.svg new file mode 100644 index 0000000000000000000000000000000000000000..c0443c98aa998d6be03b412a2c125bc0f54b7c4e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-stencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-storybook-open.svg b/packages/ui/src/assets/icons/file-types/folder-storybook-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9be24b2e1ee0512b0cfd6aa2db4023a99180a8e9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-storybook-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-sublime-open.svg b/packages/ui/src/assets/icons/file-types/folder-sublime-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5066f3a14ada671fd8bce7fbb97514e41031eda6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-sublime-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-sublime.svg b/packages/ui/src/assets/icons/file-types/folder-sublime.svg new file mode 100644 index 0000000000000000000000000000000000000000..1361eda5fe880948b642034bf56b89ced398ba2b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-sublime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-supabase-open.svg b/packages/ui/src/assets/icons/file-types/folder-supabase-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..d58a6924eda43487a4536445e941914ef01d2911 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-supabase-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-svelte-open.svg b/packages/ui/src/assets/icons/file-types/folder-svelte-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..f72ae2f7e145508a5ea0013a4eaae96a5319a280 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-svelte-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-svelte.svg b/packages/ui/src/assets/icons/file-types/folder-svelte.svg new file mode 100644 index 0000000000000000000000000000000000000000..61bf1d4db29c1431872b8d989f5f77bc123145bf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-svg-open.svg b/packages/ui/src/assets/icons/file-types/folder-svg-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..f8ef72bad3a71f6a1e160589f7175cdfe19f6d5d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-svg-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-syntax-open.svg b/packages/ui/src/assets/icons/file-types/folder-syntax-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..fd9d972b3fe2887691110ce187318edbd52649ea --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-syntax-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-target.svg b/packages/ui/src/assets/icons/file-types/folder-target.svg new file mode 100644 index 0000000000000000000000000000000000000000..5872750bd2471849c37bdbf451b47b52f8e1b7d4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-target.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-taskfile.svg b/packages/ui/src/assets/icons/file-types/folder-taskfile.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a3cac7ad83b235ff2b672083b72e6331806609a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-taskfile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-tasks-open.svg b/packages/ui/src/assets/icons/file-types/folder-tasks-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed0e67f26d386a033f8c44279d9cd2f40b824491 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-tasks-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-temp-open.svg b/packages/ui/src/assets/icons/file-types/folder-temp-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ec798b1e2cfedda1c40d727ac1d109dbee13c3d7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-temp-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-template-open.svg b/packages/ui/src/assets/icons/file-types/folder-template-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..e3f822b7191bb884e68dd54b2f321b455e334f37 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-template-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-terraform.svg b/packages/ui/src/assets/icons/file-types/folder-terraform.svg new file mode 100644 index 0000000000000000000000000000000000000000..e71fba8dc0a063123be3d22a837bdbe1653aa69b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-terraform.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-theme-open.svg b/packages/ui/src/assets/icons/file-types/folder-theme-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..5e79f991eca7e38e20508b1952227b0ec98c7cb5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-theme-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-tools.svg b/packages/ui/src/assets/icons/file-types/folder-tools.svg new file mode 100644 index 0000000000000000000000000000000000000000..d591a1f389349b65c7de737365bbdbc0e3df092d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-tools.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-trash.svg b/packages/ui/src/assets/icons/file-types/folder-trash.svg new file mode 100644 index 0000000000000000000000000000000000000000..1e81d28f99ce4321b6e1c43dda0c4f375b5cb033 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-trigger-open.svg b/packages/ui/src/assets/icons/file-types/folder-trigger-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ecd80d37c05aa74c7767318ea9ecc8a1d0c59be2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-trigger-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg b/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..e0d7c35cff483253cf9043ff888805027477bd06 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/folder-typescript-open.svg b/packages/ui/src/assets/icons/file-types/folder-typescript-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..87c8e2fa1faf3c38ffab03a2b8dc91eac10b3473 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-typescript-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ui-open.svg b/packages/ui/src/assets/icons/file-types/folder-ui-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..3044916989cbfbf2197ecef7a0b325635094f19f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ui-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-ui.svg b/packages/ui/src/assets/icons/file-types/folder-ui.svg new file mode 100644 index 0000000000000000000000000000000000000000..fa320d1014654a8804b725e9375066c7ed4eb0dd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-ui.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-unity-open.svg b/packages/ui/src/assets/icons/file-types/folder-unity-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..cb036d5d1950603ccf1355c6318b67ec64bfd5b3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-unity-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-unity.svg b/packages/ui/src/assets/icons/file-types/folder-unity.svg new file mode 100644 index 0000000000000000000000000000000000000000..c751de29c4f81846c53f208886f4936d965806c6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-unity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-upload-open.svg b/packages/ui/src/assets/icons/file-types/folder-upload-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..24fc35931dcb562036003cf7c93d30ab6d006a23 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-upload-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-upload.svg b/packages/ui/src/assets/icons/file-types/folder-upload.svg new file mode 100644 index 0000000000000000000000000000000000000000..423c6c1174864d5519d06eb261d3034bcae7d82d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-verdaccio-open.svg b/packages/ui/src/assets/icons/file-types/folder-verdaccio-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..24beac52b7d4826c31c9c6cbb8efd0d4242fe261 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-verdaccio-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-video-open.svg b/packages/ui/src/assets/icons/file-types/folder-video-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea60cd047e39d8543a82430546658a1bc4818ade --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-video-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-video.svg b/packages/ui/src/assets/icons/file-types/folder-video.svg new file mode 100644 index 0000000000000000000000000000000000000000..d1385545ac351686ff9c2d03152e71308b0c79f7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-video.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vm-open.svg b/packages/ui/src/assets/icons/file-types/folder-vm-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..e1a2b54cf3ded6d8ef7266bcc2e1432b26b48c7f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vm-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vm.svg b/packages/ui/src/assets/icons/file-types/folder-vm.svg new file mode 100644 index 0000000000000000000000000000000000000000..1ee3a95ddcdb4e98f90f38c5c1ad3fb89e5c10e3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vscode.svg b/packages/ui/src/assets/icons/file-types/folder-vscode.svg new file mode 100644 index 0000000000000000000000000000000000000000..07ccbd6e754fbebe12c9db22ff3f8d1fd6cf19a5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vscode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vue-directives.svg b/packages/ui/src/assets/icons/file-types/folder-vue-directives.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc28ccb852123748f1f8560a5207babf2e5b86b6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vue-directives.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vue-open.svg b/packages/ui/src/assets/icons/file-types/folder-vue-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..03abcafabc748cd8cfa399acd649670aa576ba29 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vue-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vue.svg b/packages/ui/src/assets/icons/file-types/folder-vue.svg new file mode 100644 index 0000000000000000000000000000000000000000..c7cf38e8ac63502be67011fa85f4a7ffbcc30094 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vuex-store-open.svg b/packages/ui/src/assets/icons/file-types/folder-vuex-store-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..77c3c4681f19cccdaba899ae6a4757344fecfe15 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vuex-store-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vuex-store.svg b/packages/ui/src/assets/icons/file-types/folder-vuex-store.svg new file mode 100644 index 0000000000000000000000000000000000000000..5c6793e6d2588e43acb2ed40bb0fb94ee970c095 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-vuex-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-wakatime-open.svg b/packages/ui/src/assets/icons/file-types/folder-wakatime-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..d1dbc384567dcb8c8725b9c20597b530f2b639c6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-wakatime-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-wakatime.svg b/packages/ui/src/assets/icons/file-types/folder-wakatime.svg new file mode 100644 index 0000000000000000000000000000000000000000..860a661ee6a4feee7ef3037dedba4455cd5fe70f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-wakatime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-windows-open.svg b/packages/ui/src/assets/icons/file-types/folder-windows-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..9173ff9cbcb7292caa8e8239e98678ae34cddbc2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-windows-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-wordpress.svg b/packages/ui/src/assets/icons/file-types/folder-wordpress.svg new file mode 100644 index 0000000000000000000000000000000000000000..a954a2b99add349f921091c106e2dff043645a03 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-wordpress.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-yarn.svg b/packages/ui/src/assets/icons/file-types/folder-yarn.svg new file mode 100644 index 0000000000000000000000000000000000000000..58aee64b81a6530bf94d9f70446da9167012de11 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-yarn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-zeabur-open.svg b/packages/ui/src/assets/icons/file-types/folder-zeabur-open.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac2a31a3e02debf6a040ffcd461c475e4c7c3f33 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder-zeabur-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder.svg b/packages/ui/src/assets/icons/file-types/folder.svg new file mode 100644 index 0000000000000000000000000000000000000000..97ee81ca430571d6ab8791e4c37ef3720661b36a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/folder.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/ui/src/assets/icons/file-types/font.svg b/packages/ui/src/assets/icons/file-types/font.svg new file mode 100644 index 0000000000000000000000000000000000000000..961586d9de515dfd1b1e5865f817c56bc0732892 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/forth.svg b/packages/ui/src/assets/icons/file-types/forth.svg new file mode 100644 index 0000000000000000000000000000000000000000..50b66af6ec9c88b571728f433ed90ab727b61cd1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/forth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/freemarker.svg b/packages/ui/src/assets/icons/file-types/freemarker.svg new file mode 100644 index 0000000000000000000000000000000000000000..edf98f6df53007cf61bb2c875448206f74315603 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/freemarker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gamemaker.svg b/packages/ui/src/assets/icons/file-types/gamemaker.svg new file mode 100644 index 0000000000000000000000000000000000000000..4097cdd7ff558accfe16705783d85d68fe43a0ef --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gamemaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/garden.svg b/packages/ui/src/assets/icons/file-types/garden.svg new file mode 100644 index 0000000000000000000000000000000000000000..a96386d60906580173e1c938acfda228fa72eb02 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/garden.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gemfile.svg b/packages/ui/src/assets/icons/file-types/gemfile.svg new file mode 100644 index 0000000000000000000000000000000000000000..757c89d167eec0991d77fcd281585b4ef8bc1305 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gemfile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gemini-ai.svg b/packages/ui/src/assets/icons/file-types/gemini-ai.svg new file mode 100644 index 0000000000000000000000000000000000000000..0911694b10b7c6d37ef728ced0348c11566dcc8d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gemini-ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gemini.svg b/packages/ui/src/assets/icons/file-types/gemini.svg new file mode 100644 index 0000000000000000000000000000000000000000..79ad4bf5ff15176307e522bf9b783c9071fa66b2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gemini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/github-sponsors.svg b/packages/ui/src/assets/icons/file-types/github-sponsors.svg new file mode 100644 index 0000000000000000000000000000000000000000..72fb66811cb7520c5cd927d945e88f34404c10e8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/github-sponsors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gitpod.svg b/packages/ui/src/assets/icons/file-types/gitpod.svg new file mode 100644 index 0000000000000000000000000000000000000000..a992017ec29c198e71d6fe442a95ac9246e6fd42 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gitpod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gnuplot.svg b/packages/ui/src/assets/icons/file-types/gnuplot.svg new file mode 100644 index 0000000000000000000000000000000000000000..8cc510b5338b7c54291191a2ba5be1dcb6386331 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gnuplot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/go-mod.svg b/packages/ui/src/assets/icons/file-types/go-mod.svg new file mode 100644 index 0000000000000000000000000000000000000000..1689116be5689798d0e12a3e899b50088117d0ac --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/go-mod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/go.svg b/packages/ui/src/assets/icons/file-types/go.svg new file mode 100644 index 0000000000000000000000000000000000000000..d874e329e13ecad9d23999613c1f5933c7b43a93 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/go.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/go_gopher.svg b/packages/ui/src/assets/icons/file-types/go_gopher.svg new file mode 100644 index 0000000000000000000000000000000000000000..e465f745613724296d63b167171c1300654663ab --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/go_gopher.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/godot.svg b/packages/ui/src/assets/icons/file-types/godot.svg new file mode 100644 index 0000000000000000000000000000000000000000..4b1dd7fc284907045bd728a6b65cca13faafc513 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/godot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gradle.svg b/packages/ui/src/assets/icons/file-types/gradle.svg new file mode 100644 index 0000000000000000000000000000000000000000..72d88fdbbe738df12c59105cbc31030312827b39 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gradle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/grafana-alloy.svg b/packages/ui/src/assets/icons/file-types/grafana-alloy.svg new file mode 100644 index 0000000000000000000000000000000000000000..cf00031260850dd47951b691856d0283fc40c1d0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/grafana-alloy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/graphcool.svg b/packages/ui/src/assets/icons/file-types/graphcool.svg new file mode 100644 index 0000000000000000000000000000000000000000..bdaedb9d5711dce9159021601a1a612c8e3842dd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/graphcool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/graphql.svg b/packages/ui/src/assets/icons/file-types/graphql.svg new file mode 100644 index 0000000000000000000000000000000000000000..252b0f7342dc67229008c29a8492ad5e5bf5a568 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/graphql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gridsome.svg b/packages/ui/src/assets/icons/file-types/gridsome.svg new file mode 100644 index 0000000000000000000000000000000000000000..872774100144af30a3e2a7961f9838994a7c3966 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gridsome.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/groovy.svg b/packages/ui/src/assets/icons/file-types/groovy.svg new file mode 100644 index 0000000000000000000000000000000000000000..9af0c08f4ba9707c5384f6f14b188497aab951c6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/groovy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/grunt.svg b/packages/ui/src/assets/icons/file-types/grunt.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b1499453c95566ff1f5c34a90e8bab87f422e87 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/grunt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/gulp.svg b/packages/ui/src/assets/icons/file-types/gulp.svg new file mode 100644 index 0000000000000000000000000000000000000000..bc6a77ffdd2142733b8c4525cf6928e75a3aa860 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/gulp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/h.svg b/packages/ui/src/assets/icons/file-types/h.svg new file mode 100644 index 0000000000000000000000000000000000000000..08db8fe482ac15b2bf91279381fc1ed712fe1b19 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/h.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/hadolint.svg b/packages/ui/src/assets/icons/file-types/hadolint.svg new file mode 100644 index 0000000000000000000000000000000000000000..26195f506a11724f01d36d6db2872c317e06f1b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/hadolint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/handlebars.svg b/packages/ui/src/assets/icons/file-types/handlebars.svg new file mode 100644 index 0000000000000000000000000000000000000000..cf8993005c95c7f61e8ec4db64e55859f8459b30 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/handlebars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/harmonix.svg b/packages/ui/src/assets/icons/file-types/harmonix.svg new file mode 100644 index 0000000000000000000000000000000000000000..299fa478c45a21fce42eb9ad9f6b87b679c75cda --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/harmonix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/haxe.svg b/packages/ui/src/assets/icons/file-types/haxe.svg new file mode 100644 index 0000000000000000000000000000000000000000..cb28364f37f7c16f4775ba65260bef889b760a06 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/haxe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/hcl_light.svg b/packages/ui/src/assets/icons/file-types/hcl_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..0196914602d38bc02ccaea69601f0cc026273b3a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/hcl_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/hex.svg b/packages/ui/src/assets/icons/file-types/hex.svg new file mode 100644 index 0000000000000000000000000000000000000000..e50c6771cd0f48ca55c4dd8f574eecf1c8a718c1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/hex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/histoire.svg b/packages/ui/src/assets/icons/file-types/histoire.svg new file mode 100644 index 0000000000000000000000000000000000000000..5619c16da0fa0735aa9ffc9d41f2d56ef000f5f3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/histoire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/hjson.svg b/packages/ui/src/assets/icons/file-types/hjson.svg new file mode 100644 index 0000000000000000000000000000000000000000..7725feb781f956906b1fa915eb376347b36645a9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/hjson.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/horusec.svg b/packages/ui/src/assets/icons/file-types/horusec.svg new file mode 100644 index 0000000000000000000000000000000000000000..9ea1155e4ca5b34992ca5d1334e3e77db80ea3c3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/horusec.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/html.svg b/packages/ui/src/assets/icons/file-types/html.svg new file mode 100644 index 0000000000000000000000000000000000000000..71caf32b6bbff61cfe1ba0b5bfceabe6ab30b088 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/html.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/husky.svg b/packages/ui/src/assets/icons/file-types/husky.svg new file mode 100644 index 0000000000000000000000000000000000000000..b48f06a695dd48c0d095a5aa2ad0e9796d0438b4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/husky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/idris.svg b/packages/ui/src/assets/icons/file-types/idris.svg new file mode 100644 index 0000000000000000000000000000000000000000..445745b68d3931831bbf14605ee445167b5626e4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/idris.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/installation.svg b/packages/ui/src/assets/icons/file-types/installation.svg new file mode 100644 index 0000000000000000000000000000000000000000..36fa21cf007894f05d5975dfca9b1a7cd2dde1be --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/installation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ionic.svg b/packages/ui/src/assets/icons/file-types/ionic.svg new file mode 100644 index 0000000000000000000000000000000000000000..2ce630d01126b2c94c6bb3e689a98205bdd4ea75 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ionic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/istanbul.svg b/packages/ui/src/assets/icons/file-types/istanbul.svg new file mode 100644 index 0000000000000000000000000000000000000000..9508a981a2eff7df78205ff81d901baacb1e3f58 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/istanbul.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/javaclass.svg b/packages/ui/src/assets/icons/file-types/javaclass.svg new file mode 100644 index 0000000000000000000000000000000000000000..9abe7ad80677d2a26b6eb290a6ac4fbea2b9ec6f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/javaclass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/javascript.svg b/packages/ui/src/assets/icons/file-types/javascript.svg new file mode 100644 index 0000000000000000000000000000000000000000..254704ab7a3166c9c745c374b306f72e59330fd2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/jenkins.svg b/packages/ui/src/assets/icons/file-types/jenkins.svg new file mode 100644 index 0000000000000000000000000000000000000000..1517b746ecd87e1d8235767775da756ce3ceb86f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/jenkins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/jest.svg b/packages/ui/src/assets/icons/file-types/jest.svg new file mode 100644 index 0000000000000000000000000000000000000000..fb40ecb37cf64f8adfecd8381b2ba1c835b10f83 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/jest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/jinja.svg b/packages/ui/src/assets/icons/file-types/jinja.svg new file mode 100644 index 0000000000000000000000000000000000000000..8163f2a1b0af7539292aa30cb5f95957d0541459 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/jinja.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/kl.svg b/packages/ui/src/assets/icons/file-types/kl.svg new file mode 100644 index 0000000000000000000000000000000000000000..967ef09e94048baad34d8644027cf3ec48e385ff --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/kl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/knip.svg b/packages/ui/src/assets/icons/file-types/knip.svg new file mode 100644 index 0000000000000000000000000000000000000000..c71d0a20dc945048f8e3d8ef48bf3f8ca81fbd95 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/knip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/kusto.svg b/packages/ui/src/assets/icons/file-types/kusto.svg new file mode 100644 index 0000000000000000000000000000000000000000..46087e83cf4d25463c8157ba2baf4309f47cc61a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/kusto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/latexmk.svg b/packages/ui/src/assets/icons/file-types/latexmk.svg new file mode 100644 index 0000000000000000000000000000000000000000..484318aafa561c2d08049e937603f589f49a1cbf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/latexmk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/lefthook.svg b/packages/ui/src/assets/icons/file-types/lefthook.svg new file mode 100644 index 0000000000000000000000000000000000000000..93f6f81b614cefd923c2eda824785b6534007e5e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/lefthook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/lib.svg b/packages/ui/src/assets/icons/file-types/lib.svg new file mode 100644 index 0000000000000000000000000000000000000000..7c8fda3e020e74b38a54ff04f2933accaceb44fb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/lib.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/liquid.svg b/packages/ui/src/assets/icons/file-types/liquid.svg new file mode 100644 index 0000000000000000000000000000000000000000..5111ab67f67ac4c71066b199506a07147eb26355 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/liquid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/log.svg b/packages/ui/src/assets/icons/file-types/log.svg new file mode 100644 index 0000000000000000000000000000000000000000..389f51d32d842fdbc809714c62ce8407e13ab05b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/lolcode.svg b/packages/ui/src/assets/icons/file-types/lolcode.svg new file mode 100644 index 0000000000000000000000000000000000000000..f9c7595812e969013e0874a3b057005e83b92877 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/lolcode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/lottie.svg b/packages/ui/src/assets/icons/file-types/lottie.svg new file mode 100644 index 0000000000000000000000000000000000000000..29981d323b766f723899d7bb654baeb8a8ec1d2d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/lottie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/lua.svg b/packages/ui/src/assets/icons/file-types/lua.svg new file mode 100644 index 0000000000000000000000000000000000000000..ca7a3d5ddbcdb9c6c69bb3c5e027f7982ada52bf --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/lua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/luau.svg b/packages/ui/src/assets/icons/file-types/luau.svg new file mode 100644 index 0000000000000000000000000000000000000000..7f9ad5768cc28f5722866a637c026b4457f3846a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/luau.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/makefile.svg b/packages/ui/src/assets/icons/file-types/makefile.svg new file mode 100644 index 0000000000000000000000000000000000000000..e211671c507ea978081b1770c04994970e474998 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/makefile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/markdoc.svg b/packages/ui/src/assets/icons/file-types/markdoc.svg new file mode 100644 index 0000000000000000000000000000000000000000..3ed2c54b7de5760f404f6a23cd1f99ba3f30600c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/markdoc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/markdown.svg b/packages/ui/src/assets/icons/file-types/markdown.svg new file mode 100644 index 0000000000000000000000000000000000000000..4c224341c02a0b0a50c980859a3d6e120cbb8b05 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/markdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/markdownlint.svg b/packages/ui/src/assets/icons/file-types/markdownlint.svg new file mode 100644 index 0000000000000000000000000000000000000000..37daf0d27a1086c4a9cd217cf6001323d4889e7c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/markdownlint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/markojs.svg b/packages/ui/src/assets/icons/file-types/markojs.svg new file mode 100644 index 0000000000000000000000000000000000000000..938b6fee872dcf88c4dac8e40b7b331e5ab3208b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/markojs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/matlab.svg b/packages/ui/src/assets/icons/file-types/matlab.svg new file mode 100644 index 0000000000000000000000000000000000000000..a2166f81e3a20cb16e30995afb4e6c23bf65af41 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/matlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/maven.svg b/packages/ui/src/assets/icons/file-types/maven.svg new file mode 100644 index 0000000000000000000000000000000000000000..7a8874502da5272a61f213df65d625ca35617f4d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/maven.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/merlin.svg b/packages/ui/src/assets/icons/file-types/merlin.svg new file mode 100644 index 0000000000000000000000000000000000000000..96b29d3fd229cd59e00c901d77a152a43ee8fae2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/merlin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/meson.svg b/packages/ui/src/assets/icons/file-types/meson.svg new file mode 100644 index 0000000000000000000000000000000000000000..f9d3bef4a61da9e561275d80e6d0d3c669fe773c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/meson.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/minecraft.svg b/packages/ui/src/assets/icons/file-types/minecraft.svg new file mode 100644 index 0000000000000000000000000000000000000000..219af8ae411b96ff674f380acf3248b53777b985 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/minecraft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/mocha.svg b/packages/ui/src/assets/icons/file-types/mocha.svg new file mode 100644 index 0000000000000000000000000000000000000000..bce8ac3b5e7b015a3cb560ee9ae6b980b87476b9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/mocha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/moon.svg b/packages/ui/src/assets/icons/file-types/moon.svg new file mode 100644 index 0000000000000000000000000000000000000000..c428ebb0e28516ae413cadaba7020a737de229c6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nano-staged_light.svg b/packages/ui/src/assets/icons/file-types/nano-staged_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..698232f0c229c73b6fad933e0a10ab7f5765826a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/nano-staged_light.svg @@ -0,0 +1,4 @@ + + + diff --git a/packages/ui/src/assets/icons/file-types/netlify_light.svg b/packages/ui/src/assets/icons/file-types/netlify_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..b142c486f9a2e468802e51d3277d4b3776c7839f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/netlify_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/next.svg b/packages/ui/src/assets/icons/file-types/next.svg new file mode 100644 index 0000000000000000000000000000000000000000..83fee37c1b041cc2a3ad049a9ffe8fb0bcd9fd38 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nginx.svg b/packages/ui/src/assets/icons/file-types/nginx.svg new file mode 100644 index 0000000000000000000000000000000000000000..658ad228f0daf5298394113131fe833e306b5374 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/nginx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-actions.svg b/packages/ui/src/assets/icons/file-types/ngrx-actions.svg new file mode 100644 index 0000000000000000000000000000000000000000..de418d48d9c41612394795955805438fb7704663 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-actions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-effects.svg b/packages/ui/src/assets/icons/file-types/ngrx-effects.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f7dc89ddb956083a168e78b7245d4f9410d1fb0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-effects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-entity.svg b/packages/ui/src/assets/icons/file-types/ngrx-entity.svg new file mode 100644 index 0000000000000000000000000000000000000000..af0dd0563637b89d162bc19284b2bf2320c6188b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-entity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-reducer.svg b/packages/ui/src/assets/icons/file-types/ngrx-reducer.svg new file mode 100644 index 0000000000000000000000000000000000000000..db7a5534a86675583d9820945efc4030aee468fe --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-reducer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-selectors.svg b/packages/ui/src/assets/icons/file-types/ngrx-selectors.svg new file mode 100644 index 0000000000000000000000000000000000000000..af03c40ffc9621fa021555e2c0d07cc416440f9a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-selectors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/ngrx-state.svg b/packages/ui/src/assets/icons/file-types/ngrx-state.svg new file mode 100644 index 0000000000000000000000000000000000000000..258c0ac87175733757e39ad1e4695fa8afc8d80e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/ngrx-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nodejs_alt.svg b/packages/ui/src/assets/icons/file-types/nodejs_alt.svg new file mode 100644 index 0000000000000000000000000000000000000000..5b70be2d0ecda53c3b4ba7fe1ef3884c91d9c09f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/nodejs_alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nodemon.svg b/packages/ui/src/assets/icons/file-types/nodemon.svg new file mode 100644 index 0000000000000000000000000000000000000000..2bd35d1c237e95bf981840ef5e3d95da99830e89 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/nodemon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nunjucks.svg b/packages/ui/src/assets/icons/file-types/nunjucks.svg new file mode 100644 index 0000000000000000000000000000000000000000..9fb8890e211025a6b873d663f2c583289ef8a43c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/nunjucks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/odin.svg b/packages/ui/src/assets/icons/file-types/odin.svg new file mode 100644 index 0000000000000000000000000000000000000000..1877a6cfcd64cc6208a5daa21c35cead7afc9222 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/odin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/opa.svg b/packages/ui/src/assets/icons/file-types/opa.svg new file mode 100644 index 0000000000000000000000000000000000000000..3afc1c6bf457f0c246e68d173856d512c6864f2f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/opa.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/openapi.svg b/packages/ui/src/assets/icons/file-types/openapi.svg new file mode 100644 index 0000000000000000000000000000000000000000..5b367a16864b78d9db8c62753c8cbb406e2300fd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/openapi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/openapi_light.svg b/packages/ui/src/assets/icons/file-types/openapi_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..179006db82f4ed8b18cf882ef85d730858a95836 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/openapi_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/packship.svg b/packages/ui/src/assets/icons/file-types/packship.svg new file mode 100644 index 0000000000000000000000000000000000000000..e03b35d66a2941dd790d0d8f063a4a79386a30b7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/packship.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/panda.svg b/packages/ui/src/assets/icons/file-types/panda.svg new file mode 100644 index 0000000000000000000000000000000000000000..dde4122bbe369949d9b98c3f8fedf889f17938fc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/panda.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/parcel.svg b/packages/ui/src/assets/icons/file-types/parcel.svg new file mode 100644 index 0000000000000000000000000000000000000000..39a1835f16de011325ea43ea6bfdab9499163888 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/parcel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pawn.svg b/packages/ui/src/assets/icons/file-types/pawn.svg new file mode 100644 index 0000000000000000000000000000000000000000..b615d75d12b671e01eb8ca1d0600d637f43ea5a3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pawn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pdf.svg b/packages/ui/src/assets/icons/file-types/pdf.svg new file mode 100644 index 0000000000000000000000000000000000000000..1c84fe82d6b39bccd3a49325e6fae19477699e3a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pdm.svg b/packages/ui/src/assets/icons/file-types/pdm.svg new file mode 100644 index 0000000000000000000000000000000000000000..dd23bb3430de2392851c6681b7102551a6248d7c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pdm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/php-cs-fixer.svg b/packages/ui/src/assets/icons/file-types/php-cs-fixer.svg new file mode 100644 index 0000000000000000000000000000000000000000..398c2145b84acdc0700fdc09a518a4ee3d377f9e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/php-cs-fixer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/php.svg b/packages/ui/src/assets/icons/file-types/php.svg new file mode 100644 index 0000000000000000000000000000000000000000..1d7e3365e628a0ad5a2dffb2bc72a1afb7f3da9d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/php.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/php_elephant.svg b/packages/ui/src/assets/icons/file-types/php_elephant.svg new file mode 100644 index 0000000000000000000000000000000000000000..d2c2995b61da3e0adff255a3368f5ba7da03f31a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/php_elephant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/php_elephant_pink.svg b/packages/ui/src/assets/icons/file-types/php_elephant_pink.svg new file mode 100644 index 0000000000000000000000000000000000000000..a7cad749387b40307204163489c597fa31881e29 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/php_elephant_pink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pnpm.svg b/packages/ui/src/assets/icons/file-types/pnpm.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc52c6ed9d3dcd29df386eeb11c1ca6c63ff4061 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pnpm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pnpm_light.svg b/packages/ui/src/assets/icons/file-types/pnpm_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..4236956cc59ba234b9392c293702c0657d2cd1f0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pnpm_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/poetry.svg b/packages/ui/src/assets/icons/file-types/poetry.svg new file mode 100644 index 0000000000000000000000000000000000000000..4a355a7ef59dd4f47dae0217bdfe9507407ca521 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/poetry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/postcss.svg b/packages/ui/src/assets/icons/file-types/postcss.svg new file mode 100644 index 0000000000000000000000000000000000000000..799edebc9248cab3ca79a3cfa765d87dcb119b4a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/postcss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/posthtml.svg b/packages/ui/src/assets/icons/file-types/posthtml.svg new file mode 100644 index 0000000000000000000000000000000000000000..54dda3c6a0756ca12fb748e8647d82760a9ac9d1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/posthtml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/powershell.svg b/packages/ui/src/assets/icons/file-types/powershell.svg new file mode 100644 index 0000000000000000000000000000000000000000..a2663936da66b6a9b921ca4ae11cdf0044ab66ae --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/powershell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/prisma.svg b/packages/ui/src/assets/icons/file-types/prisma.svg new file mode 100644 index 0000000000000000000000000000000000000000..121abea2246bc9d11ca1affae855c48293dc181f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/prisma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/proto.svg b/packages/ui/src/assets/icons/file-types/proto.svg new file mode 100644 index 0000000000000000000000000000000000000000..7757c0e5f663577c1955d05784cb3777a6248fed --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/proto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/protractor.svg b/packages/ui/src/assets/icons/file-types/protractor.svg new file mode 100644 index 0000000000000000000000000000000000000000..50f464394d8ff8e003bec9e47dc8f85be16f42d8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/protractor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/purescript.svg b/packages/ui/src/assets/icons/file-types/purescript.svg new file mode 100644 index 0000000000000000000000000000000000000000..d82c8f9d827c9d0e034d8584897575beabfb417b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/purescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/pytorch.svg b/packages/ui/src/assets/icons/file-types/pytorch.svg new file mode 100644 index 0000000000000000000000000000000000000000..4cb85d0125a5880912cacf24d57ec667833e3c6f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/pytorch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/qsharp.svg b/packages/ui/src/assets/icons/file-types/qsharp.svg new file mode 100644 index 0000000000000000000000000000000000000000..de9838d6044865cc0e1c7189b8314c8192bace20 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/qsharp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/quarto.svg b/packages/ui/src/assets/icons/file-types/quarto.svg new file mode 100644 index 0000000000000000000000000000000000000000..3bb8ef7c7a7151d95abed25f900b668e04c0a961 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/quarto.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/assets/icons/file-types/qwik.svg b/packages/ui/src/assets/icons/file-types/qwik.svg new file mode 100644 index 0000000000000000000000000000000000000000..555511694c7018214bef4beff8ca51281be5db2b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/qwik.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/racket.svg b/packages/ui/src/assets/icons/file-types/racket.svg new file mode 100644 index 0000000000000000000000000000000000000000..04ca144ba3476824ed07adcf3cd9ac894ccf0a0e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/racket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/raml.svg b/packages/ui/src/assets/icons/file-types/raml.svg new file mode 100644 index 0000000000000000000000000000000000000000..d35d561b9a6f5f583b779ab40697436f42daf12e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/raml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rbxmk.svg b/packages/ui/src/assets/icons/file-types/rbxmk.svg new file mode 100644 index 0000000000000000000000000000000000000000..e7d495376c966949ec9e36b44382f5ea162857cb --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/rbxmk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rc.svg b/packages/ui/src/assets/icons/file-types/rc.svg new file mode 100644 index 0000000000000000000000000000000000000000..83040dbc91737b09ea59f88b9607478912142273 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/rc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/react_ts.svg b/packages/ui/src/assets/icons/file-types/react_ts.svg new file mode 100644 index 0000000000000000000000000000000000000000..887f72ca12fadd5c09f7c9e8c00bf68eff27af17 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/react_ts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/reason.svg b/packages/ui/src/assets/icons/file-types/reason.svg new file mode 100644 index 0000000000000000000000000000000000000000..0f4b3e1f62810b44284b7b22bf372cb156c6c4a8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/reason.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/redux-store.svg b/packages/ui/src/assets/icons/file-types/redux-store.svg new file mode 100644 index 0000000000000000000000000000000000000000..8e644e771c8b689522a9a0a0f2009c8f111b72a9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/redux-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/regedit.svg b/packages/ui/src/assets/icons/file-types/regedit.svg new file mode 100644 index 0000000000000000000000000000000000000000..3d6320607625affc2d60a8ada6eda701775d3ea6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/regedit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/remix.svg b/packages/ui/src/assets/icons/file-types/remix.svg new file mode 100644 index 0000000000000000000000000000000000000000..763f57ff47cc0ff13034dbfe5ef55df64262e194 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/remix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/remix_light.svg b/packages/ui/src/assets/icons/file-types/remix_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..748b779284ca25fdf7989ac4e57154ffbaceed1f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/remix_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/renovate.svg b/packages/ui/src/assets/icons/file-types/renovate.svg new file mode 100644 index 0000000000000000000000000000000000000000..bc63cbb0ca8a2a320265f75d804663ada1f73d6d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/renovate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/replit.svg b/packages/ui/src/assets/icons/file-types/replit.svg new file mode 100644 index 0000000000000000000000000000000000000000..f1478a5036c2c6546fdcfb3e399a59861900cded --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/replit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/riot.svg b/packages/ui/src/assets/icons/file-types/riot.svg new file mode 100644 index 0000000000000000000000000000000000000000..587e50dfc06d9fc0b98a6bd196214ee61d902c7b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/riot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/roblox.svg b/packages/ui/src/assets/icons/file-types/roblox.svg new file mode 100644 index 0000000000000000000000000000000000000000..56cc378477c9714bc2c277af78d9eaaacdbf4d1b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/roblox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/robots.svg b/packages/ui/src/assets/icons/file-types/robots.svg new file mode 100644 index 0000000000000000000000000000000000000000..11fdaaea45c5a9dcf5b2b92f13010822fdee0b8e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/robots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rocket.svg b/packages/ui/src/assets/icons/file-types/rocket.svg new file mode 100644 index 0000000000000000000000000000000000000000..5f62f322c0aeb50c5ae09355d4d37558d6dfa3b2 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/rocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rollup.svg b/packages/ui/src/assets/icons/file-types/rollup.svg new file mode 100644 index 0000000000000000000000000000000000000000..7fa015324558f11168efaf09ac110f7d87b6bf57 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/rollup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/routing.svg b/packages/ui/src/assets/icons/file-types/routing.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea02c905419356660f6d4f96ab5c2bb2e8958d38 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/routing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rspec.svg b/packages/ui/src/assets/icons/file-types/rspec.svg new file mode 100644 index 0000000000000000000000000000000000000000..c1bf424d6de1a1ee5fe5d4620475fb3809131fc8 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/rspec.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/salesforce.svg b/packages/ui/src/assets/icons/file-types/salesforce.svg new file mode 100644 index 0000000000000000000000000000000000000000..80e1aa99e1a565d9068a9f0b741b29e7fe78b72a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/salesforce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/san.svg b/packages/ui/src/assets/icons/file-types/san.svg new file mode 100644 index 0000000000000000000000000000000000000000..d17b9faf6c348d0ae05107c8fedac81ec23a37bc --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/san.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/screwdriver.svg b/packages/ui/src/assets/icons/file-types/screwdriver.svg new file mode 100644 index 0000000000000000000000000000000000000000..cac820631e919d249bc6ee339be0e40bf4b1358b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/screwdriver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/semantic-release_light.svg b/packages/ui/src/assets/icons/file-types/semantic-release_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..21e42a09562de0b43dbcef81ca06e7abd0765021 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/semantic-release_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/shader.svg b/packages/ui/src/assets/icons/file-types/shader.svg new file mode 100644 index 0000000000000000000000000000000000000000..f42156ed8f417bb7c258722d02ec4b0dc0352eda --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/shader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/silverstripe.svg b/packages/ui/src/assets/icons/file-types/silverstripe.svg new file mode 100644 index 0000000000000000000000000000000000000000..46cdb7ee137d8c818531c2025b7f92168753bdd0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/silverstripe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/simulink.svg b/packages/ui/src/assets/icons/file-types/simulink.svg new file mode 100644 index 0000000000000000000000000000000000000000..33e97fe42a3ff8a926462c4156e91ed21362472f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/simulink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/sketch.svg b/packages/ui/src/assets/icons/file-types/sketch.svg new file mode 100644 index 0000000000000000000000000000000000000000..0d75406904d89dcd2f638664e77c03be2e9c2ae5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/sketch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/slim.svg b/packages/ui/src/assets/icons/file-types/slim.svg new file mode 100644 index 0000000000000000000000000000000000000000..edc72417354a4c0c3231a26addded9673b93463d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/slim.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/slint.svg b/packages/ui/src/assets/icons/file-types/slint.svg new file mode 100644 index 0000000000000000000000000000000000000000..b6434ec97e4fa8592b3e75fb6a2c021051bdc36e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/slint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/snakemake.svg b/packages/ui/src/assets/icons/file-types/snakemake.svg new file mode 100644 index 0000000000000000000000000000000000000000..6dd08c958bfb308a1ec4e5da1bca7539342c02f0 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/snakemake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/snapcraft.svg b/packages/ui/src/assets/icons/file-types/snapcraft.svg new file mode 100644 index 0000000000000000000000000000000000000000..17bf8d8d9f62d38ad3484e90e67b71e5b72503b9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/snapcraft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/snowpack_light.svg b/packages/ui/src/assets/icons/file-types/snowpack_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..70389d20f32b7f045bdb3d997e31039864218491 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/snowpack_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/sonarcloud.svg b/packages/ui/src/assets/icons/file-types/sonarcloud.svg new file mode 100644 index 0000000000000000000000000000000000000000..ee989616188328e3f8d1755b94ae3fbe38535870 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/sonarcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/spwn.svg b/packages/ui/src/assets/icons/file-types/spwn.svg new file mode 100644 index 0000000000000000000000000000000000000000..8a8bf43471cfcb0e37bf4430e54a0a3be630a803 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/spwn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stan.svg b/packages/ui/src/assets/icons/file-types/stan.svg new file mode 100644 index 0000000000000000000000000000000000000000..bb5cf6723fbf1a0dcf02881e55c77ee4fddf6699 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/stan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stencil.svg b/packages/ui/src/assets/icons/file-types/stencil.svg new file mode 100644 index 0000000000000000000000000000000000000000..bf8f3ea19d82cf8b06fbe237403542879043285f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/stencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/storybook.svg b/packages/ui/src/assets/icons/file-types/storybook.svg new file mode 100644 index 0000000000000000000000000000000000000000..8a4bdeaa7260385bfc2c6b442f9d277c251b8d4a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/storybook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stylable.svg b/packages/ui/src/assets/icons/file-types/stylable.svg new file mode 100644 index 0000000000000000000000000000000000000000..be5522619440084f261cfc970ecabb0abbd30152 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/stylable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stylelint_light.svg b/packages/ui/src/assets/icons/file-types/stylelint_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..502fec33f14e547e1c45b6a1c6948443f8b383b9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/stylelint_light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/file-types/stylus.svg b/packages/ui/src/assets/icons/file-types/stylus.svg new file mode 100644 index 0000000000000000000000000000000000000000..ae61b48dda61d7856c0e6e6e9c01bcbf82fde6b3 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/stylus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/sublime.svg b/packages/ui/src/assets/icons/file-types/sublime.svg new file mode 100644 index 0000000000000000000000000000000000000000..5c99fb9bec8ba3ba63fbabbe6426c28ea374846d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/sublime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/svg.svg b/packages/ui/src/assets/icons/file-types/svg.svg new file mode 100644 index 0000000000000000000000000000000000000000..fbaf9e59bf0fdaf406fb05fefd18aee95aace304 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/svgr.svg b/packages/ui/src/assets/icons/file-types/svgr.svg new file mode 100644 index 0000000000000000000000000000000000000000..01443226e3590da8ecf116f5656251525ebdca2c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/svgr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/swc.svg b/packages/ui/src/assets/icons/file-types/swc.svg new file mode 100644 index 0000000000000000000000000000000000000000..5931bd3a1cad93f82442c6c3ef2727587395a852 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/swc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/swift.svg b/packages/ui/src/assets/icons/file-types/swift.svg new file mode 100644 index 0000000000000000000000000000000000000000..df413c843e427a6250a8d43a062f8cb8903ad972 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/swift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/tailwindcss.svg b/packages/ui/src/assets/icons/file-types/tailwindcss.svg new file mode 100644 index 0000000000000000000000000000000000000000..a55450d0b93cefefaa6073a5cc7ab6f679c1506e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/tailwindcss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/tauri.svg b/packages/ui/src/assets/icons/file-types/tauri.svg new file mode 100644 index 0000000000000000000000000000000000000000..2c7aa2656ef44f62fb277563e99ec85501530cc7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/tauri.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/teal.svg b/packages/ui/src/assets/icons/file-types/teal.svg new file mode 100644 index 0000000000000000000000000000000000000000..770b63d7b548d9c492d064e22b1725382ab86181 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/teal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/template.svg b/packages/ui/src/assets/icons/file-types/template.svg new file mode 100644 index 0000000000000000000000000000000000000000..604a6f86e2a2287d5915a4863b1f528afa3cb187 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/template.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/test-js.svg b/packages/ui/src/assets/icons/file-types/test-js.svg new file mode 100644 index 0000000000000000000000000000000000000000..d6ea9949132f498d01da75f6a9462b9040488c40 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/test-js.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/test-jsx.svg b/packages/ui/src/assets/icons/file-types/test-jsx.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea2d4da46aaa855dd0d53fb8c31c08ec3f26777d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/test-jsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/tldraw_light.svg b/packages/ui/src/assets/icons/file-types/tldraw_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..41faab3f0f81936549dd702acf64e77c5d7e0fa7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/tldraw_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/tobi.svg b/packages/ui/src/assets/icons/file-types/tobi.svg new file mode 100644 index 0000000000000000000000000000000000000000..1a576a1c33119b51c198a3a2e78f8c06601e6f5c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/tobi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/toml.svg b/packages/ui/src/assets/icons/file-types/toml.svg new file mode 100644 index 0000000000000000000000000000000000000000..aa4f24c3bd722d36665edfb35fa16ce2d9322089 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/toml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/toml_light.svg b/packages/ui/src/assets/icons/file-types/toml_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..a85712bf82878de4910c0a7f1b549c626014456f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/toml_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/trigger.svg b/packages/ui/src/assets/icons/file-types/trigger.svg new file mode 100644 index 0000000000000000000000000000000000000000..7a4f63a07798ae24168a213fee6d121ebfea6c81 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/trigger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/tune.svg b/packages/ui/src/assets/icons/file-types/tune.svg new file mode 100644 index 0000000000000000000000000000000000000000..ecbde06c6ddddacb797c277bbc01d7c993718264 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/tune.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/turborepo.svg b/packages/ui/src/assets/icons/file-types/turborepo.svg new file mode 100644 index 0000000000000000000000000000000000000000..f0e54498597d1a4a9115cc62a14f220e91ff43f9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/turborepo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/turborepo_light.svg b/packages/ui/src/assets/icons/file-types/turborepo_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..b020a358d45b0760389d7ebfffcd14210e63a5dd --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/turborepo_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/twig.svg b/packages/ui/src/assets/icons/file-types/twig.svg new file mode 100644 index 0000000000000000000000000000000000000000..01f9a5dba577dfd97225e6d8f7da9db9c44ecf3f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/twig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/twine.svg b/packages/ui/src/assets/icons/file-types/twine.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac1bc553b193f0ed45e1b946a36bea5e5c3b5043 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/twine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/typescript-def.svg b/packages/ui/src/assets/icons/file-types/typescript-def.svg new file mode 100644 index 0000000000000000000000000000000000000000..a9ef95879670adf92dddfb5db0e7419fea838335 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/typescript-def.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/typescript.svg b/packages/ui/src/assets/icons/file-types/typescript.svg new file mode 100644 index 0000000000000000000000000000000000000000..acaf0ddb7b53e69d59bc1c24ee4929f6901c924a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/typst.svg b/packages/ui/src/assets/icons/file-types/typst.svg new file mode 100644 index 0000000000000000000000000000000000000000..a36473450979b4e1c2ca7443aae1988ae77cea2c --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/typst.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/uml.svg b/packages/ui/src/assets/icons/file-types/uml.svg new file mode 100644 index 0000000000000000000000000000000000000000..5f70f1e422ae814500486a29749034659c54b01f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/uml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/uml_light.svg b/packages/ui/src/assets/icons/file-types/uml_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..c296fac5abb564b5f87baba72858ffa936babf4b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/uml_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/unity.svg b/packages/ui/src/assets/icons/file-types/unity.svg new file mode 100644 index 0000000000000000000000000000000000000000..f495772f90786e8d223f48f171c93894ca2d4c03 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/unity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/unocss.svg b/packages/ui/src/assets/icons/file-types/unocss.svg new file mode 100644 index 0000000000000000000000000000000000000000..eab05c437963dae64006e20044112c1f81ff9155 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/unocss.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/assets/icons/file-types/url.svg b/packages/ui/src/assets/icons/file-types/url.svg new file mode 100644 index 0000000000000000000000000000000000000000..f065589a64182d3e173795857b8cf8ce937d5eb1 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/url.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/uv.svg b/packages/ui/src/assets/icons/file-types/uv.svg new file mode 100644 index 0000000000000000000000000000000000000000..1549270fb37de565c42d3dac44f449b41bf526f4 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/uv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vagrant.svg b/packages/ui/src/assets/icons/file-types/vagrant.svg new file mode 100644 index 0000000000000000000000000000000000000000..78c19f9e7083ae846e5a995df20bc165eab530b5 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vagrant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vala.svg b/packages/ui/src/assets/icons/file-types/vala.svg new file mode 100644 index 0000000000000000000000000000000000000000..114aff27c48b757c9d5cf0eeae9fe3f6d36af059 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vala.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vanilla-extract.svg b/packages/ui/src/assets/icons/file-types/vanilla-extract.svg new file mode 100644 index 0000000000000000000000000000000000000000..c1f1e594606e41d699bc908e8f730ffd034e560f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vanilla-extract.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/velite.svg b/packages/ui/src/assets/icons/file-types/velite.svg new file mode 100644 index 0000000000000000000000000000000000000000..ca50cfa4fdc3f80aab0ea29112a52eeb44e1c8e9 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/velite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/velocity.svg b/packages/ui/src/assets/icons/file-types/velocity.svg new file mode 100644 index 0000000000000000000000000000000000000000..f5fb988a20f5dfe880df7becaafda4befedf89fe --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/velocity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vercel.svg b/packages/ui/src/assets/icons/file-types/vercel.svg new file mode 100644 index 0000000000000000000000000000000000000000..8ff6e492e27dc9a90c22aac303a14e9b6cab873f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/verdaccio.svg b/packages/ui/src/assets/icons/file-types/verdaccio.svg new file mode 100644 index 0000000000000000000000000000000000000000..3b5f1d41278fc3710d1f846dfee3bba1acd6b2de --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/verdaccio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/verilog.svg b/packages/ui/src/assets/icons/file-types/verilog.svg new file mode 100644 index 0000000000000000000000000000000000000000..c546ea87476f4af84705913126105eab6797637e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/verilog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vim.svg b/packages/ui/src/assets/icons/file-types/vim.svg new file mode 100644 index 0000000000000000000000000000000000000000..1fc655d96a6ded6182766a4353e8de6bfcd9532b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vim.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/virtual.svg b/packages/ui/src/assets/icons/file-types/virtual.svg new file mode 100644 index 0000000000000000000000000000000000000000..0fdb620da5b98b15fe899cd24acea9e1f32ae3f7 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/virtual.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vite.svg b/packages/ui/src/assets/icons/file-types/vite.svg new file mode 100644 index 0000000000000000000000000000000000000000..d66cd5e7b8c8b12084bcda497b8205e6325603d6 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vlang.svg b/packages/ui/src/assets/icons/file-types/vlang.svg new file mode 100644 index 0000000000000000000000000000000000000000..17bf0e049c36bfd5a5f8d1e8fe510b9fe810ad3f --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vlang.svg @@ -0,0 +1,6 @@ + + + + diff --git a/packages/ui/src/assets/icons/file-types/vue-config.svg b/packages/ui/src/assets/icons/file-types/vue-config.svg new file mode 100644 index 0000000000000000000000000000000000000000..bfe01c23cad7929e93a6df909f3e1f9003d844ab --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vue-config.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vue.svg b/packages/ui/src/assets/icons/file-types/vue.svg new file mode 100644 index 0000000000000000000000000000000000000000..359f899f23de71f124af91ad6fa98235c08348ae --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/wakatime_light.svg b/packages/ui/src/assets/icons/file-types/wakatime_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..2b94c5611043f3a2d677bd901a5b0eba1a9fd26d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/wakatime_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/wally.svg b/packages/ui/src/assets/icons/file-types/wally.svg new file mode 100644 index 0000000000000000000000000000000000000000..a5c1f24facc8856c76ed2671d26dec1f3d6dac20 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/wally.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/webassembly.svg b/packages/ui/src/assets/icons/file-types/webassembly.svg new file mode 100644 index 0000000000000000000000000000000000000000..69a43aa35eaf1e0ae61313067288e346cb7f5386 --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/webassembly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/webpack.svg b/packages/ui/src/assets/icons/file-types/webpack.svg new file mode 100644 index 0000000000000000000000000000000000000000..68233d9e9da05fb25647da73cfa54573a04b5d6a --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/webpack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/windicss.svg b/packages/ui/src/assets/icons/file-types/windicss.svg new file mode 100644 index 0000000000000000000000000000000000000000..4f31c55fae2172154bfd4a9deaf3a3464cffb83e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/windicss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/wolframlanguage.svg b/packages/ui/src/assets/icons/file-types/wolframlanguage.svg new file mode 100644 index 0000000000000000000000000000000000000000..77e880995f8d93aedd9295c4dcc69d8a56cc769e --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/wolframlanguage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/yaml.svg b/packages/ui/src/assets/icons/file-types/yaml.svg new file mode 100644 index 0000000000000000000000000000000000000000..1f1cc7cb04974ae1e17d605462ed2eba169a336d --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/yaml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/yang.svg b/packages/ui/src/assets/icons/file-types/yang.svg new file mode 100644 index 0000000000000000000000000000000000000000..fba4bbfa415c206c818296dcfbeb62b79d464cac --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/yang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/zeabur_light.svg b/packages/ui/src/assets/icons/file-types/zeabur_light.svg new file mode 100644 index 0000000000000000000000000000000000000000..0d01f2b1c9f2e330338843e5f3f858426db0928b --- /dev/null +++ b/packages/ui/src/assets/icons/file-types/zeabur_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/302ai.svg b/packages/ui/src/assets/icons/provider/302ai.svg new file mode 100644 index 0000000000000000000000000000000000000000..46f2e4315e02dce2987b90c041e4754c2c37db2b --- /dev/null +++ b/packages/ui/src/assets/icons/provider/302ai.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/abacus.svg b/packages/ui/src/assets/icons/provider/abacus.svg new file mode 100644 index 0000000000000000000000000000000000000000..121a91d98ef0f6ef636b999b26f8fa34a2aa077a --- /dev/null +++ b/packages/ui/src/assets/icons/provider/abacus.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg b/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg new file mode 100644 index 0000000000000000000000000000000000000000..b3a2edc3c02a4cc86ce336046b4540dc5df6e628 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg b/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg new file mode 100644 index 0000000000000000000000000000000000000000..b3a2edc3c02a4cc86ce336046b4540dc5df6e628 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/baseten.svg b/packages/ui/src/assets/icons/provider/baseten.svg new file mode 100644 index 0000000000000000000000000000000000000000..ffd4fbd8b0028ca7c5d3c66db5eb33e27ba1c559 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/baseten.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/cortecs.svg b/packages/ui/src/assets/icons/provider/cortecs.svg new file mode 100644 index 0000000000000000000000000000000000000000..086e9aa1fca10f541ca4ec89b328e4756141b33d --- /dev/null +++ b/packages/ui/src/assets/icons/provider/cortecs.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/gitlab.svg b/packages/ui/src/assets/icons/provider/gitlab.svg new file mode 100644 index 0000000000000000000000000000000000000000..eef04ace2b09b0a1eaed11949cc7c79ec5ab54e1 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/gitlab.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg b/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg new file mode 100644 index 0000000000000000000000000000000000000000..086e9aa1fca10f541ca4ec89b328e4756141b33d --- /dev/null +++ b/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/huggingface.svg b/packages/ui/src/assets/icons/provider/huggingface.svg new file mode 100644 index 0000000000000000000000000000000000000000..4241ff94e3d48860b71b487ade7bdaff244c65d7 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/huggingface.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/io-net.svg b/packages/ui/src/assets/icons/provider/io-net.svg new file mode 100644 index 0000000000000000000000000000000000000000..651e57df845a56a50d7a1f25d0a46b7f6a3a0f46 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/io-net.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/jiekou.svg b/packages/ui/src/assets/icons/provider/jiekou.svg new file mode 100644 index 0000000000000000000000000000000000000000..7fe6378e561cd4af70672310de8b14b02d6b771b --- /dev/null +++ b/packages/ui/src/assets/icons/provider/jiekou.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/provider/meganova.svg b/packages/ui/src/assets/icons/provider/meganova.svg new file mode 100644 index 0000000000000000000000000000000000000000..59e8b3a1f271cc8dd600eee0d276305521fd7f20 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/meganova.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/minimax-cn.svg b/packages/ui/src/assets/icons/provider/minimax-cn.svg new file mode 100644 index 0000000000000000000000000000000000000000..44c5eec21d3eb79b14ccf4375fda95668de6531e --- /dev/null +++ b/packages/ui/src/assets/icons/provider/minimax-cn.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/nano-gpt.svg b/packages/ui/src/assets/icons/provider/nano-gpt.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7fbb4cdcdef1b17e76a6c820a1fe185d6c23cfc --- /dev/null +++ b/packages/ui/src/assets/icons/provider/nano-gpt.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/nebius.svg b/packages/ui/src/assets/icons/provider/nebius.svg new file mode 100644 index 0000000000000000000000000000000000000000..aeba5890d11a0d0f3266d9df3ff43c26693f2c87 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/nebius.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/provider/novita-ai.svg b/packages/ui/src/assets/icons/provider/novita-ai.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac537b8dd424d16c264207d7f4b22885bfb81e97 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/novita-ai.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/ovhcloud.svg b/packages/ui/src/assets/icons/provider/ovhcloud.svg new file mode 100644 index 0000000000000000000000000000000000000000..064b0835be24aeb445a2f78d580926d59a25c101 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/ovhcloud.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/qiniu-ai.svg b/packages/ui/src/assets/icons/provider/qiniu-ai.svg new file mode 100644 index 0000000000000000000000000000000000000000..858560f9ffe73e0fb42568eac70381838c5a5d32 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/qiniu-ai.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/scaleway.svg b/packages/ui/src/assets/icons/provider/scaleway.svg new file mode 100644 index 0000000000000000000000000000000000000000..7574f72f38874f16665bb0dad78b671cfcf852fb --- /dev/null +++ b/packages/ui/src/assets/icons/provider/scaleway.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/vultr.svg b/packages/ui/src/assets/icons/provider/vultr.svg new file mode 100644 index 0000000000000000000000000000000000000000..9205d448455590e75ab5f33c02cbeb55011252a3 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/vultr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/src/assets/icons/provider/xiaomi.svg b/packages/ui/src/assets/icons/provider/xiaomi.svg new file mode 100644 index 0000000000000000000000000000000000000000..4a893919e0d7fb1e412482904aab7fe20b08b9ae --- /dev/null +++ b/packages/ui/src/assets/icons/provider/xiaomi.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/zai.svg b/packages/ui/src/assets/icons/provider/zai.svg new file mode 100644 index 0000000000000000000000000000000000000000..d7da9b7c5f35cdd4b96caeec3321a829277ce614 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/zai.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/components/accordion.tsx b/packages/ui/src/components/accordion.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3179b8a1535bae3e5345a4384c9064bdbfec9fc0 --- /dev/null +++ b/packages/ui/src/components/accordion.tsx @@ -0,0 +1,92 @@ +import { Accordion as Kobalte } from "@kobalte/core/accordion" +import { splitProps } from "solid-js" +import type { ComponentProps, ParentProps } from "solid-js" + +export interface AccordionProps extends ComponentProps {} +export interface AccordionItemProps extends ComponentProps {} +export interface AccordionHeaderProps extends ComponentProps {} +export interface AccordionTriggerProps extends ComponentProps {} +export interface AccordionContentProps extends ComponentProps {} + +function AccordionRoot(props: AccordionProps) { + const [split, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function AccordionItem(props: AccordionItemProps) { + const [split, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function AccordionHeader(props: ParentProps) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {split.children} + + ) +} + +function AccordionTrigger(props: ParentProps) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {split.children} + + ) +} + +function AccordionContent(props: ParentProps) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {split.children} + + ) +} + +export const Accordion = Object.assign(AccordionRoot, { + Item: AccordionItem, + Header: AccordionHeader, + Trigger: AccordionTrigger, + Content: AccordionContent, +}) diff --git a/packages/ui/src/components/animated-number.css b/packages/ui/src/components/animated-number.css new file mode 100644 index 0000000000000000000000000000000000000000..022b347e968907591ec049764f9845e0428c489b --- /dev/null +++ b/packages/ui/src/components/animated-number.css @@ -0,0 +1,75 @@ +[data-component="animated-number"] { + display: inline-flex; + align-items: baseline; + vertical-align: baseline; + line-height: inherit; + font-variant-numeric: tabular-nums; + + [data-slot="animated-number-value"] { + display: inline-flex; + flex-direction: row-reverse; + align-items: baseline; + justify-content: flex-end; + line-height: inherit; + width: var(--animated-number-width, 1ch); + overflow: hidden; + transition: width var(--tool-motion-spring-ms, 560ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="animated-number-digit"] { + display: inline-block; + width: 1ch; + height: 1em; + line-height: 1em; + overflow: hidden; + vertical-align: baseline; + -webkit-mask-image: linear-gradient( + to bottom, + transparent 0%, + #000 var(--tool-motion-mask, 18%), + #000 calc(100% - var(--tool-motion-mask, 18%)), + transparent 100% + ); + mask-image: linear-gradient( + to bottom, + transparent 0%, + #000 var(--tool-motion-mask, 18%), + #000 calc(100% - var(--tool-motion-mask, 18%)), + transparent 100% + ); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + } + + [data-slot="animated-number-strip"] { + display: inline-flex; + flex-direction: column; + transform: translateY(calc(var(--animated-number-offset, 10) * -1em)); + transition-property: transform; + transition-duration: var(--animated-number-duration, 560ms); + transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + [data-slot="animated-number-strip"][data-animating="false"] { + transition-duration: 0ms; + } + + [data-slot="animated-number-cell"] { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1ch; + height: 1em; + line-height: 1em; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="animated-number"] [data-slot="animated-number-value"] { + transition-duration: 0ms; + } + + [data-component="animated-number"] [data-slot="animated-number-strip"] { + transition-duration: 0ms; + } +} diff --git a/packages/ui/src/components/app-icon.css b/packages/ui/src/components/app-icon.css new file mode 100644 index 0000000000000000000000000000000000000000..16cb0dcc1ce660890f85229ad2a41ad8449e1a24 --- /dev/null +++ b/packages/ui/src/components/app-icon.css @@ -0,0 +1,5 @@ +img[data-component="app-icon"] { + display: block; + box-sizing: border-box; + object-fit: contain; +} diff --git a/packages/ui/src/components/app-icon.tsx b/packages/ui/src/components/app-icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..541dfc5708219b5a0cda469e367c611f92d6c586 --- /dev/null +++ b/packages/ui/src/components/app-icon.tsx @@ -0,0 +1,85 @@ +import type { Component, ComponentProps } from "solid-js" +import { createSignal, onCleanup, onMount, splitProps } from "solid-js" +import type { IconName } from "./app-icons/types" + +import androidStudio from "../assets/icons/app/android-studio.svg" +import antigravity from "../assets/icons/app/antigravity.svg" +import cursor from "../assets/icons/app/cursor.svg" +import fileExplorer from "../assets/icons/app/file-explorer.svg" +import finder from "../assets/icons/app/finder.png" +import ghostty from "../assets/icons/app/ghostty.svg" +import iterm2 from "../assets/icons/app/iterm2.svg" +import powershell from "../assets/icons/app/powershell.svg" +import terminal from "../assets/icons/app/terminal.png" +import textmate from "../assets/icons/app/textmate.png" +import vscode from "../assets/icons/app/vscode.svg" +import warp from "../assets/icons/app/warp.png" +import xcode from "../assets/icons/app/xcode.png" +import zed from "../assets/icons/app/zed.svg" +import zedDark from "../assets/icons/app/zed-dark.svg" +import sublimetext from "../assets/icons/app/sublimetext.svg" + +const icons = { + vscode, + cursor, + zed, + "file-explorer": fileExplorer, + finder, + terminal, + iterm2, + ghostty, + warp, + xcode, + "android-studio": androidStudio, + antigravity, + textmate, + powershell, + "sublime-text": sublimetext, +} satisfies Record + +const themed: Partial> = { + zed: { + light: zed, + dark: zedDark, + }, +} + +const scheme = () => { + if (typeof document !== "object") return "light" as const + if (document.documentElement.dataset.colorScheme === "dark") return "dark" as const + return "light" as const +} + +export type AppIconProps = Omit, "src"> & { + id: IconName +} + +export const AppIcon: Component = (props) => { + const [local, rest] = splitProps(props, ["id", "class", "classList", "alt", "draggable"]) + const [mode, setMode] = createSignal(scheme()) + + onMount(() => { + const sync = () => setMode(scheme()) + const observer = new MutationObserver(sync) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-color-scheme"], + }) + sync() + onCleanup(() => observer.disconnect()) + }) + + return ( + {local.alt + ) +} diff --git a/packages/ui/src/components/app-icons/sprite.svg b/packages/ui/src/components/app-icons/sprite.svg new file mode 100644 index 0000000000000000000000000000000000000000..68361f4137ba5b342d0f1689ee588102c21ae2c1 --- /dev/null +++ b/packages/ui/src/components/app-icons/sprite.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/app-icons/types.ts b/packages/ui/src/components/app-icons/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fb3abf39c3e8daa9351e76765b59b0e41b74082 --- /dev/null +++ b/packages/ui/src/components/app-icons/types.ts @@ -0,0 +1,21 @@ +// This file is generated by icon spritesheet generator + +export const iconNames = [ + "vscode", + "cursor", + "zed", + "file-explorer", + "finder", + "terminal", + "iterm2", + "ghostty", + "warp", + "xcode", + "android-studio", + "antigravity", + "textmate", + "powershell", + "sublime-text", +] as const + +export type IconName = (typeof iconNames)[number] diff --git a/packages/ui/src/components/avatar.css b/packages/ui/src/components/avatar.css new file mode 100644 index 0000000000000000000000000000000000000000..5872160771ce6c973817f0865b750a1235ed235b --- /dev/null +++ b/packages/ui/src/components/avatar.css @@ -0,0 +1,49 @@ +[data-component="avatar"] { + --avatar-bg: var(--color-surface-info-base); + --avatar-fg: var(--color-text-base); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + border-radius: var(--radius-sm); + border: 1px solid var(--color-border-weak-base); + font-family: var(--font-mono); + font-weight: 500; + text-transform: uppercase; + background-color: var(--avatar-bg); + color: var(--avatar-fg); +} + +[data-component="avatar"][data-has-image] { + background-color: transparent; + border: none; +} + +[data-component="avatar"][data-size="small"] { + width: 1.25rem; + height: 1.25rem; + font-size: 0.75rem; + line-height: 1; +} + +[data-component="avatar"][data-size="normal"] { + width: 1.5rem; + height: 1.5rem; + font-size: 1.125rem; + line-height: 1.5rem; +} + +[data-component="avatar"][data-size="large"] { + width: 2rem; + height: 2rem; + font-size: 1.25rem; + line-height: 2rem; +} + +[data-component="avatar"] [data-slot="avatar-image"] { + width: 100%; + height: 100%; + display: block; + object-fit: cover; + border-radius: inherit; +} diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..035c2d304149e6472fecc1a6330bfe2c8a633d8f --- /dev/null +++ b/packages/ui/src/components/avatar.tsx @@ -0,0 +1,55 @@ +import { type ComponentProps, splitProps, Show } from "solid-js" + +const segmenter = + typeof Intl !== "undefined" && "Segmenter" in Intl + ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) + : undefined + +function first(value: string) { + if (!value) return "" + if (!segmenter) return Array.from(value)[0] ?? "" + return segmenter.segment(value)[Symbol.iterator]().next().value?.segment ?? Array.from(value)[0] ?? "" +} + +export interface AvatarProps extends ComponentProps<"div"> { + fallback: string + src?: string + background?: string + foreground?: string + size?: "small" | "normal" | "large" +} + +export function Avatar(props: AvatarProps) { + const [split, rest] = splitProps(props, [ + "fallback", + "src", + "background", + "foreground", + "size", + "class", + "classList", + "style", + ]) + const src = split.src // did this so i can zero it out to test fallback + return ( +
+ + {(src) => } + +
+ ) +} diff --git a/packages/ui/src/components/button.css b/packages/ui/src/components/button.css new file mode 100644 index 0000000000000000000000000000000000000000..923b2bab374b659fe7ffa369e51185570b56edc7 --- /dev/null +++ b/packages/ui/src/components/button.css @@ -0,0 +1,194 @@ +[data-component="button"] { + display: inline-flex; + align-items: center; + justify-content: center; + border-style: solid; + border-width: 1px; + border-radius: var(--radius-md); + text-decoration: none; + user-select: none; + cursor: default; + outline: none; + white-space: nowrap; + + &[data-variant="primary"] { + background-color: var(--button-primary-base); + border-color: var(--border-weak-base); + color: var(--icon-invert-base); + + [data-slot="icon-svg"] { + color: var(--icon-invert-base); + } + + &:hover:not(:disabled) { + background-color: var(--icon-strong-hover); + } + &:focus:not(:disabled) { + background-color: var(--icon-strong-focus); + } + &:active:not(:disabled) { + background-color: var(--icon-strong-active); + } + &:disabled { + background-color: var(--icon-strong-disabled); + + [data-slot="icon-svg"] { + color: var(--icon-invert-base); + } + } + } + + &[data-variant="ghost"] { + border-color: transparent; + background-color: transparent; + color: var(--text-strong); + + [data-slot="icon-svg"] { + color: var(--icon-base); + } + + &:hover:not(:disabled) { + background-color: var(--surface-base-hover); + } + &:focus-visible:not(:disabled) { + background-color: var(--surface-base-hover); + } + &:active:not(:disabled) { + background-color: var(--surface-base-active); + } + &:disabled { + color: var(--text-weak); + cursor: not-allowed; + + [data-slot="icon-svg"] { + color: var(--icon-disabled); + } + } + &[data-selected="true"]:not(:disabled) { + background-color: var(--surface-base-hover); + } + &[data-active="true"] { + background-color: var(--surface-base-active); + } + } + + &[data-variant="secondary"] { + border: transparent; + background-color: var(--button-secondary-base); + color: var(--text-strong); + box-shadow: var(--shadow-xs-border-base); + + &:hover:not(:disabled) { + background-color: var(--button-secondary-hover); + } + &:focus:not(:disabled) { + background-color: var(--button-secondary-base); + } + &:focus-visible:not(:active) { + background-color: var(--button-secondary-base); + box-shadow: var(--shadow-xs-border-focus); + } + &:focus-visible:active { + box-shadow: none; + } + &:active:not(:disabled) { + background-color: var(--button-secondary-base); + } + &:disabled { + border-color: var(--border-disabled); + background-color: var(--surface-disabled); + color: var(--text-weak); + cursor: not-allowed; + } + + [data-slot="icon-svg"] { + color: var(--icon-base); + } + } + + &[data-size="small"] { + height: 24px; + padding: 0 8px; + &[data-icon] { + padding: 0 12px 0 4px; + } + + font-size: var(--font-size-small); + line-height: var(--line-height-large); + gap: 8px; + + /* text-12-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + } + + &[data-size="normal"] { + height: 28px; + line-height: 28px; + padding: 0 6px; + &[data-icon] { + padding: 0 12px 0 4px; + } + + font-size: var(--font-size-small); + gap: 8px; + + /* text-12-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + letter-spacing: var(--letter-spacing-normal); + } + + &[data-size="large"] { + height: 32px; + padding: 6px 12px; + + &[data-icon] { + padding: 0 12px 0 8px; + } + + gap: 8px; + + /* text-14-medium */ + font-family: var(--font-family-sans); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 142.857% */ + letter-spacing: var(--letter-spacing-normal); + } + + &:focus { + outline: none; + } +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] { + background-color: var(--surface-base-active); +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] [data-slot="icon-svg"] { + color: var(--icon-strong-base); +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"]:hover:not(:disabled) { + background-color: var(--surface-base-active); +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-current="page"] { + background-color: var(--surface-base-active); +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-current="page"] [data-slot="icon-svg"] { + color: var(--icon-strong-base); +} + +[data-component="button"].titlebar-icon[data-variant="ghost"][aria-current="page"]:hover:not(:disabled) { + background-color: var(--surface-base-active); +} diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1652145f51b72e1f0b4d65bef991fc3af8cd3d4 --- /dev/null +++ b/packages/ui/src/components/button.tsx @@ -0,0 +1,33 @@ +import { Button as Kobalte } from "@kobalte/core/button" +import { type ComponentProps, Show, splitProps } from "solid-js" +import { Icon, IconProps } from "./icon" + +export interface ButtonProps + extends ComponentProps, + Pick, "class" | "classList" | "children"> { + size?: "small" | "normal" | "large" + variant?: "primary" | "secondary" | "ghost" + icon?: IconProps["name"] +} + +export function Button(props: ButtonProps) { + const [split, rest] = splitProps(props, ["variant", "size", "icon", "class", "classList"]) + return ( + + + + + {props.children} + + ) +} diff --git a/packages/ui/src/components/checkbox.css b/packages/ui/src/components/checkbox.css new file mode 100644 index 0000000000000000000000000000000000000000..3041799659165180e86c1c54045814d1e0409a3b --- /dev/null +++ b/packages/ui/src/components/checkbox.css @@ -0,0 +1,131 @@ +[data-component="checkbox"] { + display: flex; + align-items: var(--checkbox-align, center); + gap: 12px; + cursor: default; + + [data-slot="checkbox-checkbox-input"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + [data-slot="checkbox-checkbox-control"] { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 2px; + margin-top: var(--checkbox-offset, 0px); + aspect-ratio: 1; + flex-shrink: 0; + border-radius: var(--radius-sm); + border: 1px solid var(--border-weak-base); + transition: + border-color 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + background-color 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + box-shadow 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + /* background-color: var(--surface-weak); */ + } + + [data-slot="checkbox-checkbox-indicator"] { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: var(--icon-base); + opacity: 0; + transform: scale(0.9); + transition: + opacity 180ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), + transform 220ms var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + } + + /* [data-slot="checkbox-checkbox-content"] { */ + /* } */ + + [data-slot="checkbox-checkbox-label"] { + user-select: none; + color: var(--text-base); + + /* text-12-regular */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="checkbox-checkbox-description"] { + color: var(--text-base); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="checkbox-checkbox-error"] { + color: var(--text-error); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + &:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &:not([data-readonly]) [data-slot="checkbox-checkbox-input"]:focus-visible + [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-focus); + box-shadow: var(--shadow-xs-border-focus); + } + + &[data-checked] [data-slot="checkbox-checkbox-control"], + &[data-indeterminate] [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-base); + background-color: var(--surface-weak); + } + + &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"], + &[data-indeterminate]:hover:not([data-disabled]) [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &[data-checked] [data-slot="checkbox-checkbox-indicator"], + &[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] { + opacity: 1; + transform: scale(1); + } + + &[data-disabled] { + cursor: not-allowed; + } + + &[data-disabled] [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-disabled); + background-color: var(--surface-disabled); + } + + &[data-invalid] [data-slot="checkbox-checkbox-control"] { + border-color: var(--border-error); + } + + &[data-readonly] { + cursor: default; + pointer-events: none; + } +} diff --git a/packages/ui/src/components/checkbox.tsx b/packages/ui/src/components/checkbox.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7187e4ac300b90db3b2cc205e8a2cbd4ae352aba --- /dev/null +++ b/packages/ui/src/components/checkbox.tsx @@ -0,0 +1,43 @@ +import { Checkbox as Kobalte } from "@kobalte/core/checkbox" +import { Show, splitProps } from "solid-js" +import type { ComponentProps, JSX, ParentProps } from "solid-js" + +export interface CheckboxProps extends ParentProps> { + hideLabel?: boolean + description?: string + icon?: JSX.Element +} + +export function Checkbox(props: CheckboxProps) { + const [local, others] = splitProps(props, ["children", "class", "label", "hideLabel", "description", "icon"]) + return ( + + + + + {local.icon || ( + + + + )} + + +
+ + + {props.children} + + + + {local.description} + + +
+
+ ) +} diff --git a/packages/ui/src/components/collapsible.css b/packages/ui/src/components/collapsible.css new file mode 100644 index 0000000000000000000000000000000000000000..8e0b6f1a9a7b8c4ba0496e40cb53f26736e93a9e --- /dev/null +++ b/packages/ui/src/components/collapsible.css @@ -0,0 +1,154 @@ +[data-component="collapsible"] { + width: 100%; + display: flex; + flex-direction: column; + background-color: transparent; + border: none; + transition: background-color 0.15s ease; + border-radius: var(--radius-md); + overflow: visible; + + &.tool-collapsible { + --tool-content-gap: 4px; + gap: var(--tool-content-gap); + } + + [data-slot="collapsible-trigger"] { + width: 100%; + display: flex; + height: 32px; + padding: 0; + align-items: center; + align-self: stretch; + cursor: default; + user-select: none; + color: var(--text-base); + + [data-slot="collapsible-arrow"] { + opacity: 0; + transition: opacity 0.15s ease; + will-change: opacity; + transform: translateZ(0); + } + + [data-slot="collapsible-arrow-icon"] { + display: inline-flex; + color: var(--icon-weaker); + transform: translateZ(0) rotate(-90deg); + transition: transform 0.15s ease; + will-change: transform; + } + + &:hover [data-slot="collapsible-arrow"] { + opacity: 1; + } + + /* text-12-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + + /* &:hover { */ + /* background-color: var(--surface-base); */ + /* } */ + &:focus-visible { + outline: none; + background-color: var(--surface-raised-base-hover); + } + &[data-disabled] { + cursor: not-allowed; + } + + &[data-hide-details="true"] { + height: auto; + align-items: stretch; + } + + [data-slot="collapsible-arrow"] { + flex-shrink: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + } + } + + [data-slot="collapsible-trigger"][aria-expanded="true"] { + [data-slot="collapsible-arrow"] { + opacity: 1; + } + + [data-slot="collapsible-arrow-icon"] { + transform: translateZ(0) rotate(0deg); + } + } + + [data-slot="collapsible-content"] { + overflow: hidden; + /* animation: slideUp 250ms ease-out; */ + + &[data-expanded] { + overflow: visible; + } + + /* &[data-expanded] { */ + /* animation: slideDown 250ms ease-out; */ + /* } */ + } + + &[data-variant="ghost"] { + background-color: transparent; + border: none; + + > [data-slot="collapsible-trigger"] { + background-color: transparent; + border: none; + padding: 0; + + /* &:hover { */ + /* color: var(--text-strong); */ + /* } */ + &:focus-visible { + outline: none; + background-color: var(--surface-raised-base-hover); + } + &[data-disabled] { + cursor: not-allowed; + } + } + } + + &[data-variant="ghost"][data-scope="filetree"] { + > [data-slot="collapsible-trigger"] { + height: 24px; + } + } + + &[data-variant="ghost"][data-scope="file-tree-v2"] { + > [data-slot="collapsible-trigger"] { + height: 28px; + } + } +} + +@keyframes slideDown { + from { + height: 0; + } + to { + height: var(--kb-collapsible-content-height); + } +} + +@keyframes slideUp { + from { + height: var(--kb-collapsible-content-height); + } + to { + height: 0; + } +} diff --git a/packages/ui/src/components/collapsible.stories.tsx b/packages/ui/src/components/collapsible.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..67883b229974089325d18ef14371cfa7b99f2844 --- /dev/null +++ b/packages/ui/src/components/collapsible.stories.tsx @@ -0,0 +1,86 @@ +// @ts-nocheck +import * as mod from "./collapsible" + +const docs = `### Overview +Toggleable content region with optional arrow indicator. + +Compose \`Collapsible.Trigger\`, \`Collapsible.Content\`, and \`Collapsible.Arrow\`. + +### API +- Root accepts Kobalte Collapsible props (\`open\`, \`defaultOpen\`, \`onOpenChange\`). +- \`variant\` controls styling ("normal" | "ghost"). + +### Variants and states +- Normal and ghost variants. +- Open/closed states. + +### Behavior +- Trigger toggles the content visibility. + +### Accessibility +- TODO: confirm ARIA attributes provided by Kobalte. + +### Theming/tokens +- Uses \`data-component="collapsible"\` and slots for trigger/content/arrow. + +` + +export default { + title: "UI/Collapsible", + id: "components-collapsible", + component: mod.Collapsible, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + variant: { + control: "select", + options: ["normal", "ghost"], + }, + }, +} + +export const Basic = { + args: { + variant: "normal", + defaultOpen: true, + }, + render: (props) => ( + + +
+ Details + +
+
+ +
Optional details sit here.
+
+
+ ), +} + +export const Ghost = { + args: { + variant: "ghost", + defaultOpen: false, + }, + render: (props) => ( + + +
+ Ghost trigger + +
+
+ +
Ghost content.
+
+
+ ), +} diff --git a/packages/ui/src/components/collapsible.tsx b/packages/ui/src/components/collapsible.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b2a603264645afe1b3dee771e4f3aa03b571aec8 --- /dev/null +++ b/packages/ui/src/components/collapsible.tsx @@ -0,0 +1,48 @@ +import { Collapsible as Kobalte, CollapsibleRootProps } from "@kobalte/core/collapsible" +import { ComponentProps, ParentProps, splitProps } from "solid-js" +import { Icon } from "./icon" + +export interface CollapsibleProps extends ParentProps { + class?: string + classList?: ComponentProps<"div">["classList"] + variant?: "normal" | "ghost" +} + +function CollapsibleRoot(props: CollapsibleProps) { + const [local, others] = splitProps(props, ["class", "classList", "variant"]) + return ( + + ) +} + +function CollapsibleTrigger(props: ComponentProps) { + return +} + +function CollapsibleContent(props: ComponentProps) { + return +} + +function CollapsibleArrow(props?: ComponentProps<"div">) { + return ( +
+ + + +
+ ) +} + +export const Collapsible = Object.assign(CollapsibleRoot, { + Arrow: CollapsibleArrow, + Trigger: CollapsibleTrigger, + Content: CollapsibleContent, +}) diff --git a/packages/ui/src/components/context-menu.css b/packages/ui/src/components/context-menu.css new file mode 100644 index 0000000000000000000000000000000000000000..1e366dccd42ae5d6d7399b15dd89e04ecbaeeb49 --- /dev/null +++ b/packages/ui/src/components/context-menu.css @@ -0,0 +1,134 @@ +[data-component="context-menu-content"], +[data-component="context-menu-sub-content"] { + min-width: 8rem; + overflow: hidden; + border: none; + border-radius: var(--radius-md); + box-shadow: var(--shadow-xs-border); + background-clip: padding-box; + background-color: var(--surface-raised-stronger-non-alpha); + padding: 4px; + z-index: 100; + transform-origin: var(--kb-menu-content-transform-origin); + + &:focus-within, + &:focus { + outline: none; + } + + animation: contextMenuContentHide var(--transition-duration) var(--transition-easing) forwards; + + @starting-style { + animation: none; + } + + &[data-expanded] { + pointer-events: auto; + animation: contextMenuContentShow var(--transition-duration) var(--transition-easing) forwards; + } +} + +[data-component="context-menu-content"], +[data-component="context-menu-sub-content"] { + [data-slot="context-menu-item"], + [data-slot="context-menu-checkbox-item"], + [data-slot="context-menu-radio-item"], + [data-slot="context-menu-sub-trigger"] { + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border-radius: var(--radius-sm); + cursor: default; + outline: none; + + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + color: var(--text-strong); + + transition-property: background-color, color; + transition-duration: var(--transition-duration); + transition-timing-function: var(--transition-easing); + user-select: none; + + &:hover { + background-color: var(--surface-raised-base-hover); + } + + &[data-disabled] { + color: var(--text-weak); + pointer-events: none; + } + } + + [data-slot="context-menu-sub-trigger"] { + &[data-expanded] { + background: var(--surface-raised-base-hover); + outline: none; + border: none; + } + } + + [data-slot="context-menu-item-indicator"] { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + } + + [data-slot="context-menu-item-label"] { + flex: 1; + } + + [data-slot="context-menu-item-description"] { + font-size: var(--font-size-x-small); + color: var(--text-weak); + } + + [data-slot="context-menu-separator"] { + height: 1px; + margin: 4px -4px; + border-top-color: var(--border-weak-base); + } + + [data-slot="context-menu-group-label"] { + padding: 4px 8px; + font-family: var(--font-family-sans); + font-size: var(--font-size-x-small); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + color: var(--text-weak); + } + + [data-slot="context-menu-arrow"] { + fill: var(--surface-raised-stronger-non-alpha); + } +} + +@keyframes contextMenuContentShow { + from { + opacity: 0; + transform: scaleY(0.95); + } + to { + opacity: 1; + transform: scaleY(1); + } +} + +@keyframes contextMenuContentHide { + from { + opacity: 1; + transform: scaleY(1); + } + to { + opacity: 0; + transform: scaleY(0.95); + } +} diff --git a/packages/ui/src/components/context-menu.stories.tsx b/packages/ui/src/components/context-menu.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bee5a55965548d748996ad6f47d37abdfa16dd0f --- /dev/null +++ b/packages/ui/src/components/context-menu.stories.tsx @@ -0,0 +1,113 @@ +// @ts-nocheck +import * as mod from "./context-menu" + +const docs = `### Overview +Context menu for right-click interactions with composable items and submenus. + +Use \`ItemLabel\` and \`ItemDescription\` for rich items. + +### API +- Root accepts Kobalte ContextMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`). +- Compose \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections. + +### Variants and states +- Supports grouped sections and nested submenus. + +### Behavior +- Opens on context menu gesture over the trigger element. + +### Accessibility +- TODO: confirm keyboard and focus behavior from Kobalte. + +### Theming/tokens +- Uses \`data-component="context-menu"\` and slot attributes for styling. + +` + +export default { + title: "UI/ContextMenu", + id: "components-context-menu", + component: mod.ContextMenu, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( + + +
+ Right click (or open) here +
+
+ + + + Actions + + Copy + + + Paste + + + + + More + + + Duplicate + + + Move + + + + + +
+ ), +} + +export const CheckboxRadio = { + render: () => ( + + +
+ Right click (or open) here +
+
+ + + Show line numbers + Wrap lines + + + Compact + Comfortable + + + +
+ ), +} diff --git a/packages/ui/src/components/context-menu.tsx b/packages/ui/src/components/context-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f4566a17a9b1ceea688ed5ae6b6509cf49cd0793 --- /dev/null +++ b/packages/ui/src/components/context-menu.tsx @@ -0,0 +1,308 @@ +import { ContextMenu as Kobalte } from "@kobalte/core/context-menu" +import { splitProps } from "solid-js" +import type { ComponentProps, ParentProps } from "solid-js" + +export interface ContextMenuProps extends ComponentProps {} +export interface ContextMenuTriggerProps extends ComponentProps {} +export interface ContextMenuIconProps extends ComponentProps {} +export interface ContextMenuPortalProps extends ComponentProps {} +export interface ContextMenuContentProps extends ComponentProps {} +export interface ContextMenuArrowProps extends ComponentProps {} +export interface ContextMenuSeparatorProps extends ComponentProps {} +export interface ContextMenuGroupProps extends ComponentProps {} +export interface ContextMenuGroupLabelProps extends ComponentProps {} +export interface ContextMenuItemProps extends ComponentProps {} +export interface ContextMenuItemLabelProps extends ComponentProps {} +export interface ContextMenuItemDescriptionProps extends ComponentProps {} +export interface ContextMenuItemIndicatorProps extends ComponentProps {} +export interface ContextMenuRadioGroupProps extends ComponentProps {} +export interface ContextMenuRadioItemProps extends ComponentProps {} +export interface ContextMenuCheckboxItemProps extends ComponentProps {} +export interface ContextMenuSubProps extends ComponentProps {} +export interface ContextMenuSubTriggerProps extends ComponentProps {} +export interface ContextMenuSubContentProps extends ComponentProps {} + +function ContextMenuRoot(props: ContextMenuProps) { + return +} + +function ContextMenuTrigger(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuIcon(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuPortal(props: ContextMenuPortalProps) { + return +} + +function ContextMenuContent(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuArrow(props: ContextMenuArrowProps) { + const [local, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function ContextMenuSeparator(props: ContextMenuSeparatorProps) { + const [local, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function ContextMenuGroup(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuGroupLabel(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuItemLabel(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuItemDescription(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuItemIndicator(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuRadioGroup(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuRadioItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuCheckboxItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuSub(props: ContextMenuSubProps) { + return +} + +function ContextMenuSubTrigger(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function ContextMenuSubContent(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +export const ContextMenu = Object.assign(ContextMenuRoot, { + Trigger: ContextMenuTrigger, + Icon: ContextMenuIcon, + Portal: ContextMenuPortal, + Content: ContextMenuContent, + Arrow: ContextMenuArrow, + Separator: ContextMenuSeparator, + Group: ContextMenuGroup, + GroupLabel: ContextMenuGroupLabel, + Item: ContextMenuItem, + ItemLabel: ContextMenuItemLabel, + ItemDescription: ContextMenuItemDescription, + ItemIndicator: ContextMenuItemIndicator, + RadioGroup: ContextMenuRadioGroup, + RadioItem: ContextMenuRadioItem, + CheckboxItem: ContextMenuCheckboxItem, + Sub: ContextMenuSub, + SubTrigger: ContextMenuSubTrigger, + SubContent: ContextMenuSubContent, +}) diff --git a/packages/ui/src/components/dialog.css b/packages/ui/src/components/dialog.css new file mode 100644 index 0000000000000000000000000000000000000000..f17232c3507df31d8fcdcdf9e2780dced5e80a3e --- /dev/null +++ b/packages/ui/src/components/dialog.css @@ -0,0 +1,181 @@ +/* [data-component="dialog-trigger"] { } */ + +[data-component="dialog-overlay"] { + position: fixed; + inset: 0; + z-index: 50; + background-color: hsl(from var(--background-base) h s l / 0.2); +} + +[data-component="dialog"] { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + + [data-slot="dialog-container"] { + position: relative; + z-index: 50; + width: min(calc(100vw - 16px), 640px); + height: min(calc(100vh - 16px), 512px); + display: flex; + flex-direction: column; + align-items: center; + justify-items: start; + overflow: visible; + + [data-slot="dialog-content"] { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + width: 100%; + max-height: 100%; + min-height: 280px; + overflow: auto; + pointer-events: auto; + + /* Hide scrollbar */ + scrollbar-width: none; + -ms-overflow-style: none; + &::-webkit-scrollbar { + display: none; + } + + /* padding: 8px; */ + /* padding: 8px 8px 0 8px; */ + border-radius: var(--radius-xl); + background: var(--surface-raised-stronger-non-alpha); + background-clip: padding-box; + box-shadow: var(--shadow-lg-border-base); + + [data-slot="dialog-header"] { + display: flex; + padding: 16px 20px; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + align-self: stretch; + + [data-slot="dialog-title"] { + color: var(--text-strong); + + /* text-16-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-large); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-x-large); /* 150% */ + letter-spacing: var(--letter-spacing-tight); + } + /* [data-slot="dialog-close-button"] {} */ + } + + [data-slot="dialog-description"] { + display: flex; + padding: 16px; + padding-left: 24px; + padding-top: 0; + margin-top: -8px; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + align-self: stretch; + + color: var(--text-base); + + /* text-14-regular */ + font-family: var(--font-family-sans); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); /* 142.857% */ + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="dialog-body"] { + width: 100%; + position: relative; + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + + &:focus-visible { + outline: none; + } + } + &:focus-visible { + outline: none; + } + } + } + + &[data-fit] { + [data-slot="dialog-container"] { + height: auto; + + [data-slot="dialog-content"] { + min-height: 0; + } + } + } + + &[data-size="large"] [data-slot="dialog-container"] { + width: min(calc(100vw - 32px), 800px); + height: min(calc(100vh - 32px), 600px); + } + + &[data-size="x-large"] [data-slot="dialog-container"] { + width: min(calc(100vw - 32px), 980px); + height: min(calc(100vh - 92px), 600px); + } +} + +[data-component="dialog"][data-transition] [data-slot="dialog-content"] { + animation: contentHide 100ms ease-in forwards; + + &[data-expanded] { + animation: contentShow 150ms ease-out; + } +} + +@keyframes overlayShow { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes overlayHide { + from { + opacity: 1; + } + to { + opacity: 0; + } +} +@keyframes contentShow { + from { + opacity: 0; + transform: scale(0.98); + } + to { + opacity: 1; + transform: scale(1); + } +} +@keyframes contentHide { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.98); + } +} diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..981e3f45d74f0ddab5d8e726f8c23bb1d63de4a8 --- /dev/null +++ b/packages/ui/src/components/dialog.tsx @@ -0,0 +1,72 @@ +import { Dialog as Kobalte } from "@kobalte/core/dialog" +import { ComponentProps, JSXElement, Match, ParentProps, Show, Switch } from "solid-js" +import { useI18n } from "../context/i18n" +import { IconButton } from "./icon-button" + +export interface DialogProps extends ParentProps { + title?: JSXElement + description?: JSXElement + action?: JSXElement + size?: "normal" | "large" | "x-large" + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] + fit?: boolean + transition?: boolean +} + +export function Dialog(props: DialogProps) { + const i18n = useI18n() + return ( +
+
+ { + const target = e.currentTarget as HTMLElement | null + const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null + if (autofocusEl) { + e.preventDefault() + autofocusEl.focus() + } + }} + > + +
+ + {props.title} + + + {props.action} + + + + +
+
+ + + {props.description} + + +
{props.children}
+
+
+
+ ) +} diff --git a/packages/ui/src/components/diff-changes.css b/packages/ui/src/components/diff-changes.css new file mode 100644 index 0000000000000000000000000000000000000000..a20780488713ebdab09ae1099f56c3cda5c80143 --- /dev/null +++ b/packages/ui/src/components/diff-changes.css @@ -0,0 +1,44 @@ +[data-component="diff-changes"] { + display: flex; + direction: ltr; + unicode-bidi: isolate; + gap: 8px; + justify-content: flex-end; + align-items: center; + + [data-slot="diff-changes-additions"] { + font-family: var(--font-family-mono); + font-feature-settings: var(--font-family-mono--font-feature-settings); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + text-align: right; + color: var(--text-diff-add-base); + } + + [data-slot="diff-changes-deletions"] { + font-family: var(--font-family-mono); + font-feature-settings: var(--font-family-mono--font-feature-settings); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + text-align: right; + color: var(--text-diff-delete-base); + } +} + +[data-component="diff-changes"][data-variant="bars"] { + width: 18px; + height: 14px; + flex-shrink: 0; + + svg { + display: block; + width: 100%; + height: 100%; + } +} diff --git a/packages/ui/src/components/diff-changes.stories.tsx b/packages/ui/src/components/diff-changes.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fe0ba6eb4f2e9850c9ec8ecd469a9e1cb6271ab1 --- /dev/null +++ b/packages/ui/src/components/diff-changes.stories.tsx @@ -0,0 +1,81 @@ +// @ts-nocheck +import * as mod from "./diff-changes" +import { create } from "../storybook/scaffold" +import { changes } from "../storybook/fixtures" + +const docs = `### Overview +Summarize additions/deletions as text or compact bars. + +Pair with \`Diff\`/\`DiffSSR\` to contextualize a change set. + +### API +- Required: \`changes\` as { additions, deletions } or an array of those objects. +- Optional: \`variant\` ("default" | "bars"). + +### Variants and states +- Default text summary or bar visualization. +- Handles zero-change state (renders nothing in default variant). + +### Behavior +- Aggregates arrays into total additions/deletions. + +### Accessibility +- Ensure surrounding context conveys meaning of the counts/bars. + +### Theming/tokens +- Uses \`data-component="diff-changes"\` and diff color tokens. + +` + +const story = create({ + title: "UI/DiffChanges", + mod, + args: { + changes, + variant: "default", + }, +}) + +export default { + title: "UI/DiffChanges", + id: "components-diff-changes", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + variant: { + control: "select", + options: ["default", "bars"], + }, + }, +} + +export const Default = story.Basic + +export const Bars = { + args: { + variant: "bars", + }, +} + +export const MultipleFiles = { + args: { + changes: [ + { additions: 4, deletions: 1 }, + { additions: 8, deletions: 3 }, + { additions: 2, deletions: 0 }, + ], + }, +} + +export const Zero = { + args: { + changes: { additions: 0, deletions: 0 }, + }, +} diff --git a/packages/ui/src/components/diff-changes.tsx b/packages/ui/src/components/diff-changes.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0794b972869f5cd1b896aa4eb88b65e297e47344 --- /dev/null +++ b/packages/ui/src/components/diff-changes.tsx @@ -0,0 +1,115 @@ +import { createMemo, For, Match, Show, Switch } from "solid-js" + +export function DiffChanges(props: { + class?: string + changes: { additions: number; deletions: number } | { additions: number; deletions: number }[] + variant?: "default" | "bars" +}) { + const variant = () => props.variant ?? "default" + + const additions = createMemo(() => + Array.isArray(props.changes) + ? props.changes.reduce((acc, diff) => acc + (diff.additions ?? 0), 0) + : props.changes.additions, + ) + const deletions = createMemo(() => + Array.isArray(props.changes) + ? props.changes.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0) + : props.changes.deletions, + ) + const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0)) + + const blockCounts = createMemo(() => { + const TOTAL_BLOCKS = 5 + + const adds = additions() ?? 0 + const dels = deletions() ?? 0 + + if (adds === 0 && dels === 0) { + return { added: 0, deleted: 0, neutral: TOTAL_BLOCKS } + } + + const total = adds + dels + + if (total < 5) { + const added = adds > 0 ? 1 : 0 + const deleted = dels > 0 ? 1 : 0 + const neutral = TOTAL_BLOCKS - added - deleted + return { added, deleted, neutral } + } + + const ratio = adds > dels ? adds / dels : dels / adds + let BLOCKS_FOR_COLORS = TOTAL_BLOCKS + + if (total < 20) { + BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1 + } else if (ratio < 4) { + BLOCKS_FOR_COLORS = TOTAL_BLOCKS - 1 + } + + const percentAdded = adds / total + const percentDeleted = dels / total + + const added_raw = percentAdded * BLOCKS_FOR_COLORS + const deleted_raw = percentDeleted * BLOCKS_FOR_COLORS + + let added = adds > 0 ? Math.max(1, Math.round(added_raw)) : 0 + let deleted = dels > 0 ? Math.max(1, Math.round(deleted_raw)) : 0 + + // Cap bars based on actual change magnitude + if (adds > 0 && adds <= 5) added = Math.min(added, 1) + if (adds > 5 && adds <= 10) added = Math.min(added, 2) + if (dels > 0 && dels <= 5) deleted = Math.min(deleted, 1) + if (dels > 5 && dels <= 10) deleted = Math.min(deleted, 2) + + let total_allocated = added + deleted + if (total_allocated > BLOCKS_FOR_COLORS) { + if (added_raw > deleted_raw) { + added = BLOCKS_FOR_COLORS - deleted + } else { + deleted = BLOCKS_FOR_COLORS - added + } + total_allocated = added + deleted + } + + const neutral = Math.max(0, TOTAL_BLOCKS - total_allocated) + + return { added, deleted, neutral } + }) + + const ADD_COLOR = "var(--icon-diff-add-base)" + const DELETE_COLOR = "var(--icon-diff-delete-base)" + const NEUTRAL_COLOR = "var(--icon-weak-base)" + + const visibleBlocks = createMemo(() => { + const counts = blockCounts() + const blocks = [ + ...Array(counts.added).fill(ADD_COLOR), + ...Array(counts.deleted).fill(DELETE_COLOR), + ...Array(counts.neutral).fill(NEUTRAL_COLOR), + ] + return blocks.slice(0, 5) + }) + + return ( + 0 : true}> +
+ + + + + + {(color, i) => } + + + + + + {`+${additions()}`} + {`-${deletions()}`} + + +
+
+ ) +} diff --git a/packages/ui/src/components/dock-surface.css b/packages/ui/src/components/dock-surface.css new file mode 100644 index 0000000000000000000000000000000000000000..91fe5022648f157d8678905e4b492bb2cfb11b01 --- /dev/null +++ b/packages/ui/src/components/dock-surface.css @@ -0,0 +1,37 @@ +[data-dock-surface="shell"] { + background-color: var(--surface-raised-stronger-non-alpha); + box-shadow: var(--shadow-xs-border); + position: relative; + z-index: 10; + border-radius: 12px; + overflow: clip; +} + +[data-dock-border-underlay] { + /* Later shadows paint underneath, so this solid ring masks overlapping surfaces without changing border geometry. */ + box-shadow: + var(--dock-shell-visual-shadow, var(--shadow-xs-border)), + 0 0 0 var(--dock-shell-border-underlay-width, 1px) + var(--dock-shell-border-underlay-background, var(--surface-raised-stronger-non-alpha)); +} + +[data-dock-border-underlay="v2"] { + --dock-shell-visual-shadow: var(--v2-elevation-raised); + --dock-shell-border-underlay-width: 0.5px; + --dock-shell-border-underlay-background: var(--v2-background-bg-base); +} + +[data-dock-surface="tray"] { + background-color: var(--background-base); + border: 1px solid var(--border-weak-base); + position: relative; + z-index: 0; + border-radius: 12px; + overflow: clip; +} + +[data-dock-surface="tray"][data-dock-attach="top"] { + margin-top: -0.875rem; + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/packages/ui/src/components/dock-surface.tsx b/packages/ui/src/components/dock-surface.tsx new file mode 100644 index 0000000000000000000000000000000000000000..06cf2a5ebada22020871f86b85587fd3376928fc --- /dev/null +++ b/packages/ui/src/components/dock-surface.tsx @@ -0,0 +1,54 @@ +import { type ComponentProps, splitProps } from "solid-js" + +export interface DockTrayProps extends ComponentProps<"div"> { + attach?: "none" | "top" +} + +export function DockShell(props: ComponentProps<"div">) { + const [split, rest] = splitProps(props, ["children", "class", "classList"]) + return ( +
+ {split.children} +
+ ) +} + +export function DockShellForm(props: ComponentProps<"form">) { + const [split, rest] = splitProps(props, ["children", "class", "classList"]) + return ( +
+ {split.children} +
+ ) +} + +export function DockTray(props: DockTrayProps) { + const [split, rest] = splitProps(props, ["attach", "children", "class", "classList"]) + return ( +
+ {split.children} +
+ ) +} diff --git a/packages/ui/src/components/dropdown-menu.css b/packages/ui/src/components/dropdown-menu.css new file mode 100644 index 0000000000000000000000000000000000000000..f8a9fa60ee35a2ed25f0f58bb808873b97706153 --- /dev/null +++ b/packages/ui/src/components/dropdown-menu.css @@ -0,0 +1,135 @@ +[data-component="dropdown-menu-content"], +[data-component="dropdown-menu-sub-content"] { + min-width: 8rem; + overflow: hidden; + border-radius: var(--radius-md); + border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent); + background-clip: padding-box; + background-color: var(--surface-raised-stronger-non-alpha); + padding: 4px; + box-shadow: var(--shadow-md); + z-index: 50; + transform-origin: var(--kb-menu-content-transform-origin); + + &:focus, + &:focus-visible { + outline: none; + } + + &[data-closed] { + animation: dropdown-menu-close 0.15s ease-out; + } + + &[data-expanded] { + animation: dropdown-menu-open 0.15s ease-out; + } +} + +[data-component="dropdown-menu-content"], +[data-component="dropdown-menu-sub-content"] { + [data-slot="dropdown-menu-item"], + [data-slot="dropdown-menu-checkbox-item"], + [data-slot="dropdown-menu-radio-item"], + [data-slot="dropdown-menu-sub-trigger"] { + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border-radius: var(--radius-sm); + cursor: default; + user-select: none; + outline: none; + + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + color: var(--text-strong); + + &[data-highlighted] { + background: var(--surface-raised-base-hover); + } + + &[data-disabled] { + color: var(--text-weak); + pointer-events: none; + } + } + + [data-slot="dropdown-menu-checkbox-item"], + [data-slot="dropdown-menu-radio-item"] { + padding-inline-end: 28px; + } + + [data-slot="dropdown-menu-sub-trigger"] { + &[data-expanded] { + background: var(--surface-raised-base-hover); + } + } + + [data-slot="dropdown-menu-item-indicator"] { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + position: absolute; + inset-inline-end: 8px; + top: 50%; + transform: translateY(-50%); + } + + [data-slot="dropdown-menu-item-label"] { + flex: 1; + } + + [data-slot="dropdown-menu-item-description"] { + font-size: var(--font-size-x-small); + color: var(--text-weak); + } + + [data-slot="dropdown-menu-separator"] { + height: 1px; + margin: 4px -4px; + border-top-color: var(--border-weak-base); + } + + [data-slot="dropdown-menu-group-label"] { + display: block; + padding: 4px 8px; + font-family: var(--font-family-sans); + font-size: var(--font-size-x-small); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + color: var(--text-weak); + } + + [data-slot="dropdown-menu-arrow"] { + fill: var(--surface-raised-stronger-non-alpha); + } +} + +@keyframes dropdown-menu-open { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes dropdown-menu-close { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.96); + } +} diff --git a/packages/ui/src/components/dropdown-menu.stories.tsx b/packages/ui/src/components/dropdown-menu.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7a2d644fac3cf0f2ee5067f75fb1ea5bdd12eb93 --- /dev/null +++ b/packages/ui/src/components/dropdown-menu.stories.tsx @@ -0,0 +1,97 @@ +// @ts-nocheck +import * as mod from "./dropdown-menu" +import { Button } from "./button" + +const docs = `### Overview +Dropdown menu built on Kobalte with composable items, groups, and submenus. + +Use \`DropdownMenu.ItemLabel\`/\`ItemDescription\` for richer rows. + +### API +- Root accepts Kobalte DropdownMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`). +- Compose with \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections. + +### Variants and states +- Supports item groups, separators, and nested submenus. + +### Behavior +- Menu opens from trigger and renders in a portal by default. + +### Accessibility +- TODO: confirm keyboard navigation from Kobalte. + +### Theming/tokens +- Uses \`data-component="dropdown-menu"\` and slot attributes for styling. + +` + +export default { + title: "UI/DropdownMenu", + id: "components-dropdown-menu", + component: mod.DropdownMenu, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( + + + Open menu + + + + + Actions + + New file + + + Rename + Shift+R + + + + + More options + + + Duplicate + + + Move + + + + + + + ), +} + +export const CheckboxRadio = { + render: () => ( + + + Open menu + + + + Show line numbers + Wrap lines + + + Compact + Comfortable + + + + + ), +} diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..259cb791abba4fb0c3f066703528ff24b259454a --- /dev/null +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -0,0 +1,308 @@ +import { DropdownMenu as Kobalte } from "@kobalte/core/dropdown-menu" +import { splitProps } from "solid-js" +import type { ComponentProps, ParentProps } from "solid-js" + +export interface DropdownMenuProps extends ComponentProps {} +export interface DropdownMenuTriggerProps extends ComponentProps {} +export interface DropdownMenuIconProps extends ComponentProps {} +export interface DropdownMenuPortalProps extends ComponentProps {} +export interface DropdownMenuContentProps extends ComponentProps {} +export interface DropdownMenuArrowProps extends ComponentProps {} +export interface DropdownMenuSeparatorProps extends ComponentProps {} +export interface DropdownMenuGroupProps extends ComponentProps {} +export interface DropdownMenuGroupLabelProps extends ComponentProps {} +export interface DropdownMenuItemProps extends ComponentProps {} +export interface DropdownMenuItemLabelProps extends ComponentProps {} +export interface DropdownMenuItemDescriptionProps extends ComponentProps {} +export interface DropdownMenuItemIndicatorProps extends ComponentProps {} +export interface DropdownMenuRadioGroupProps extends ComponentProps {} +export interface DropdownMenuRadioItemProps extends ComponentProps {} +export interface DropdownMenuCheckboxItemProps extends ComponentProps {} +export interface DropdownMenuSubProps extends ComponentProps {} +export interface DropdownMenuSubTriggerProps extends ComponentProps {} +export interface DropdownMenuSubContentProps extends ComponentProps {} + +function DropdownMenuRoot(props: DropdownMenuProps) { + return +} + +function DropdownMenuTrigger(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuIcon(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuPortal(props: DropdownMenuPortalProps) { + return +} + +function DropdownMenuContent(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuArrow(props: DropdownMenuArrowProps) { + const [local, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) { + const [local, rest] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function DropdownMenuGroup(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuGroupLabel(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuItemLabel(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuItemDescription(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuItemIndicator(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuRadioGroup(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuRadioItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuCheckboxItem(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuSub(props: DropdownMenuSubProps) { + return +} + +function DropdownMenuSubTrigger(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +function DropdownMenuSubContent(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + + {local.children} + + ) +} + +export const DropdownMenu = Object.assign(DropdownMenuRoot, { + Trigger: DropdownMenuTrigger, + Icon: DropdownMenuIcon, + Portal: DropdownMenuPortal, + Content: DropdownMenuContent, + Arrow: DropdownMenuArrow, + Separator: DropdownMenuSeparator, + Group: DropdownMenuGroup, + GroupLabel: DropdownMenuGroupLabel, + Item: DropdownMenuItem, + ItemLabel: DropdownMenuItemLabel, + ItemDescription: DropdownMenuItemDescription, + ItemIndicator: DropdownMenuItemIndicator, + RadioGroup: DropdownMenuRadioGroup, + RadioItem: DropdownMenuRadioItem, + CheckboxItem: DropdownMenuCheckboxItem, + Sub: DropdownMenuSub, + SubTrigger: DropdownMenuSubTrigger, + SubContent: DropdownMenuSubContent, +}) diff --git a/packages/ui/src/components/favicon.stories.tsx b/packages/ui/src/components/favicon.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a693c0f4607a089cb0a6eb9c114fd7c05c99760b --- /dev/null +++ b/packages/ui/src/components/favicon.stories.tsx @@ -0,0 +1,49 @@ +// @ts-nocheck +import * as mod from "./favicon" + +const docs = `### Overview +Injects favicon and app icon meta tags for the document head. + +Render once near the app root (head management). + +### API +- No props. + +### Variants and states +- Single configuration. + +### Behavior +- Registers link and meta tags via Solid Meta components. + +### Accessibility +- Not applicable. + +### Theming/tokens +- Not applicable. + +` + +export default { + title: "UI/Favicon", + id: "components-favicon", + component: mod.Favicon, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( +
+ +
+ Head tags are injected for favicon and app icons. +
+
+ ), +} diff --git a/packages/ui/src/components/favicon.tsx b/packages/ui/src/components/favicon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..94cc82489fd9ec954f3193af02988f0bca2515c2 --- /dev/null +++ b/packages/ui/src/components/favicon.tsx @@ -0,0 +1,13 @@ +import { Link, Meta } from "@solidjs/meta" + +export const Favicon = () => { + return ( + <> + + + + + + + ) +} diff --git a/packages/ui/src/components/file-icon.css b/packages/ui/src/components/file-icon.css new file mode 100644 index 0000000000000000000000000000000000000000..5776425de3278ddae13ddcd1f0f5fe1decee1b7a --- /dev/null +++ b/packages/ui/src/components/file-icon.css @@ -0,0 +1,26 @@ +[data-component="file-icon"] { + display: block; + flex-shrink: 0; + width: 16px; + height: 16px; +} + +/* + File tree: show monochrome weak icons by default. + On hover, show the original file-type colors. +*/ +[data-component="filetree"] .filetree-icon--mono { + color: var(--icon-base); +} + +[data-component="filetree"] .filetree-iconpair { + position: relative; + display: inline-flex; + width: 16px; + height: 16px; +} + +[data-component="filetree"] .filetree-iconpair [data-component="file-icon"] { + position: absolute; + inset: 0; +} diff --git a/packages/ui/src/components/file-icon.stories.tsx b/packages/ui/src/components/file-icon.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..937328502f7ca40ac688b0f51a589dc1f5ded0d1 --- /dev/null +++ b/packages/ui/src/components/file-icon.stories.tsx @@ -0,0 +1,94 @@ +// @ts-nocheck +import * as mod from "./file-icon" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +File and folder icon renderer based on file name and extension. + +Use in file trees and lists. + +### API +- Required: \`node\` with \`path\` and \`type\`. +- Optional: \`expanded\` (for folders), \`mono\` for monochrome rendering. + +### Variants and states +- Folder vs file icons; expanded folder variant. + +### Behavior +- Maps file names and extensions to sprite icons. + +### Accessibility +- Provide adjacent text labels for filenames; icons are decorative. + +### Theming/tokens +- Uses \`data-component="file-icon"\` and sprite-based styling. + +` + +const story = create({ + title: "UI/FileIcon", + mod, + args: { + node: { path: "package.json", type: "file" }, + mono: true, + }, +}) + +export default { + title: "UI/FileIcon", + id: "components-file-icon", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const Folder = { + args: { + node: { path: "src", type: "directory" }, + expanded: true, + mono: false, + }, +} + +export const Samples = { + render: () => { + const items = [ + { path: "README.md", type: "file" }, + { path: "package.json", type: "file" }, + { path: "tsconfig.json", type: "file" }, + { path: "index.ts", type: "file" }, + { path: "styles.css", type: "file" }, + { path: "logo.svg", type: "file" }, + { path: "photo.png", type: "file" }, + { path: "Dockerfile", type: "file" }, + { path: ".env", type: "file" }, + { path: "src", type: "directory" }, + { path: "public", type: "directory" }, + ] as const + + return ( +
+ {items.map((node) => ( +
+ +
{node.path}
+
+ ))} +
+ ) + }, +} diff --git a/packages/ui/src/components/file-icon.tsx b/packages/ui/src/components/file-icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d66ee1c250888419e97db76066df4b995ba7a03f --- /dev/null +++ b/packages/ui/src/components/file-icon.tsx @@ -0,0 +1,588 @@ +import type { Component, JSX } from "solid-js" +import { createMemo, createUniqueId, splitProps, Show } from "solid-js" +import sprite from "./file-icons/sprite.svg" +import type { IconName } from "./file-icons/types" + +export type FileIconProps = JSX.GSVGAttributes & { + node: { path: string; type: "file" | "directory" } + expanded?: boolean + mono?: boolean +} + +export const FileIcon: Component = (props) => { + const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded", "mono"]) + const name = createMemo(() => chooseIconName(local.node.path, local.node.type, local.expanded || false)) + const id = `file-icon-mono-${createUniqueId()}` + return ( + + }> + + + + + + + + + ) +} + +type IconMaps = { + fileNames: Record + fileExtensions: Record + folderNames: Record + defaults: { + file: IconName + folder: IconName + folderOpen: IconName + } +} + +const ICON_MAPS: IconMaps = { + fileNames: { + // Documentation files + "readme.md": "Readme", + "changelog.md": "Changelog", + "contributing.md": "Contributing", + "conduct.md": "Conduct", + license: "Certificate", + authors: "Authors", + credits: "Credits", + install: "Installation", + + // Node.js files + "package.json": "Nodejs", + "package-lock.json": "Nodejs", + "yarn.lock": "Yarn", + "pnpm-lock.yaml": "Pnpm", + "bun.lock": "Bun", + "bun.lockb": "Bun", + "bunfig.toml": "Bun", + ".nvmrc": "Nodejs", + ".node-version": "Nodejs", + + // Docker files + dockerfile: "Docker", + "docker-compose.yml": "Docker", + "docker-compose.yaml": "Docker", + ".dockerignore": "Docker", + + // Config files + "jest.config.js": "Jest", + "jest.config.ts": "Jest", + "jest.config.mjs": "Jest", + "vitest.config.js": "Vitest", + "vitest.config.ts": "Vitest", + "tailwind.config.js": "Tailwindcss", + "tailwind.config.ts": "Tailwindcss", + "turbo.json": "Turborepo", + "tsconfig.json": "Tsconfig", + "jsconfig.json": "Jsconfig", + ".eslintrc": "Eslint", + ".eslintrc.js": "Eslint", + ".eslintrc.json": "Eslint", + ".prettierrc": "Prettier", + ".prettierrc.js": "Prettier", + ".prettierrc.json": "Prettier", + "vite.config.js": "Vite", + "vite.config.ts": "Vite", + "webpack.config.js": "Webpack", + "rollup.config.js": "Rollup", + "astro.config.mjs": "AstroConfig", + "astro.config.js": "AstroConfig", + "next.config.js": "Next", + "next.config.mjs": "Next", + "nuxt.config.js": "Nuxt", + "nuxt.config.ts": "Nuxt", + "svelte.config.js": "Svelte", + "gatsby-config.js": "Gatsby", + "remix.config.js": "Remix", + "prisma.schema": "Prisma", + ".gitignore": "Git", + ".gitattributes": "Git", + makefile: "Makefile", + cmake: "Cmake", + "cargo.toml": "Rust", + "go.mod": "GoMod", + "go.sum": "GoMod", + "requirements.txt": "Python", + "pyproject.toml": "Python", + pipfile: "Python", + "poetry.lock": "Poetry", + gemfile: "Gemfile", + rakefile: "Ruby", + "composer.json": "Php", + "build.gradle": "Gradle", + "pom.xml": "Maven", + "deno.json": "Deno", + "deno.jsonc": "Deno", + "vercel.json": "Vercel", + "netlify.toml": "Netlify", + ".env": "Tune", + ".env.local": "Tune", + ".env.development": "Tune", + ".env.production": "Tune", + ".env.example": "Tune", + ".editorconfig": "Editorconfig", + "robots.txt": "Robots", + "favicon.ico": "Favicon", + browserlist: "Browserlist", + ".babelrc": "Babel", + "babel.config.js": "Babel", + "gulpfile.js": "Gulp", + "gruntfile.js": "Grunt", + "capacitor.config.json": "Capacitor", + "ionic.config.json": "Ionic", + "angular.json": "Angular", + ".storybook": "Storybook", + "storybook.config.js": "Storybook", + "cypress.config.js": "Cypress", + "playwright.config.js": "Playwright", + "puppeteer.config.js": "Puppeteer", + "wrangler.toml": "Wrangler", + "firebase.json": "Firebase", + supabase: "Supabase", + terraform: "Terraform", + kubernetes: "Kubernetes", + ".gitpod.yml": "Gitpod", + ".devcontainer": "Vscode", + "travis.yml": "Travis", + "appveyor.yml": "Appveyor", + ".circleci": "Circleci", + "renovate.json": "Renovate", + "dependabot.yml": "Dependabot", + "lerna.json": "Lerna", + "nx.json": "Nx", + }, + fileExtensions: { + // Test files + "spec.ts": "TestTs", + "test.ts": "TestTs", + "spec.tsx": "TestJsx", + "test.tsx": "TestJsx", + "spec.js": "TestJs", + "test.js": "TestJs", + "spec.jsx": "TestJsx", + "test.jsx": "TestJsx", + + // JavaScript/TypeScript + "js.map": "JavascriptMap", + "d.ts": "TypescriptDef", + ts: "Typescript", + tsx: "React_ts", + js: "Javascript", + jsx: "React", + mjs: "Javascript", + cjs: "Javascript", + + // Web languages + html: "Html", + htm: "Html", + css: "Css", + scss: "Sass", + sass: "Sass", + less: "Less", + styl: "Stylus", + + // Data formats + json: "Json", + xml: "Xml", + yml: "Yaml", + yaml: "Yaml", + toml: "Toml", + hjson: "Hjson", + + // Documentation + md: "Markdown", + mdx: "Mdx", + tex: "Tex", + + // Programming languages + py: "Python", + pyx: "Python", + pyw: "Python", + rs: "Rust", + go: "Go", + java: "Java", + kt: "Kotlin", + scala: "Scala", + php: "Php", + rb: "Ruby", + cs: "Csharp", + vb: "Visualstudio", + cpp: "Cpp", + cc: "Cpp", + cxx: "Cpp", + c: "C", + h: "H", + hpp: "Hpp", + swift: "Swift", + m: "ObjectiveC", + mm: "ObjectiveCpp", + dart: "Dart", + lua: "Lua", + pl: "Perl", + r: "R", + jl: "Julia", + hs: "Haskell", + elm: "Elm", + ml: "Ocaml", + clj: "Clojure", + cljs: "Clojure", + erl: "Erlang", + ex: "Elixir", + exs: "Elixir", + nim: "Nim", + zig: "Zig", + v: "Vlang", + odin: "Odin", + gleam: "Gleam", + grain: "Grain", + roc: "Rocket", + fs: "Fsharp", + + // Shell scripts + sh: "Console", + bash: "Console", + zsh: "Console", + fish: "Console", + ps1: "Powershell", + + // Config/build files + cfg: "Settings", + ini: "Settings", + conf: "Settings", + properties: "Settings", + + // Media files + svg: "Svg", + png: "Image", + jpg: "Image", + jpeg: "Image", + gif: "Image", + webp: "Image", + bmp: "Image", + ico: "Favicon", + mp4: "Video", + mov: "Video", + avi: "Video", + webm: "Video", + mp3: "Audio", + wav: "Audio", + flac: "Audio", + + // Archive files + zip: "Zip", + tar: "Zip", + gz: "Zip", + rar: "Zip", + "7z": "Zip", + + // Document files + pdf: "Pdf", + doc: "Word", + docx: "Word", + ppt: "Powerpoint", + pptx: "Powerpoint", + xls: "Document", + xlsx: "Document", + + // Database files + sql: "Database", + db: "Database", + sqlite: "Database", + + // Other + env: "Tune", + log: "Log", + lock: "Lock", + key: "Key", + pem: "Certificate", + crt: "Certificate", + proto: "Proto", + graphql: "Graphql", + gql: "Graphql", + wasm: "Webassembly", + dockerfile: "Docker", + }, + folderNames: { + // Source code + src: "FolderSrc", + source: "FolderSrc", + lib: "FolderLib", + libs: "FolderLib", + + // Testing + test: "FolderTest", + tests: "FolderTest", + testing: "FolderTest", + spec: "FolderTest", + specs: "FolderTest", + __tests__: "FolderTest", + e2e: "FolderTest", + integration: "FolderTest", + unit: "FolderTest", + cypress: "FolderCypress", + + // Dependencies + node_modules: "FolderNode", + vendor: "FolderPackages", + packages: "FolderPackages", + deps: "FolderPackages", + + // Build/dist + build: "FolderBuildkite", + dist: "FolderDist", + out: "FolderDist", + output: "FolderDist", + target: "FolderTarget", + + // Configuration + config: "FolderConfig", + configs: "FolderConfig", + configuration: "FolderConfig", + settings: "FolderConfig", + env: "FolderEnvironment", + environments: "FolderEnvironment", + + // Docker + docker: "FolderDocker", + dockerfiles: "FolderDocker", + containers: "FolderDocker", + + // Documentation + docs: "FolderDocs", + doc: "FolderDocs", + documentation: "FolderDocs", + readme: "FolderDocs", + + // Public/assets + public: "FolderPublic", + static: "FolderPublic", + assets: "FolderImages", + images: "FolderImages", + img: "FolderImages", + icons: "FolderImages", + media: "FolderImages", + fonts: "FolderFont", + styles: "FolderCss", + stylesheets: "FolderCss", + css: "FolderCss", + sass: "FolderSass", + scss: "FolderSass", + less: "FolderLess", + + // Scripts + scripts: "FolderScripts", + script: "FolderScripts", + tools: "FolderTools", + utils: "FolderUtils", + utilities: "FolderUtils", + helpers: "FolderHelper", + + // Framework specific + components: "FolderComponents", + component: "FolderComponents", + views: "FolderViews", + view: "FolderViews", + layouts: "FolderLayout", + layout: "FolderLayout", + templates: "FolderTemplate", + template: "FolderTemplate", + hooks: "FolderHook", + hook: "FolderHook", + store: "FolderStore", + stores: "FolderStore", + state: "FolderNgrxStore", + reducers: "FolderReduxReducer", + reducer: "FolderReduxReducer", + services: "FolderApi", + service: "FolderApi", + api: "FolderApi", + apis: "FolderApi", + routes: "FolderRoutes", + route: "FolderRoutes", + routing: "FolderRoutes", + middleware: "FolderMiddleware", + middlewares: "FolderMiddleware", + controllers: "FolderController", + controller: "FolderController", + models: "FolderDatabase", + model: "FolderDatabase", + schemas: "FolderDatabase", + schema: "FolderDatabase", + migrations: "FolderDatabase", + migration: "FolderDatabase", + seeders: "FolderSeeders", + seeder: "FolderSeeders", + + // TypeScript + types: "FolderTypescript", + typing: "FolderTypescript", + typings: "FolderTypescript", + "@types": "FolderTypescript", + interfaces: "FolderInterface", + interface: "FolderInterface", + + // Mobile + android: "FolderAndroid", + ios: "FolderIos", + mobile: "FolderMobile", + flutter: "FolderFlutter", + + // Infrastructure + kubernetes: "FolderKubernetes", + k8s: "FolderKubernetes", + terraform: "FolderTerraform", + aws: "FolderAws", + azure: "FolderAzurePipelines", + firebase: "FolderFirebase", + supabase: "FolderSupabase", + vercel: "FolderVercel", + netlify: "FolderNetlify", + + // CI/CD + ".github": "FolderGithub", + ".gitlab": "FolderGitlab", + ".circleci": "FolderCircleci", + ci: "FolderCi", + ".ci": "FolderCi", + workflows: "FolderGhWorkflows", + + // Git + ".git": "FolderGit", + + // Development tools + ".vscode": "FolderVscode", + ".idea": "FolderIntellij", + ".cursor": "FolderCursor", + ".devcontainer": "FolderContainer", + ".storybook": "FolderStorybook", + + // Localization + i18n: "FolderI18n", + locales: "FolderI18n", + locale: "FolderI18n", + lang: "FolderI18n", + languages: "FolderI18n", + + // Other common patterns + temp: "FolderTemp", + tmp: "FolderTemp", + logs: "FolderLog", + log: "FolderLog", + backup: "FolderBackup", + backups: "FolderBackup", + examples: "FolderExamples", + example: "FolderExamples", + demo: "FolderExamples", + demos: "FolderExamples", + samples: "FolderExamples", + sample: "FolderExamples", + fixtures: "FolderTest", + mocks: "FolderMock", + mock: "FolderMock", + data: "FolderDatabase", + database: "FolderDatabase", + db: "FolderDatabase", + sql: "FolderDatabase", + prisma: "FolderPrisma", + drizzle: "FolderDrizzle", + + // Security + security: "FolderSecure", + auth: "FolderSecure", + authentication: "FolderSecure", + authorization: "FolderSecure", + keys: "FolderKeys", + certs: "FolderKeys", + certificates: "FolderKeys", + + // Content + content: "FolderContent", + posts: "FolderContent", + articles: "FolderContent", + blog: "FolderContent", + + // Functions + functions: "FolderFunctions", + function: "FolderFunctions", + lambda: "FolderFunctions", + lambdas: "FolderFunctions", + serverless: "FolderServerless", + + // Jobs/tasks + jobs: "FolderJob", + job: "FolderJob", + tasks: "FolderTasks", + task: "FolderTasks", + cron: "FolderTasks", + queue: "FolderQueue", + queues: "FolderQueue", + + // Desktop platforms + desktop: "FolderDesktop", + windows: "FolderWindows", + macos: "FolderMacos", + linux: "FolderLinux", + }, + defaults: { + file: "Document", + folder: "Folder", + folderOpen: "FolderOpen", + }, +} + +const toOpenVariant = (icon: IconName): IconName => { + if (!icon.startsWith("Folder")) return icon + if (icon.endsWith("_light")) return icon.replace("_light", "Open_light") as IconName + if (!icon.endsWith("Open")) return (icon + "Open") as IconName + return icon +} + +const basenameOf = (p: string) => p.split("\\").join("/").split("/").filter(Boolean).pop() ?? "" + +const folderNameVariants = (name: string) => { + const n = name.toLowerCase() + return [n, `.${n}`, `_${n}`, `__${n}__`] +} + +const dottedSuffixesDesc = (name: string) => { + const n = name.toLowerCase() + const idxs: number[] = [] + for (let i = 0; i < n.length; i++) if (n[i] === ".") idxs.push(i) + const out = new Set() + out.add(n) // allow exact whole-name "extensions" like "dockerfile" + for (const i of idxs) if (i + 1 < n.length) out.add(n.slice(i + 1)) + return Array.from(out).sort((a, b) => b.length - a.length) // longest first +} + +export function chooseIconName(path: string, type: "directory" | "file", expanded: boolean): IconName { + const base = basenameOf(path) + const baseLower = base.toLowerCase() + + if (type === "directory") { + for (const cand of folderNameVariants(baseLower)) { + const icon = ICON_MAPS.folderNames[cand] + if (icon) return expanded ? toOpenVariant(icon) : icon + } + return expanded ? ICON_MAPS.defaults.folderOpen : ICON_MAPS.defaults.folder + } + + const byName = ICON_MAPS.fileNames[baseLower] + if (byName) return byName + + for (const ext of dottedSuffixesDesc(baseLower)) { + const icon = ICON_MAPS.fileExtensions[ext] + if (icon) return icon + } + + return ICON_MAPS.defaults.file +} diff --git a/packages/ui/src/components/file-icons/sprite.svg b/packages/ui/src/components/file-icons/sprite.svg new file mode 100644 index 0000000000000000000000000000000000000000..619b2b58cace6ed6d84b0f37c20b1c6e7b8b0ce0 --- /dev/null +++ b/packages/ui/src/components/file-icons/sprite.svg @@ -0,0 +1,11707 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/file-icons/types.ts b/packages/ui/src/components/file-icons/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b47661f9b8c3bab7ffa55ad4ca9b2c91b3f5172 --- /dev/null +++ b/packages/ui/src/components/file-icons/types.ts @@ -0,0 +1,1095 @@ +// This file is generated by icon spritesheet generator + +export const iconNames = [ + "Zip", + "Zig", + "Zeabur_light", + "Zeabur", + "Yarn", + "Yang", + "Yaml", + "Xml", + "Xmake", + "Xaml", + "Wxt", + "Wrangler", + "Word", + "Wolframlanguage", + "Windicss", + "Werf", + "Wepy", + "Webpack", + "Webhint", + "Webassembly", + "Watchman", + "Wally", + "Wallaby", + "Wakatime_light", + "Wakatime", + "VuexStore", + "Vue", + "VueConfig", + "Vscode", + "Vlang", + "Vitest", + "Vite", + "Visualstudio", + "Virtual", + "Vim", + "Video", + "Vfl", + "Verilog", + "Verified", + "Verdaccio", + "Vercel_light", + "Vercel", + "Velocity", + "Velite", + "Vedic", + "Varnish", + "VanillaExtract", + "Vala", + "Vagrant", + "Uv", + "Url", + "Unocss", + "Unity", + "Uml_light", + "Uml", + "Umi", + "Typst", + "Typescript", + "TypescriptDef", + "Twine", + "Twig", + "Turborepo_light", + "Turborepo", + "Tune", + "Tsil", + "Tsdoc", + "Tsconfig", + "Trigger", + "Tree", + "Travis", + "Toml_light", + "Toml", + "Todo", + "Tobimake", + "Tobi", + "Tldraw_light", + "Tldraw", + "Tilt", + "Textlint", + "Tex", + "TestTs", + "TestJsx", + "TestJs", + "Terraform", + "Template", + "Templ", + "Teal", + "Tcl", + "Taze", + "Tauri", + "Taskfile", + "Tailwindcss", + "Table", + "Systemd_light", + "Systemd", + "Syncpack", + "Swift", + "Swc", + "Sway", + "Swagger", + "Svgr", + "Svgo", + "Svg", + "Svelte", + "Supabase", + "Subtitles", + "Sublime", + "Stylus", + "Stylelint_light", + "Stylelint", + "Stylable", + "Stryker", + "Storybook", + "Stitches_light", + "Stitches", + "Stencil", + "Steadybit", + "Stan", + "Stackblitz", + "Spwn", + "Sonarcloud", + "Solidity", + "Snyk", + "Snowpack_light", + "Snowpack", + "Snapcraft", + "Snakemake", + "Sml", + "Smarty", + "Slug", + "Slint", + "Slim", + "Sketch", + "Siyuan", + "Simulink", + "Silverstripe", + "Shader", + "Settings", + "Serverless", + "Sequelize", + "Sentry", + "Semgrep", + "SemanticRelease_light", + "SemanticRelease", + "Search", + "Screwdriver", + "Scons_light", + "Scons", + "Scheme", + "Scala", + "Sbt", + "Sass", + "Sas", + "San", + "Salesforce", + "Rust", + "Ruff", + "Ruby", + "Rubocop_light", + "Rubocop", + "Rspec", + "Routing", + "Rome", + "Rollup", + "Rojo", + "Rocket", + "Robots", + "Robot", + "Roblox", + "Roadmap", + "Riot", + "Restql", + "Rescript", + "RescriptInterface", + "Replit", + "Renovate", + "Remix_light", + "Remix", + "Remark", + "Regedit", + "ReduxStore", + "ReduxSelector", + "ReduxReducer", + "ReduxAction", + "Red", + "Reason", + "Readme", + "React_ts", + "React", + "Rc", + "Rbxmk", + "Razor", + "Raml", + "Racket", + "R", + "Qwik", + "Quokka", + "Quasar", + "Quarto", + "Qsharp", + "Pytorch", + "Python", + "PythonMisc", + "Purescript", + "Puppeteer", + "Puppet", + "Pug", + "Protractor", + "Proto", + "Prompt", + "Prolog", + "Processing", + "Prisma", + "Prettier", + "PreCommit", + "Powershell", + "Powerpoint", + "Posthtml", + "Postcss", + "Poetry", + "Pnpm_light", + "Pnpm", + "Pm2Ecosystem", + "Plop", + "Playwright", + "Plastic", + "Pkl", + "Pipeline", + "Pinejs", + "Phpunit", + "Phpstan", + "Php_elephant_pink", + "Php_elephant", + "Php", + "PhpCsFixer", + "Perl", + "Percy", + "Pdm", + "Pdf", + "Payload_light", + "Payload", + "Pawn", + "Pascal", + "Parcel", + "Panda", + "Palette", + "Packship", + "Oxlint", + "Otne", + "Openapi_light", + "Openapi", + "Opam", + "Opa", + "Odin", + "Ocaml", + "ObjectiveCpp", + "ObjectiveC", + "Nx", + "Nuxt", + "Nunjucks", + "Nuget", + "Npm", + "Nodemon", + "Nodejs_alt", + "Nodejs", + "Nix", + "Nim", + "NgrxState", + "NgrxSelectors", + "NgrxReducer", + "NgrxEntity", + "NgrxEffects", + "NgrxActions", + "Nginx", + "Next_light", + "Next", + "Netlify_light", + "Netlify", + "Nest", + "Ndst", + "NanoStaged_light", + "NanoStaged", + "Mxml", + "Moonscript", + "Moon", + "Mojo", + "Modernizr", + "Mocha", + "Mjml", + "Mint", + "Minecraft", + "MinecraftFabric", + "Meson", + "Mermaid", + "Merlin", + "Mercurial", + "Mdx", + "Mdsvex", + "Maven", + "Matlab", + "Mathematica", + "Markojs", + "Markdownlint", + "Markdown", + "Markdoc", + "MarkdocConfig", + "Makefile", + "Lyric", + "Luau", + "Lua", + "Lottie", + "Lolcode", + "Log", + "Lock", + "Livescript", + "Lisp", + "Liquid", + "Lintstaged", + "Lilypond", + "Lighthouse", + "Lib", + "Liara", + "Less", + "Lerna", + "Lefthook", + "Lbx", + "Latexmk", + "Laravel", + "Label", + "Kusto", + "Kubernetes", + "Kotlin", + "Knip", + "Kl", + "Kivy", + "Keystatic", + "Key", + "Kcl", + "Karma", + "Just", + "Jupyter", + "Julia", + "Jsr_light", + "Jsr", + "Json", + "Jsconfig", + "Jinja_light", + "Jinja", + "Jest", + "Jenkins", + "Javascript", + "JavascriptMap", + "Javaclass", + "Java", + "Jar", + "Istanbul", + "Ionic", + "Installation", + "Imba", + "Image", + "IfanrCloud", + "Idris", + "I18n", + "Husky", + "Hurl", + "Huff_light", + "Huff", + "Http", + "Html", + "Hpp", + "Hosts_light", + "Hosts", + "Horusec", + "Hjson", + "Histoire", + "Hex", + "Heroku", + "Helm", + "Hcl_light", + "Hcl", + "Haxe", + "Haskell", + "Harmonix", + "Hardhat", + "Handlebars", + "Haml", + "Hadolint", + "Hack", + "H", + "Gulp", + "Grunt", + "Groovy", + "Gridsome", + "Graphql", + "Graphcool", + "Grain", + "GrafanaAlloy", + "Gradle", + "Godot", + "GodotAssets", + "Go_gopher", + "Go", + "GoMod", + "Gnuplot", + "Gleam", + "Gitpod", + "Gitlab", + "GithubSponsors", + "GithubActionsWorkflow", + "Git", + "Gemini", + "GeminiAi", + "Gemfile", + "Gcp", + "Gatsby", + "Garden", + "Gamemaker", + "Fusebox", + "Fsharp", + "Freemarker", + "Foxpro", + "Fortran", + "Forth", + "Font", + "Folder", + "FolderZeabur", + "FolderZeaburOpen", + "FolderYarn", + "FolderYarnOpen", + "FolderWordpress", + "FolderWordpressOpen", + "FolderWindows", + "FolderWindowsOpen", + "FolderWebpack", + "FolderWebpackOpen", + "FolderWakatime", + "FolderWakatimeOpen", + "FolderVuexStore", + "FolderVuexStoreOpen", + "FolderVuepress", + "FolderVuepressOpen", + "FolderVue", + "FolderVueOpen", + "FolderVueDirectives", + "FolderVueDirectivesOpen", + "FolderVscode", + "FolderVscodeOpen", + "FolderVm", + "FolderVmOpen", + "FolderViews", + "FolderViewsOpen", + "FolderVideo", + "FolderVideoOpen", + "FolderVerdaccio", + "FolderVerdaccioOpen", + "FolderVercel", + "FolderVercelOpen", + "FolderUtils", + "FolderUtilsOpen", + "FolderUpload", + "FolderUploadOpen", + "FolderUpdate", + "FolderUpdateOpen", + "FolderUnity", + "FolderUnityOpen", + "FolderUi", + "FolderUiOpen", + "FolderTypescript", + "FolderTypescriptOpen", + "FolderTurborepo", + "FolderTurborepoOpen", + "FolderTrigger", + "FolderTriggerOpen", + "FolderTrash", + "FolderTrashOpen", + "FolderTools", + "FolderToolsOpen", + "FolderTheme", + "FolderThemeOpen", + "FolderTest", + "FolderTestOpen", + "FolderTerraform", + "FolderTerraformOpen", + "FolderTemplate", + "FolderTemplateOpen", + "FolderTemp", + "FolderTempOpen", + "FolderTelevision", + "FolderTelevisionOpen", + "FolderTasks", + "FolderTasksOpen", + "FolderTaskfile", + "FolderTaskfileOpen", + "FolderTarget", + "FolderTargetOpen", + "FolderSyntax", + "FolderSyntaxOpen", + "FolderSvg", + "FolderSvgOpen", + "FolderSvelte", + "FolderSvelteOpen", + "FolderSupabase", + "FolderSupabaseOpen", + "FolderSublime", + "FolderSublimeOpen", + "FolderStylus", + "FolderStylusOpen", + "FolderStorybook", + "FolderStorybookOpen", + "FolderStore", + "FolderStoreOpen", + "FolderStencil", + "FolderStencilOpen", + "FolderStack", + "FolderStackOpen", + "FolderSrc", + "FolderSrcTauri", + "FolderSrcTauriOpen", + "FolderSrcOpen", + "FolderSnippet", + "FolderSnippetOpen", + "FolderSnapcraft", + "FolderSnapcraftOpen", + "FolderShared", + "FolderSharedOpen", + "FolderShader", + "FolderShaderOpen", + "FolderServerless", + "FolderServerlessOpen", + "FolderServer", + "FolderServerOpen", + "FolderSeeders", + "FolderSeedersOpen", + "FolderSecure", + "FolderSecureOpen", + "FolderScripts", + "FolderScriptsOpen", + "FolderScons", + "FolderSconsOpen", + "FolderScala", + "FolderScalaOpen", + "FolderSass", + "FolderSassOpen", + "FolderSandbox", + "FolderSandboxOpen", + "FolderRust", + "FolderRustOpen", + "FolderRules", + "FolderRulesOpen", + "FolderRoutes", + "FolderRoutesOpen", + "FolderRobot", + "FolderRobotOpen", + "FolderReview", + "FolderReviewOpen", + "FolderResource", + "FolderResourceOpen", + "FolderResolver", + "FolderResolverOpen", + "FolderRepository", + "FolderRepositoryOpen", + "FolderReduxReducer", + "FolderReduxReducerOpen", + "FolderReactComponents", + "FolderReactComponentsOpen", + "FolderQueue", + "FolderQueueOpen", + "FolderQuasar", + "FolderQuasarOpen", + "FolderPytorch", + "FolderPytorchOpen", + "FolderPython", + "FolderPythonOpen", + "FolderPublic", + "FolderPublicOpen", + "FolderProto", + "FolderProtoOpen", + "FolderPrompts", + "FolderPromptsOpen", + "FolderProject", + "FolderProjectOpen", + "FolderPrivate", + "FolderPrivateOpen", + "FolderPrisma", + "FolderPrismaOpen", + "FolderPowershell", + "FolderPowershellOpen", + "FolderPolicy", + "FolderPolicyOpen", + "FolderPlugin", + "FolderPluginOpen", + "FolderPlastic", + "FolderPlasticOpen", + "FolderPipe", + "FolderPipeOpen", + "FolderPhpmailer", + "FolderPhpmailerOpen", + "FolderPhp", + "FolderPhpOpen", + "FolderPdm", + "FolderPdmOpen", + "FolderPdf", + "FolderPdfOpen", + "FolderPackages", + "FolderPackagesOpen", + "FolderOther", + "FolderOtherOpen", + "FolderOrganism", + "FolderOrganismOpen", + "FolderOpen", + "FolderObsidian", + "FolderObsidianOpen", + "FolderNuxt", + "FolderNuxtOpen", + "FolderNode", + "FolderNodeOpen", + "FolderNgrxStore", + "FolderNgrxStoreOpen", + "FolderNext", + "FolderNextOpen", + "FolderNetlify", + "FolderNetlifyOpen", + "FolderMoon", + "FolderMoonOpen", + "FolderMolecule", + "FolderMoleculeOpen", + "FolderMojo", + "FolderMojoOpen", + "FolderMock", + "FolderMockOpen", + "FolderMobile", + "FolderMobileOpen", + "FolderMjml", + "FolderMjmlOpen", + "FolderMiddleware", + "FolderMiddlewareOpen", + "FolderMeta", + "FolderMetaOpen", + "FolderMessages", + "FolderMessagesOpen", + "FolderMercurial", + "FolderMercurialOpen", + "FolderMarkdown", + "FolderMarkdownOpen", + "FolderMappings", + "FolderMappingsOpen", + "FolderMail", + "FolderMailOpen", + "FolderMacos", + "FolderMacosOpen", + "FolderLuau", + "FolderLuauOpen", + "FolderLua", + "FolderLuaOpen", + "FolderLottie", + "FolderLottieOpen", + "FolderLog", + "FolderLogOpen", + "FolderLiquibase", + "FolderLiquibaseOpen", + "FolderLinux", + "FolderLinuxOpen", + "FolderLink", + "FolderLinkOpen", + "FolderLib", + "FolderLibOpen", + "FolderLess", + "FolderLessOpen", + "FolderLefthook", + "FolderLefthookOpen", + "FolderLayout", + "FolderLayoutOpen", + "FolderKusto", + "FolderKustoOpen", + "FolderKubernetes", + "FolderKubernetesOpen", + "FolderKeys", + "FolderKeysOpen", + "FolderJupyter", + "FolderJupyterOpen", + "FolderJson", + "FolderJsonOpen", + "FolderJob", + "FolderJobOpen", + "FolderJinja_light", + "FolderJinja", + "FolderJinjaOpen_light", + "FolderJinjaOpen", + "FolderJavascript", + "FolderJavascriptOpen", + "FolderJava", + "FolderJavaOpen", + "FolderIos", + "FolderIosOpen", + "FolderInterface", + "FolderInterfaceOpen", + "FolderInterceptor", + "FolderInterceptorOpen", + "FolderIntellij_light", + "FolderIntellij", + "FolderIntellijOpen_light", + "FolderIntellijOpen", + "FolderInclude", + "FolderIncludeOpen", + "FolderImport", + "FolderImportOpen", + "FolderImages", + "FolderImagesOpen", + "FolderI18n", + "FolderI18nOpen", + "FolderHusky", + "FolderHuskyOpen", + "FolderHook", + "FolderHookOpen", + "FolderHome", + "FolderHomeOpen", + "FolderHelper", + "FolderHelperOpen", + "FolderHelm", + "FolderHelmOpen", + "FolderGulp", + "FolderGulpOpen", + "FolderGuard", + "FolderGuardOpen", + "FolderGraphql", + "FolderGraphqlOpen", + "FolderGradle", + "FolderGradleOpen", + "FolderGodot", + "FolderGodotOpen", + "FolderGlobal", + "FolderGlobalOpen", + "FolderGitlab", + "FolderGitlabOpen", + "FolderGithub", + "FolderGithubOpen", + "FolderGitea", + "FolderGiteaOpen", + "FolderGit", + "FolderGitOpen", + "FolderGhWorkflows", + "FolderGhWorkflowsOpen", + "FolderGenerator", + "FolderGeneratorOpen", + "FolderGamemaker", + "FolderGamemakerOpen", + "FolderFunctions", + "FolderFunctionsOpen", + "FolderForgejo", + "FolderForgejoOpen", + "FolderFont", + "FolderFontOpen", + "FolderFlutter", + "FolderFlutterOpen", + "FolderFlow", + "FolderFlowOpen", + "FolderFirestore", + "FolderFirestoreOpen", + "FolderFirebase", + "FolderFirebaseOpen", + "FolderFavicon", + "FolderFaviconOpen", + "FolderFastlane", + "FolderFastlaneOpen", + "FolderExport", + "FolderExportOpen", + "FolderExpo", + "FolderExpoOpen", + "FolderExamples", + "FolderExamplesOpen", + "FolderEvent", + "FolderEventOpen", + "FolderError", + "FolderErrorOpen", + "FolderEnvironment", + "FolderEnvironmentOpen", + "FolderEnum", + "FolderEnumOpen", + "FolderElement", + "FolderElementOpen", + "FolderDump", + "FolderDumpOpen", + "FolderDrizzle", + "FolderDrizzleOpen", + "FolderDownload", + "FolderDownloadOpen", + "FolderDocs", + "FolderDocsOpen", + "FolderDocker", + "FolderDockerOpen", + "FolderDist", + "FolderDistOpen", + "FolderDirective", + "FolderDirectiveOpen", + "FolderDesktop", + "FolderDesktopOpen", + "FolderDelta", + "FolderDeltaOpen", + "FolderDecorators", + "FolderDecoratorsOpen", + "FolderDebug", + "FolderDebugOpen", + "FolderDatabase", + "FolderDatabaseOpen", + "FolderDart", + "FolderDartOpen", + "FolderCypress", + "FolderCypressOpen", + "FolderCustom", + "FolderCustomOpen", + "FolderCursor_light", + "FolderCursor", + "FolderCursorOpen_light", + "FolderCursorOpen", + "FolderCss", + "FolderCssOpen", + "FolderCoverage", + "FolderCoverageOpen", + "FolderCore", + "FolderCoreOpen", + "FolderController", + "FolderControllerOpen", + "FolderContract", + "FolderContractOpen", + "FolderContext", + "FolderContextOpen", + "FolderContent", + "FolderContentOpen", + "FolderContainer", + "FolderContainerOpen", + "FolderConstant", + "FolderConstantOpen", + "FolderConsole", + "FolderConsoleOpen", + "FolderConnection", + "FolderConnectionOpen", + "FolderConfig", + "FolderConfigOpen", + "FolderComponents", + "FolderComponentsOpen", + "FolderCommand", + "FolderCommandOpen", + "FolderCobol", + "FolderCobolOpen", + "FolderCluster", + "FolderClusterOpen", + "FolderCloudflare", + "FolderCloudflareOpen", + "FolderCloudFunctions", + "FolderCloudFunctionsOpen", + "FolderCline", + "FolderClineOpen", + "FolderClient", + "FolderClientOpen", + "FolderClaude", + "FolderClaudeOpen", + "FolderClass", + "FolderClassOpen", + "FolderCircleci", + "FolderCircleciOpen", + "FolderCi", + "FolderCiOpen", + "FolderChangesets", + "FolderChangesetsOpen", + "FolderCart", + "FolderCartOpen", + "FolderBuildkite", + "FolderBuildkiteOpen", + "FolderBower", + "FolderBowerOpen", + "FolderBloc", + "FolderBlocOpen", + "FolderBlender", + "FolderBlenderOpen", + "FolderBicep", + "FolderBicepOpen", + "FolderBibliography", + "FolderBibliographyOpen", + "FolderBenchmark", + "FolderBenchmarkOpen", + "FolderBatch", + "FolderBatchOpen", + "FolderBase", + "FolderBaseOpen", + "FolderBackup", + "FolderBackupOpen", + "FolderAzurePipelines", + "FolderAzurePipelinesOpen", + "FolderAws", + "FolderAwsOpen", + "FolderAurelia", + "FolderAureliaOpen", + "FolderAudio", + "FolderAudioOpen", + "FolderAttachment", + "FolderAttachmentOpen", + "FolderAtom", + "FolderAtomOpen", + "FolderAstro", + "FolderAstroOpen", + "FolderArchive", + "FolderArchiveOpen", + "FolderApp", + "FolderAppOpen", + "FolderApollo", + "FolderApolloOpen", + "FolderApi", + "FolderApiOpen", + "FolderAnsible", + "FolderAnsibleOpen", + "FolderAnimation", + "FolderAnimationOpen", + "FolderAngular", + "FolderAngularOpen", + "FolderAndroid", + "FolderAndroidOpen", + "FolderAdmin", + "FolderAdminOpen", + "Flow", + "Flash", + "Firebase", + "Figma", + "Favicon", + "Fastlane", + "Exe", + "Excalidraw", + "Eslint", + "Esbuild", + "Erlang", + "Epub", + "Ember", + "Email", + "Elm", + "Elixir", + "Ejs", + "Editorconfig", + "Edge", + "Dune", + "Duc", + "Drone_light", + "Drone", + "Drizzle", + "Drawio", + "Dotjs", + "Document", + "DoctexInstaller", + "Docker", + "Dll", + "Django", + "Disc", + "Dinophp", + "Diff", + "Dhall", + "DependenciesUpdate", + "Dependabot", + "Deno_light", + "Deno", + "Denizenscript", + "Deepsource", + "Database", + "Dart_generated", + "Dart", + "D", + "Cypress", + "Cursor_light", + "Cursor", + "Cuda", + "Cucumber", + "Css", + "CssMap", + "Csharp", + "Crystal_light", + "Crystal", + "Credits", + "Craco", + "Cpp", + "Copilot_light", + "Copilot", + "Controller", + "Contributing", + "Context", + "Contentlayer", + "Console", + "Conduct", + "Concourse", + "Commitlint", + "Commitizen", + "Command", + "Coloredpetrinets", + "Coldfusion", + "Coffee", + "CoderabbitAi", + "Codeowners", + "Codecov", + "CodeClimate_light", + "CodeClimate", + "Coconut", + "Cobol", + "Coala", + "Cmake", + "Cloudfoundry", + "Clojure", + "Cline", + "Claude", + "Clangd", + "Citation", + "Circleci_light", + "Circleci", + "Chrome", + "Chess_light", + "Chess", + "Changelog", + "Certificate", + "Cds", + "Cbx", + "Capnp", + "Capacitor", + "Cake", + "Cairo", + "Cadence", + "Caddy", + "Cabal", + "C3", + "C", + "Bun_light", + "Bun", + "Buildkite", + "Bucklescript", + "Buck", + "Bruno", + "Browserlist_light", + "Browserlist", + "Brainfuck", + "Bower", + "Blitz", + "Blink_light", + "Blink", + "Blender", + "Bithound", + "Bitbucket", + "Biome", + "Bicep", + "BibtexStyle", + "Bibliography", + "BenchTs", + "BenchJsx", + "BenchJs", + "Beancount", + "Bbx", + "Bazel", + "Ballerina", + "Babel", + "Azure", + "AzurePipelines", + "Autoit", + "Autohotkey", + "Auto_light", + "Auto", + "Authors", + "Aurelia", + "Audio", + "Astyle", + "Astro", + "AstroConfig", + "Assembly", + "Asciidoc", + "Arduino", + "Architecture", + "Appveyor", + "AppsScript", + "Applescript", + "Apollo", + "Apiblueprint", + "Antlr", + "Angular", + "Android", + "Amplify", + "Advpl", + "Adonis", + "AdobeSwc", + "AdobePhotoshop_light", + "AdobePhotoshop", + "AdobeIllustrator_light", + "AdobeIllustrator", + "Ada", + "Actionscript", + "Abc", + "Abap", + "3d", +] as const + +export type IconName = (typeof iconNames)[number] diff --git a/packages/ui/src/components/font.stories.tsx b/packages/ui/src/components/font.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f4e90bde097c357b2524f8738be9663425f6e843 --- /dev/null +++ b/packages/ui/src/components/font.stories.tsx @@ -0,0 +1,48 @@ +// @ts-nocheck +import * as mod from "./font" + +const docs = `### Overview +Uses native system font stacks for sans and mono typography. + +Optional compatibility component. Existing roots can keep rendering it, but it does nothing. + +### API +- No props. + +### Variants and states +- No variants. + +### Behavior +- Compatibility wrapper only. No font assets are injected or preloaded. + +### Accessibility +- Not applicable. + +### Theming/tokens +- Theme tokens come from CSS variables, not this component. + +` + +export default { + title: "UI/Font", + id: "components-font", + component: mod.Font, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( +
+ +
OpenCode Sans Sample
+
OpenCode Mono Sample
+
+ ), +} diff --git a/packages/ui/src/components/font.tsx b/packages/ui/src/components/font.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f89dfafe1db32114fde53f9581a7c46992512092 --- /dev/null +++ b/packages/ui/src/components/font.tsx @@ -0,0 +1 @@ +export const Font = () => null diff --git a/packages/ui/src/components/icon-button.css b/packages/ui/src/components/icon-button.css new file mode 100644 index 0000000000000000000000000000000000000000..7a47270fe9ae2216ff286f378d42fb04f911e6f0 --- /dev/null +++ b/packages/ui/src/components/icon-button.css @@ -0,0 +1,181 @@ +[data-component="icon-button"] { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + text-decoration: none; + user-select: none; + aspect-ratio: 1; + flex-shrink: 0; + + &[data-variant="primary"] { + background-color: var(--icon-strong-base); + + [data-slot="icon-svg"] { + /* color: var(--icon-weak-base); */ + color: var(--icon-invert-base); + + /* &:hover:not(:disabled) { */ + /* color: var(--icon-weak-hover); */ + /* } */ + /* &:active:not(:disabled) { */ + /* color: var(--icon-strong-active); */ + /* } */ + } + + &:hover:not(:disabled) { + background-color: var(--icon-strong-hover); + } + &:focus:not(:disabled) { + background-color: var(--icon-strong-focus); + } + &:active:not(:disabled) { + background-color: var(--icon-strong-active); + } + &:disabled { + background-color: var(--icon-strong-disabled); + + [data-slot="icon-svg"] { + color: var(--icon-invert-base); + } + } + } + + &[data-variant="secondary"] { + border: transparent; + background-color: var(--button-secondary-base); + color: var(--text-strong); + box-shadow: var(--shadow-xs-border-base); + + &:hover:not(:disabled) { + background-color: var(--button-secondary-hover); + } + &:focus:not(:disabled) { + background-color: var(--button-secondary-base); + } + &:focus-visible:not(:active) { + background-color: var(--button-secondary-base); + box-shadow: var(--shadow-xs-border-focus); + } + &:focus-visible:active { + box-shadow: none; + } + &:active:not(:disabled) { + background-color: var(--button-secondary-base); + } + + [data-slot="icon-svg"] { + color: var(--icon-strong-base); + } + + &:disabled { + background-color: var(--icon-strong-disabled); + color: var(--icon-invert-base); + cursor: not-allowed; + } + } + + &[data-variant="ghost"] { + background-color: transparent; + /* color: var(--icon-base); */ + + [data-slot="icon-svg"] { + color: var(--icon-base); + } + + &:hover:not(:disabled) { + background-color: var(--surface-base-hover); + + /* [data-slot="icon-svg"] { */ + /* color: var(--icon-hover); */ + /* } */ + } + &:focus-visible:not(:disabled) { + background-color: var(--surface-base-hover); + } + &:active:not(:disabled) { + background-color: var(--surface-base-active); + /* [data-slot="icon-svg"] { */ + /* color: var(--icon-active); */ + /* } */ + } + &:selected:not(:disabled) { + background-color: var(--surface-base-active); + /* [data-slot="icon-svg"] { */ + /* color: var(--icon-selected); */ + /* } */ + } + &:disabled { + color: var(--icon-invert-base); + cursor: not-allowed; + } + } + + &[data-size="normal"] { + width: 24px; + height: 24px; + + font-size: var(--font-size-small); + line-height: var(--line-height-large); + gap: calc(var(--spacing) * 0.5); + } + + &[data-size="small"] { + width: 20px; + height: 20px; + } + + &[data-size="large"] { + height: 32px; + /* padding: 0 8px 0 6px; */ + gap: 8px; + + /* text-12-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + } + + &:focus { + outline: none; + } +} + +@media (prefers-reduced-motion: no-preference) { + [data-component="icon-button"][data-icon="stop"] [data-slot="icon-svg"] rect { + transform-origin: center; + transform-box: fill-box; + animation: stop-pulse 1.8s ease-in-out infinite; + } +} + +@keyframes stop-pulse { + 0%, + 100% { + transform: scale(0.95); + } + 50% { + transform: scale(1.12); + } +} + +[data-component="icon-button"].titlebar-icon { + width: 32px; + height: 24px; + aspect-ratio: auto; +} + +[data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] { + background-color: var(--surface-raised-base-active); +} + +[data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] [data-slot="icon-svg"] { + color: var(--icon-strong-base); +} + +[data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"]:hover:not(:disabled) { + background-color: var(--surface-raised-base-active); +} diff --git a/packages/ui/src/components/icon-button.tsx b/packages/ui/src/components/icon-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..457283aa039c16793c94548bfb05f2b2b40bbf2c --- /dev/null +++ b/packages/ui/src/components/icon-button.tsx @@ -0,0 +1,29 @@ +import { Button as Kobalte } from "@kobalte/core/button" +import { type ComponentProps, splitProps } from "solid-js" +import { Icon, IconProps } from "./icon" + +export interface IconButtonProps extends ComponentProps { + icon: IconProps["name"] + size?: "small" | "normal" | "large" + iconSize?: IconProps["size"] + variant?: "primary" | "secondary" | "ghost" +} + +export function IconButton(props: ComponentProps<"button"> & IconButtonProps) { + const [split, rest] = splitProps(props, ["variant", "size", "iconSize", "class", "classList"]) + return ( + + + + ) +} diff --git a/packages/ui/src/components/image-preview.css b/packages/ui/src/components/image-preview.css new file mode 100644 index 0000000000000000000000000000000000000000..3c47f7a2584264196284fc6349ece2e70fd4eaa6 --- /dev/null +++ b/packages/ui/src/components/image-preview.css @@ -0,0 +1,63 @@ +[data-component="image-preview"] { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + + [data-slot="image-preview-container"] { + position: relative; + z-index: 50; + width: min(calc(100vw - 32px), 90vw); + max-width: 1200px; + height: min(calc(100vh - 32px), 90vh); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + [data-slot="image-preview-content"] { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + max-height: 100%; + border-radius: var(--radius-lg); + background: var(--surface-raised-stronger-non-alpha); + box-shadow: + 0 15px 45px 0 rgba(19, 16, 16, 0.35), + 0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25), + 0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2); + overflow: hidden; + + &:focus-visible { + outline: none; + } + + [data-slot="image-preview-header"] { + display: flex; + padding: 8px 8px 0; + justify-content: flex-end; + align-items: center; + align-self: stretch; + } + + [data-slot="image-preview-body"] { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + overflow: auto; + } + + [data-slot="image-preview-image"] { + max-width: 100%; + max-height: calc(90vh - 100px); + object-fit: contain; + border-radius: var(--radius-md); + } + } + } +} diff --git a/packages/ui/src/components/image-preview.stories.tsx b/packages/ui/src/components/image-preview.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f0a00c782519e37f037bcf318d62fc13ae83c8d6 --- /dev/null +++ b/packages/ui/src/components/image-preview.stories.tsx @@ -0,0 +1,59 @@ +// @ts-nocheck +import { onMount } from "solid-js" +import * as mod from "./image-preview" +import { Button } from "./button" +import { useDialog } from "../context/dialog" + +const docs = `### Overview +Image preview content intended to render inside the dialog stack. + +Use for full-size image inspection; keep images optimized. + +### API +- Required: \`src\`. +- Optional: \`alt\` text. + +### Variants and states +- Single layout with close action. + +### Behavior +- Intended to be used via \`useDialog().show\`. + +### Accessibility +- Uses localized aria-label for close button. + +### Theming/tokens +- Uses \`data-component="image-preview"\` and slot attributes. + +` + +export default { + title: "UI/ImagePreview", + id: "components-image-preview", + component: mod.ImagePreview, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => { + const dialog = useDialog() + const src = "https://placehold.co/640x360/png" + + const open = () => dialog.show(() => ) + + onMount(open) + + return ( + + ) + }, +} diff --git a/packages/ui/src/components/image-preview.tsx b/packages/ui/src/components/image-preview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8462ce2560bd957eef62c53959f26aecce538075 --- /dev/null +++ b/packages/ui/src/components/image-preview.tsx @@ -0,0 +1,32 @@ +import { Dialog as Kobalte } from "@kobalte/core/dialog" +import { useI18n } from "../context/i18n" +import { IconButton } from "./icon-button" + +export interface ImagePreviewProps { + src: string + alt?: string +} + +export function ImagePreview(props: ImagePreviewProps) { + const i18n = useI18n() + return ( +
+
+ +
+ +
+
+ {props.alt +
+
+
+
+ ) +} diff --git a/packages/ui/src/components/inline-input.css b/packages/ui/src/components/inline-input.css new file mode 100644 index 0000000000000000000000000000000000000000..503dfe2863c48ef9e9f39a08df4df07c6c2ff88c --- /dev/null +++ b/packages/ui/src/components/inline-input.css @@ -0,0 +1,17 @@ +[data-component="inline-input"] { + color: inherit; + background: transparent; + border: 0; + border-radius: var(--radius-md); + padding: 0; + min-width: 0; + font: inherit; + letter-spacing: inherit; + line-height: inherit; + box-sizing: border-box; + + &:focus { + outline: none; + box-shadow: var(--inline-input-shadow, 0 0 0 1px var(--border-interactive-focus)); + } +} diff --git a/packages/ui/src/components/inline-input.tsx b/packages/ui/src/components/inline-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0a603ee39488552d8579d44888501dc994ae98f --- /dev/null +++ b/packages/ui/src/components/inline-input.tsx @@ -0,0 +1,22 @@ +import type { ComponentProps } from "solid-js" +import { splitProps } from "solid-js" + +export type InlineInputProps = ComponentProps<"input"> & { + width?: string +} + +export function InlineInput(props: InlineInputProps) { + const [local, others] = splitProps(props, ["class", "width", "style"]) + + const style = () => { + if (!local.style) return { width: local.width } + if (typeof local.style === "string") { + if (!local.width) return local.style + return `${local.style};width:${local.width}` + } + if (!local.width) return local.style + return { ...local.style, width: local.width } + } + + return +} diff --git a/packages/ui/src/components/keybind.stories.tsx b/packages/ui/src/components/keybind.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a458a53a7421caf9d4c90bdc2abb845ce9b07dfe --- /dev/null +++ b/packages/ui/src/components/keybind.stories.tsx @@ -0,0 +1,43 @@ +// @ts-nocheck +import * as mod from "./keybind" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Keyboard shortcut pill for displaying keybindings. + +Pair with menu items or command palettes. + +### API +- Children render the key sequence text. +- Accepts standard span props. + +### Variants and states +- Single visual style. + +### Behavior +- Presentational only. + +### Accessibility +- Ensure text conveys the shortcut (e.g., "Cmd+K"). + +### Theming/tokens +- Uses \`data-component="keybind"\`. + +` + +const story = create({ title: "UI/Keybind", mod, args: { children: "Cmd+K" } }) +export default { + title: "UI/Keybind", + id: "components-keybind", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic diff --git a/packages/ui/src/components/list.stories.tsx b/packages/ui/src/components/list.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..280f323c0bd43d3ee801b8c8603d0f15ca25c86f --- /dev/null +++ b/packages/ui/src/components/list.stories.tsx @@ -0,0 +1,170 @@ +// @ts-nocheck +import * as mod from "./list" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Filterable list with keyboard navigation and optional search input. + +Use within panels or popovers where keyboard navigation is expected. + +### API +- Required: \`items\` and \`key\`. +- Required: \`children\` render function for items. +- Optional: \`search\`, \`filterKeys\`, \`groupBy\`, \`onSelect\`, \`onKeyEvent\`. + +### Variants and states +- Optional search bar and group headers. + +### Behavior +- Uses fuzzy search when \`search\` is enabled. +- Keyboard navigation via arrow keys; Enter selects. + +### Accessibility +- TODO: confirm ARIA roles for list items and search input. + +### Theming/tokens +- Uses \`data-component="list"\` and data slots for structure. + +` + +const story = create({ + title: "UI/List", + mod, + args: { + items: ["One", "Two", "Three", "Four"], + key: (x: string) => x, + children: (x: string) => x, + search: true, + }, +}) + +export default { + title: "UI/List", + id: "components-list", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const Grouped = { + render: () => { + const items = [ + { id: "a1", title: "Alpha", group: "Group A" }, + { id: "a2", title: "Bravo", group: "Group A" }, + { id: "b1", title: "Delta", group: "Group B" }, + ] + return ( + item.id} groupBy={(item) => item.group} search={true}> + {(item) => item.title} + + ) + }, +} + +export const Empty = { + render: () => ( + item} search={true}> + {(item) => item} + + ), +} + +export const WithAdd = { + render: () => ( + item} + search={true} + add={{ + render: () => ( + + ), + }} + > + {(item) => item} + + ), +} + +export const Divider = { + render: () => ( + item} divider={true}> + {(item) => item} + + ), +} + +export const ActiveIcon = { + render: () => ( + item} activeIcon="chevron-right"> + {(item) => item} + + ), +} + +export const NoSearch = { + render: () => ( + item} search={false}> + {(item) => item} + + ), +} + +export const SearchOptions = { + render: () => ( + item} + search={{ + placeholder: "Filter...", + hideIcon: true, + action: , + }} + > + {(item) => item} + + ), +} + +export const ItemWrapper = { + render: () => ( + item} + itemWrapper={(item, node) => ( +
{node}
+ )} + > + {(item) => item} +
+ ), +} + +export const GroupHeader = { + render: () => { + const items = [ + { id: "a1", title: "Alpha", group: "Group A" }, + { id: "b1", title: "Beta", group: "Group B" }, + ] + return ( + item.id} + groupBy={(item) => item.group} + groupHeader={(group) => {group.category}} + > + {(item) => item.title} + + ) + }, +} diff --git a/packages/ui/src/components/logo.css b/packages/ui/src/components/logo.css new file mode 100644 index 0000000000000000000000000000000000000000..a909782b7708d8803fd5127494754deffac1b476 --- /dev/null +++ b/packages/ui/src/components/logo.css @@ -0,0 +1,4 @@ +[data-component="logo-mark"] { + width: 16px; + aspect-ratio: 4/5; +} diff --git a/packages/ui/src/components/logo.stories.tsx b/packages/ui/src/components/logo.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3f5dd9cef738656622a23ec4f20c03cb0e1ba646 --- /dev/null +++ b/packages/ui/src/components/logo.stories.tsx @@ -0,0 +1,57 @@ +// @ts-nocheck +import * as mod from "./logo" + +const docs = `### Overview +OpenCode logo assets: mark, splash, and wordmark. + +Use Mark for compact spaces, Logo for headers, Splash for hero sections. + +### API +- \`Mark\`, \`Splash\`, and \`Logo\` components accept standard SVG props. + +### Variants and states +- Multiple logo variants for different contexts. + +### Behavior +- Pure SVG rendering. + +### Accessibility +- Provide title/aria-label when logos convey meaning. + +### Theming/tokens +- Uses theme color tokens via CSS variables. + +` + +export default { + title: "UI/Logo", + id: "components-logo", + component: mod.Logo, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( +
+
+
Mark
+ +
+
+
Splash
+ +
+
+
Logo
+ +
+
+ ), +} diff --git a/packages/ui/src/components/logo.tsx b/packages/ui/src/components/logo.tsx new file mode 100644 index 0000000000000000000000000000000000000000..35b4e44427b82b9debf3af7699327306cdfba96d --- /dev/null +++ b/packages/ui/src/components/logo.tsx @@ -0,0 +1,62 @@ +import { type ComponentProps } from "solid-js" + +export const Mark = (props: { class?: string }) => { + return ( + + + + + ) +} + +export const Splash = (props: Pick, "ref" | "class">) => { + return ( + + + + + ) +} + +export const Logo = (props: { class?: string }) => { + return ( + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/ui/src/components/progress-circle.css b/packages/ui/src/components/progress-circle.css new file mode 100644 index 0000000000000000000000000000000000000000..4d1e79cddc6767890d4afc77876d5ddfe3cb1f3a --- /dev/null +++ b/packages/ui/src/components/progress-circle.css @@ -0,0 +1,16 @@ +[data-component="progress-circle"] { + transform: rotate(-90deg); + + [data-slot="progress-circle-background"] { + stroke: var(--progress-circle-background, var(--border-weak-base)); + } + + [data-slot="progress-circle-background-overlay"] { + stroke: var(--progress-circle-background-overlay, transparent); + } + + [data-slot="progress-circle-progress"] { + stroke: var(--progress-circle-progress, var(--border-active)); + transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1); + } +} diff --git a/packages/ui/src/components/progress-circle.stories.tsx b/packages/ui/src/components/progress-circle.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5bc23c3108c3be2632377501562b65269b28befa --- /dev/null +++ b/packages/ui/src/components/progress-circle.stories.tsx @@ -0,0 +1,59 @@ +// @ts-nocheck +import * as mod from "./progress-circle" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Circular progress indicator for compact loading states. + +Pair with labels for clarity in dashboards. + +### API +- Required: \`percentage\` (0-100). +- Optional: \`size\`, \`strokeWidth\`. + +### Variants and states +- Single visual style; size and stroke width adjust appearance. + +### Behavior +- Percentage is clamped between 0 and 100. + +### Accessibility +- Use alongside text or aria-live messaging for progress context. + +### Theming/tokens +- Uses \`data-component="progress-circle"\` with background/progress slots. + +` + +const story = create({ title: "UI/ProgressCircle", mod, args: { percentage: 65, size: 48 } }) + +export default { + title: "UI/ProgressCircle", + id: "components-progress-circle", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + percentage: { + control: { type: "range", min: 0, max: 100, step: 1 }, + }, + }, +} + +export const Basic = story.Basic + +export const States = { + render: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/components/progress-circle.tsx b/packages/ui/src/components/progress-circle.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7c764b5dd1c7577e2360157869139a32bd433866 --- /dev/null +++ b/packages/ui/src/components/progress-circle.tsx @@ -0,0 +1,64 @@ +import { type ComponentProps, createMemo, splitProps } from "solid-js" + +export interface ProgressCircleProps extends Pick, "class" | "classList" | "style"> { + percentage: number + size?: number + strokeWidth?: number +} + +export function ProgressCircle(props: ProgressCircleProps) { + const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"]) + + const size = () => split.size || 16 + const strokeWidth = () => split.strokeWidth || 3 + + const viewBoxSize = 16 + const center = viewBoxSize / 2 + const radius = () => center - strokeWidth() / 2 + const circumference = createMemo(() => 2 * Math.PI * radius()) + + const offset = createMemo(() => { + const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0)) + const progress = clampedPercentage / 100 + return circumference() * (1 - progress) + }) + + return ( + + + + + + ) +} diff --git a/packages/ui/src/components/progress.stories.tsx b/packages/ui/src/components/progress.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2ee322343496002bed2c8ae9e4bb2f1f2a7fd427 --- /dev/null +++ b/packages/ui/src/components/progress.stories.tsx @@ -0,0 +1,67 @@ +// @ts-nocheck +import * as mod from "./progress" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Linear progress indicator with optional label and value display. + +Use in forms, uploads, or background tasks. + +### API +- \`value\` and \`maxValue\` control progress. +- Optional: \`showValueLabel\`, \`hideLabel\`. +- Children provide the label text. + +### Variants and states +- Supports indeterminate state via Kobalte props (if provided). + +### Behavior +- Uses Kobalte Progress for value calculation. + +### Accessibility +- TODO: confirm ARIA attributes from Kobalte. + +### Theming/tokens +- Uses \`data-component="progress"\` with track/fill slots. + +` + +const story = create({ + title: "UI/Progress", + mod, + args: { + value: 60, + maxValue: 100, + children: "Progress", + showValueLabel: true, + }, +}) + +export default { + title: "UI/Progress", + id: "components-progress", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const NoLabel = { + args: { + children: "", + hideLabel: true, + showValueLabel: false, + value: 30, + }, +} + +export const Indeterminate = { + render: () => Loading, +} diff --git a/packages/ui/src/components/provider-icon.css b/packages/ui/src/components/provider-icon.css new file mode 100644 index 0000000000000000000000000000000000000000..142c5ca7cd540223619c6039117b8697ae2d0d9f --- /dev/null +++ b/packages/ui/src/components/provider-icon.css @@ -0,0 +1,5 @@ +[data-component="provider-icon"] { + flex-shrink: 0; + width: 16px; + height: 16px; +} diff --git a/packages/ui/src/components/provider-icon.stories.tsx b/packages/ui/src/components/provider-icon.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e7fc39967bf1087e11ca16b4074852e01c47738d --- /dev/null +++ b/packages/ui/src/components/provider-icon.stories.tsx @@ -0,0 +1,69 @@ +// @ts-nocheck +import { iconNames } from "./provider-icons/types" +import * as mod from "./provider-icon" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Provider icon sprite renderer for model/provider badges. + +Use in model pickers or provider lists. + +### API +- Required: \`id\` (provider icon name). +- Accepts standard SVG props. + +### Variants and states +- Single visual style; size via CSS. + +### Behavior +- Renders from the provider SVG sprite sheet. + +### Accessibility +- Provide accessible text nearby when the icon conveys meaning. + +### Theming/tokens +- Uses \`data-component="provider-icon"\`. + +` + +const story = create({ title: "UI/ProviderIcon", mod, args: { id: "openai" } }) +export default { + title: "UI/ProviderIcon", + id: "components-provider-icon", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + id: { + control: "select", + options: iconNames, + }, + }, +} + +export const Basic = story.Basic + +export const AllIcons = { + render: () => ( +
+ {iconNames.map((id) => ( +
+ +
{id}
+
+ ))} +
+ ), +} diff --git a/packages/ui/src/components/provider-icon.tsx b/packages/ui/src/components/provider-icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7c0eb3d047dfb0a780ac101875ec8f9136e3300d --- /dev/null +++ b/packages/ui/src/components/provider-icon.tsx @@ -0,0 +1,25 @@ +import type { Component, JSX } from "solid-js" +import { createMemo, splitProps } from "solid-js" +import sprite from "./provider-icons/sprite.svg" +import { iconNames, type IconName } from "./provider-icons/types" + +export type ProviderIconProps = JSX.SVGElementTags["svg"] & { + id: string +} + +export const ProviderIcon: Component = (props) => { + const [local, rest] = splitProps(props, ["id", "class", "classList"]) + const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "synthetic")) + return ( + + + + ) +} diff --git a/packages/ui/src/components/provider-icons/sprite.svg b/packages/ui/src/components/provider-icons/sprite.svg new file mode 100644 index 0000000000000000000000000000000000000000..392226323400f7cb9d144eabaa40d4b7bb826cf4 --- /dev/null +++ b/packages/ui/src/components/provider-icons/sprite.svg @@ -0,0 +1,1118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/provider-icons/types.ts b/packages/ui/src/components/provider-icons/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..811df958f007246f4f02b0b55c0670b5ff62bf2a --- /dev/null +++ b/packages/ui/src/components/provider-icons/types.ts @@ -0,0 +1,106 @@ +// This file is generated by icon spritesheet generator + +export const iconNames = [ + "zhipuai", + "zhipuai-coding-plan", + "zenmux", + "zai", + "zai-coding-plan", + "xiaomi", + "xai", + "wandb", + "vultr", + "vivgrid", + "vercel", + "venice", + "v0", + "upstage", + "togetherai", + "synthetic", + "submodel", + "stepfun", + "stackit", + "siliconflow", + "siliconflow-cn", + "scaleway", + "sap-ai-core", + "requesty", + "qiniu-ai", + "qihang-ai", + "privatemode-ai", + "poe", + "perplexity", + "ovhcloud", + "openrouter", + "llmgateway", + "opencode", + "opencode-go", + "openai", + "ollama-cloud", + "nvidia", + "novita-ai", + "nova", + "nebius", + "nano-gpt", + "morph", + "moonshotai", + "moonshotai-cn", + "modelscope", + "moark", + "mistral", + "minimax", + "minimax-coding-plan", + "minimax-cn", + "minimax-cn-coding-plan", + "merge-gateway", + "meganova", + "lucidquery", + "lmstudio", + "llama", + "kuae-cloud-coding-plan", + "kimi-for-coding", + "kilo", + "jiekou", + "io-net", + "inference", + "inception", + "iflowcn", + "huggingface", + "helicone", + "groq", + "google", + "google-vertex", + "google-vertex-anthropic", + "gitlab", + "github-models", + "github-copilot", + "friendli", + "firmware", + "fireworks-ai", + "fastrouter", + "evroc", + "digitalocean", + "deepseek", + "deepinfra", + "cortecs", + "cohere", + "cloudflare-workers-ai", + "cloudflare-ai-gateway", + "cloudferro-sherlock", + "chutes", + "cerebras", + "berget", + "baseten", + "bailing", + "azure", + "azure-cognitive-services", + "anthropic", + "amazon-bedrock", + "alibaba", + "alibaba-cn", + "aihubmix", + "abacus", + "302ai", +] as const + +export type IconName = (typeof iconNames)[number] diff --git a/packages/ui/src/components/radio-group.css b/packages/ui/src/components/radio-group.css new file mode 100644 index 0000000000000000000000000000000000000000..e9cc71184633c6c239c4b3ced8c8cf3837eb6eaa --- /dev/null +++ b/packages/ui/src/components/radio-group.css @@ -0,0 +1,187 @@ +[data-component="radio-group"] { + --radio-group-height: 28px; + --radio-group-gap: 4px; + --radio-group-padding: 2px; + + display: inline-flex; + + [data-slot="radio-group-wrapper"] { + all: unset; + background-color: var(--surface-inset-base); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-xxs-border); + display: inline-flex; + height: var(--radio-group-height); + margin: 0; + overflow: visible; + padding: 0; + position: relative; + width: fit-content; + } + + &[data-fill] [data-slot="radio-group-wrapper"] { + width: 100%; + } + + [data-slot="radio-group-items"] { + display: inline-flex; + flex-direction: row; + gap: var(--radio-group-gap); + height: 100%; + list-style: none; + position: relative; + z-index: 1; + } + + &[data-fill] [data-slot="radio-group-items"] { + width: 100%; + } + + [data-slot="radio-group-indicator"] { + background: var(--button-secondary-base); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-xs-border-base); + content: ""; + opacity: var(--indicator-opacity, 1); + pointer-events: none; + position: absolute; + transition: + opacity 200ms ease-out, + box-shadow 100ms ease-in-out, + width 200ms cubic-bezier(0.22, 1.2, 0.36, 1), + height 200ms cubic-bezier(0.22, 1.2, 0.36, 1), + transform 300ms cubic-bezier(0.22, 1.2, 0.36, 1); + will-change: transform; + z-index: 0; + } + + [data-slot="radio-group-item"] { + display: flex; + height: 100%; + min-width: 0; + position: relative; + } + + &[data-fill] [data-slot="radio-group-item"] { + flex: 1; + } + + [data-slot="radio-group-item-input"] { + border-width: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + width: 1px; + } + + [data-slot="radio-group-item-label"] { + color: var(--text-weak); + cursor: default; + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + flex: 1; + height: 100%; + line-height: 1; + padding: var(--radio-group-padding); + position: relative; + transition: + color 200ms ease-out, + opacity 200ms ease-out; + user-select: none; + } + + [data-slot="radio-group-item-control"] { + align-items: center; + border-radius: var(--radius-xs); + display: inline-flex; + height: 100%; + justify-content: center; + min-width: 0; + padding: var(--radio-group-control-padding, 0 10px); + transition: background-color 200ms ease-out; + width: 100%; + } + + &[data-pad="none"] { + --radio-group-control-padding: 0; + } + + &[data-pad="normal"] { + --radio-group-control-padding: 0 10px; + } + + /* Checked state */ + [data-slot="radio-group-item-input"][data-checked] + [data-slot="radio-group-item-label"] { + color: var(--text-strong); + } + + /* Hover state: match the inset background (adds subtle density) */ + [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + + [data-slot="radio-group-item-label"]:hover + [data-slot="radio-group-item-control"] { + background-color: var(--surface-inset-base); + } + + /* Do not overlay hover on the active segment */ + [data-slot="radio-group-item-input"][data-checked] + + [data-slot="radio-group-item-label"]:hover + [data-slot="radio-group-item-control"] { + background-color: transparent; + } + + /* Disabled state */ + [data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] { + cursor: not-allowed; + opacity: 0.5; + } + + /* Focus state */ + [data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible) + [data-slot="radio-group-indicator"] { + box-shadow: var(--shadow-xs-border-focus); + } + + /* Hide indicator when nothing is checked */ + [data-slot="radio-group-wrapper"]:not(:has([data-slot="radio-group-item-input"][data-checked])) + [data-slot="radio-group-indicator"] { + --indicator-opacity: 0; + } + + /* Vertical orientation */ + &[aria-orientation="vertical"] [data-slot="radio-group-items"] { + flex-direction: column; + } + + /* Small size variant */ + &[data-size="small"] { + --radio-group-height: 24px; + --radio-group-gap: 3px; + --radio-group-padding: 2px; + + [data-slot="radio-group-item-label"] { + font-size: 12px; + } + + [data-slot="radio-group-item-control"] { + padding: var(--radio-group-control-padding, 0 8px); + } + } + + &[data-size="small"][data-pad="normal"] { + --radio-group-control-padding: 0 8px; + } + + /* Disabled root state */ + &[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + } +} diff --git a/packages/ui/src/components/radio-group.stories.tsx b/packages/ui/src/components/radio-group.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4900ead8467dd2018ff20a3fe5408bc329cd1059 --- /dev/null +++ b/packages/ui/src/components/radio-group.stories.tsx @@ -0,0 +1,92 @@ +// @ts-nocheck +import * as mod from "./radio-group" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Segmented radio group for choosing a single option. + +Use for view toggles or mode selection. + +### API +- Required: \`options\`. +- Optional: \`current\`, \`defaultValue\`, \`value\`, \`label\`, \`onSelect\`. +- Optional layout: \`size\`, \`fill\`, \`pad\`. + +### Variants and states +- Size variants: small, medium. +- Optional fill and padding behavior. + +### Behavior +- Maps options to segmented items and manages selection. + +### Accessibility +- TODO: confirm role/aria attributes from Kobalte SegmentedControl. + +### Theming/tokens +- Uses \`data-component="radio-group"\` with size/pad data attributes. + +` + +const story = create({ + title: "UI/RadioGroup", + mod, + args: { + options: ["One", "Two", "Three"], + defaultValue: "One", + }, +}) + +export default { + title: "UI/RadioGroup", + id: "components-radio-group", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + size: { + control: "select", + options: ["small", "medium"], + }, + pad: { + control: "select", + options: ["none", "normal"], + }, + fill: { + control: "boolean", + }, + }, +} + +export const Basic = story.Basic + +export const Sizes = { + render: () => ( +
+ + +
+ ), +} + +export const Filled = { + args: { + fill: true, + pad: "none", + }, +} + +export const CustomLabels = { + render: () => ( + (value === "list" ? "List view" : "Grid view")} + /> + ), +} diff --git a/packages/ui/src/components/radio-group.tsx b/packages/ui/src/components/radio-group.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9151a24b0f9c481bf1ec957775f0aa7439bf1e7e --- /dev/null +++ b/packages/ui/src/components/radio-group.tsx @@ -0,0 +1,83 @@ +import { SegmentedControl as Kobalte } from "@kobalte/core/segmented-control" +import { For, splitProps } from "solid-js" +import type { ComponentProps, JSX } from "solid-js" + +export type RadioGroupProps = Omit< + ComponentProps, + "value" | "defaultValue" | "onChange" | "children" +> & { + options: T[] + current?: T + defaultValue?: T + value?: (x: T) => string + label?: (x: T) => JSX.Element | string + onSelect?: (value: T | undefined) => void + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] + size?: "small" | "medium" + fill?: boolean + pad?: "none" | "normal" +} + +export function RadioGroup(props: RadioGroupProps) { + const [local, others] = splitProps(props, [ + "class", + "classList", + "options", + "current", + "defaultValue", + "value", + "label", + "onSelect", + "size", + "fill", + "pad", + ]) + + const getValue = (item: T): string => { + if (local.value) return local.value(item) + return String(item) + } + + const getLabel = (item: T): JSX.Element | string => { + if (local.label) return local.label(item) + return String(item) + } + + const findOption = (v: string): T | undefined => { + return local.options.find((opt) => getValue(opt) === v) + } + + return ( + local.onSelect?.(findOption(v))} + > +
+ +
+ + {(option) => ( + + + + {getLabel(option)} + + + )} + +
+
+
+ ) +} diff --git a/packages/ui/src/components/resize-handle.css b/packages/ui/src/components/resize-handle.css new file mode 100644 index 0000000000000000000000000000000000000000..5d8b90b32909fd66e822322ab1ab09843dbd0d59 --- /dev/null +++ b/packages/ui/src/components/resize-handle.css @@ -0,0 +1,70 @@ +[data-component="resize-handle"] { + position: absolute; + z-index: 10; + + &::after { + content: ""; + position: absolute; + opacity: 0; + transition: opacity 0.15s ease-in-out; + } + + &:hover::after, + &:active::after { + opacity: 1; + } + + &:where([data-direction="horizontal"]) { + inset-block: 0; + inset-inline-end: 0; + width: 8px; + transform: translateX(50%); + cursor: col-resize; + + &[data-edge="start"] { + inset-inline-start: 0; + inset-inline-end: auto; + transform: translateX(-50%); + } + + &::after { + width: 3px; + inset-block: 0; + inset-inline-start: 50%; + transform: translateX(-50%); + } + + &:dir(rtl) { + transform: translateX(-50%); + + &[data-edge="start"] { + transform: translateX(50%); + } + + &::after { + transform: translateX(50%); + } + } + } + + &:where([data-direction="vertical"]) { + inset-inline: 0; + inset-block-start: 0; + height: 8px; + transform: translateY(-50%); + cursor: row-resize; + + &[data-edge="end"] { + inset-block-start: auto; + inset-block-end: 0; + transform: translateY(50%); + } + + &::after { + height: 3px; + inset-inline: 0; + inset-block-start: 50%; + transform: translateY(-50%); + } + } +} diff --git a/packages/ui/src/components/resize-handle.stories.tsx b/packages/ui/src/components/resize-handle.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c0151ee702b72575d8a8276891f4436b3d5f8a4a --- /dev/null +++ b/packages/ui/src/components/resize-handle.stories.tsx @@ -0,0 +1,161 @@ +// @ts-nocheck +import { createSignal } from "solid-js" +import { createStore } from "solid-js/store" +import * as mod from "./resize-handle" + +const docs = `### Overview +Drag handle for resizing panels or split views. + +Use alongside resizable panels and split layouts. + +### API +- Required: \`direction\`, \`size\`, \`min\`, \`max\`, \`onResize\`. +- Optional: \`edge\`, \`onCollapse\`, \`collapseThreshold\`. + +### Variants and states +- Horizontal and vertical directions. + +### Behavior +- Drag updates size and calls \`onResize\` with clamped values. + +### Accessibility +- TODO: provide keyboard resizing guidance if needed. + +### Theming/tokens +- Uses \`data-component="resize-handle"\` with direction/edge data attributes. + +` + +export default { + title: "UI/ResizeHandle", + id: "components-resize-handle", + component: mod.ResizeHandle, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => { + const [size, setSize] = createSignal(240) + return ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} + +export const Vertical = { + render: () => { + const [size, setSize] = createSignal(180) + return ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} + +export const Collapse = { + render: () => { + const [state, setState] = createStore({ + size: 200, + collapsed: false, + }) + const size = () => state.size + const collapsed = () => state.collapsed + return ( +
+
+ {collapsed() ? "Collapsed" : `Size: ${size()}px`} +
+
+ { + setState("collapsed", false) + setState("size", next) + }} + onCollapse={() => setState("collapsed", true)} + style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)" + /> +
+ ) + }, +} + +export const EdgeStart = { + render: () => { + const [size, setSize] = createSignal(240) + return ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} diff --git a/packages/ui/src/components/scroll-view.css b/packages/ui/src/components/scroll-view.css new file mode 100644 index 0000000000000000000000000000000000000000..adebf190c5ec07d1a243c263db97d82cfc5248fc --- /dev/null +++ b/packages/ui/src/components/scroll-view.css @@ -0,0 +1,67 @@ +.scroll-view { + position: relative; + display: flex; + flex-direction: column; + min-height: 0; + overflow: hidden; +} + +.scroll-view__viewport { + flex: 1 1 auto; + min-height: 0; + width: 100%; + overflow-y: auto; + scrollbar-width: none; + outline: none; +} + +.scroll-view__viewport::-webkit-scrollbar { + display: none; +} + +.scroll-view__thumb { + position: absolute; + inset-inline-end: 0; + top: 0; + width: 12px; + transition: opacity 200ms ease; + cursor: default; + user-select: none; + pointer-events: auto; + opacity: 0; +} + +.scroll-view__thumb::after { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%); + top: 0; + bottom: 0; + width: 4px; + border-radius: 9999px; + background-color: var(--border-weak-base); + backdrop-filter: blur(4px); + transition: background-color 150ms ease; +} + +.scroll-view__thumb:hover::after, +.scroll-view__thumb[data-dragging="true"]::after { + background-color: var(--border-strong-base); +} + +.dark .scroll-view__thumb::after, +[data-theme="dark"] .scroll-view__thumb::after { + background-color: var(--border-weak-base); +} + +.dark .scroll-view__thumb:hover::after, +[data-theme="dark"] .scroll-view__thumb:hover::after, +.dark .scroll-view__thumb[data-dragging="true"]::after, +[data-theme="dark"] .scroll-view__thumb[data-dragging="true"]::after { + background-color: var(--border-strong-base); +} + +.scroll-view__thumb[data-visible="true"] { + opacity: 1; +} diff --git a/packages/ui/src/components/scroll-view.test.ts b/packages/ui/src/components/scroll-view.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..84131b1f17f212d84b7e69ac52694b6e94d5d026 --- /dev/null +++ b/packages/ui/src/components/scroll-view.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test } from "bun:test" +import { canScrollKey, scrollKey, scrollTopFromThumbPointer } from "./scroll-view" + +describe("scrollKey", () => { + test("maps plain navigation keys", () => { + expect(scrollKey({ key: "PageDown", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe( + "page-down", + ) + expect(scrollKey({ key: "ArrowUp", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe("up") + }) + + test("ignores modified keybinds", () => { + expect( + scrollKey({ key: "ArrowDown", altKey: false, ctrlKey: false, metaKey: true, shiftKey: false }), + ).toBeUndefined() + expect(scrollKey({ key: "PageUp", altKey: false, ctrlKey: true, metaKey: false, shiftKey: false })).toBeUndefined() + expect(scrollKey({ key: "End", altKey: false, ctrlKey: false, metaKey: false, shiftKey: true })).toBeUndefined() + }) + + test("maps space and shift-space directions", () => { + expect(scrollKey({ key: " ", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe("page-down") + expect(scrollKey({ key: " ", altKey: false, ctrlKey: false, metaKey: false, shiftKey: true })).toBe("page-up") + }) +}) + +describe("canScrollKey", () => { + const element = (scrollTop: number, clientHeight = 100, scrollHeight = 300) => + ({ scrollTop, clientHeight, scrollHeight }) as HTMLElement + + test("owns upward keys only above the top boundary", () => { + expect(canScrollKey(element(50), "page-up")).toBe(true) + expect(canScrollKey(element(0), "page-up")).toBe(false) + }) + + test("owns downward keys only before the bottom boundary", () => { + expect(canScrollKey(element(50), "page-down")).toBe(true) + expect(canScrollKey(element(200), "page-down")).toBe(false) + expect(canScrollKey(element(0, 100, 100), "page-down")).toBe(false) + }) +}) + +describe("scrollTopFromThumbPointer", () => { + test("keeps downward thumb movement monotonic when content height changes", () => { + const first = scrollTopFromThumbPointer({ + pointer: 300, + viewportTop: 100, + grabOffset: 12, + clientHeight: 600, + scrollHeight: 6_000, + thumbHeight: 60, + }) + const second = scrollTopFromThumbPointer({ + pointer: 320, + viewportTop: 100, + grabOffset: 12, + clientHeight: 600, + scrollHeight: 60_000, + thumbHeight: 32, + }) + + expect(second).toBeGreaterThan(first) + }) + + test("clamps pointer positions to the scroll range", () => { + const input = { + viewportTop: 100, + grabOffset: 12, + clientHeight: 600, + scrollHeight: 6_000, + thumbHeight: 60, + } + expect(scrollTopFromThumbPointer({ ...input, pointer: 0 })).toBe(0) + expect(scrollTopFromThumbPointer({ ...input, pointer: 1_000 })).toBe(5_400) + }) + + test("uses scrollClientHeight when the thumb track differs from the viewport", () => { + const input = { + pointer: 400, + viewportTop: 100, + grabOffset: 0, + clientHeight: 400, + scrollClientHeight: 800, + scrollHeight: 8_000, + thumbHeight: 40, + } + // track usable = 400 - 16 - 40 = 344; thumbTop = 400 - 100 - 8 = 292 + // maxScroll = 8000 - 800 = 7200 → 292/344 * 7200 + expect(scrollTopFromThumbPointer(input)).toBeCloseTo((292 / 344) * 7200) + }) +}) diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5116d94f9ead77392f2e01a74ab31e8ced76d6b0 --- /dev/null +++ b/packages/ui/src/components/scroll-view.tsx @@ -0,0 +1,388 @@ +import { + createEffect, + createMemo, + mergeProps, + onCleanup, + onMount, + Show, + splitProps, + type Accessor, + type ComponentProps, +} from "solid-js" +import { Portal } from "solid-js/web" +import { createResizeObserver } from "@solid-primitives/resize-observer" +import { createStore } from "solid-js/store" +import { useI18n } from "../context/i18n" + +export type ScrollViewThumbVisibility = "hover" | "scroll" + +export interface ScrollViewProps extends ComponentProps<"div"> { + viewportRef?: (el: HTMLDivElement) => void + orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb + /** + * `hover`: show while hovered or scrolling. `scroll`: show only while scrolling. + * + * In most cases, scrolling a container = hovering over it, so this change has no effect. + * This is a special case to account for the home page scroll, where scrolling a container != hovering over it + * */ + thumbVisibility?: ScrollViewThumbVisibility + /** Mount the thumb into an external track. Scroll metrics still come from this ScrollView. */ + thumbContainer?: HTMLElement | Accessor + /** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */ + thumbHoverTarget?: HTMLElement | Accessor +} + +export const scrollKey = (event: Pick) => { + if (event.altKey || event.ctrlKey || event.metaKey) return + if (event.shiftKey && event.key !== " ") return + + switch (event.key) { + case "PageDown": + return "page-down" + case "PageUp": + return "page-up" + case "Home": + return "home" + case "End": + return "end" + case "ArrowUp": + return "up" + case "ArrowDown": + return "down" + case " ": + return event.shiftKey ? "page-up" : "page-down" + } +} + +export function canScrollKey(element: HTMLElement, key: NonNullable>) { + const up = key === "up" || key === "page-up" || key === "home" + return up ? element.scrollTop > 0 : element.scrollTop + element.clientHeight < element.scrollHeight +} + +export function scrollKeyOwner( + root: HTMLElement, + target: EventTarget | null, + key: NonNullable>, +) { + const element = target instanceof Element ? target : undefined + const owner = element?.closest("[data-scrollable]") + if (!owner || owner === root) return root + if (!root.contains(owner)) return owner + return canScrollKey(owner, key) ? owner : root +} + +export function isScrollKeyTarget(target: EventTarget | null, key: NonNullable>) { + const element = target instanceof HTMLElement ? target : undefined + if (!element) return true + if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) || element.isContentEditable) return false + if ((key === "page-up" || key === "page-down") && element.closest('button, a[href], [role="button"]')) return false + return true +} + +export function scrollTopFromThumbPointer(input: { + pointer: number + viewportTop: number + grabOffset: number + clientHeight: number + scrollHeight: number + thumbHeight: number + /** Viewport height used for max scroll. Defaults to `clientHeight` (track == viewport). */ + scrollClientHeight?: number +}) { + const padding = 8 + const maxThumbTop = input.clientHeight - padding * 2 - input.thumbHeight + if (maxThumbTop <= 0) return 0 + const thumbTop = Math.max(0, Math.min(input.pointer - input.viewportTop - padding - input.grabOffset, maxThumbTop)) + return (thumbTop / maxThumbTop) * Math.max(0, input.scrollHeight - (input.scrollClientHeight ?? input.clientHeight)) +} + +export function ScrollView(props: ScrollViewProps) { + const i18n = useI18n() + const merged = mergeProps({ orientation: "vertical", thumbVisibility: "hover" }, props) + const [local, events, rest] = splitProps( + merged, + [ + "class", + "children", + "viewportRef", + "orientation", + "thumbVisibility", + "thumbContainer", + "thumbHoverTarget", + "style", + ], + [ + "onScroll", + "onWheel", + "onTouchStart", + "onTouchMove", + "onTouchEnd", + "onTouchCancel", + "onPointerDown", + "onClick", + "onKeyDown", + ], + ) + + let rootRef!: HTMLDivElement + let viewportRef!: HTMLDivElement + let thumbRef!: HTMLDivElement + + const resolveEl = (value: HTMLElement | Accessor | undefined) => { + if (typeof value === "function") return value() + return value + } + + const thumbMount = createMemo(() => resolveEl(local.thumbContainer)) + const thumbHover = createMemo(() => resolveEl(local.thumbHoverTarget)) + const hoverRoot = () => !local.thumbHoverTarget && !local.thumbContainer + + const [state, setState] = createStore({ + isHovered: false, + isDragging: false, + isScrolling: false, + thumbHeight: 0, + thumbTop: 0, + showThumb: false, + }) + const isHovered = () => state.isHovered + const isDragging = () => state.isDragging + const isScrolling = () => state.isScrolling + const thumbHeight = () => state.thumbHeight + const thumbTop = () => state.thumbTop + const showThumb = () => state.showThumb + + let scrollIdleTimer: ReturnType | undefined + + const markScrolling = () => { + setState("isScrolling", true) + if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer) + scrollIdleTimer = setTimeout(() => setState("isScrolling", false), 800) + } + + const thumbVisible = () => { + if (isDragging()) return true + if (isScrolling()) return true + return local.thumbVisibility === "hover" && isHovered() + } + + onCleanup(() => { + if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer) + }) + + const updateThumb = () => { + if (!viewportRef) return + const { scrollTop, scrollHeight, clientHeight } = viewportRef + + if (scrollHeight <= clientHeight || scrollHeight === 0) { + setState("showThumb", false) + return + } + + setState("showThumb", true) + const trackPadding = 8 + const trackClientHeight = thumbMount()?.clientHeight || clientHeight + const trackHeight = trackClientHeight - trackPadding * 2 + + const minThumbHeight = 32 + // Calculate raw thumb height based on ratio + let height = (clientHeight / scrollHeight) * trackHeight + height = Math.max(height, minThumbHeight) + + const maxScrollTop = scrollHeight - clientHeight + const maxThumbTop = trackHeight - height + + const top = maxScrollTop > 0 ? (scrollTop / maxScrollTop) * maxThumbTop : 0 + + // Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety) + const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop)) + + setState("thumbHeight", height) + setState("thumbTop", boundedTop) + } + + onMount(() => { + if (local.viewportRef) { + local.viewportRef(viewportRef) + } + + createResizeObserver( + () => [viewportRef, viewportRef.firstElementChild, thumbMount()].filter(Boolean) as HTMLElement[], + updateThumb, + ) + + updateThumb() + }) + + createEffect(() => { + thumbMount() + updateThumb() + }) + + createEffect(() => { + const target = thumbHover() + if (!target) return + + const enter = () => setState("isHovered", true) + const leave = () => setState("isHovered", false) + target.addEventListener("pointerenter", enter) + target.addEventListener("pointerleave", leave) + onCleanup(() => { + target.removeEventListener("pointerenter", enter) + target.removeEventListener("pointerleave", leave) + setState("isHovered", false) + }) + }) + + const onThumbPointerDown = (e: PointerEvent) => { + e.preventDefault() + e.stopPropagation() + setState("isDragging", true) + const grabOffset = e.clientY - thumbRef.getBoundingClientRect().top + const track = thumbMount() ?? viewportRef + + thumbRef.setPointerCapture(e.pointerId) + + const onPointerMove = (e: PointerEvent) => { + const { scrollHeight, clientHeight } = viewportRef + viewportRef.scrollTop = scrollTopFromThumbPointer({ + pointer: e.clientY, + viewportTop: track.getBoundingClientRect().top, + grabOffset, + clientHeight: track.clientHeight, + scrollClientHeight: clientHeight, + scrollHeight, + thumbHeight: thumbHeight(), + }) + } + + const done = (e: PointerEvent) => { + setState("isDragging", false) + thumbRef.releasePointerCapture(e.pointerId) + thumbRef.removeEventListener("pointermove", onPointerMove) + thumbRef.removeEventListener("pointerup", done) + thumbRef.removeEventListener("pointercancel", done) + } + + thumbRef.addEventListener("pointermove", onPointerMove) + thumbRef.addEventListener("pointerup", done) + thumbRef.addEventListener("pointercancel", done) + } + + const renderThumb = () => ( +
{ + thumbRef = el + }} + onPointerDown={onThumbPointerDown} + class="scroll-view__thumb" + data-visible={thumbVisible()} + data-dragging={isDragging()} + style={{ + height: `${thumbHeight()}px`, + transform: `translateY(${thumbTop()}px)`, + "z-index": 100, // ensure it displays over content + }} + /> + ) + + // Keybinds implementation + // We ensure the viewport has a tabindex so it can receive focus + // We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior, + // but native usually handles this perfectly. Let's explicitly ensure it behaves well. + const onKeyDown = (e: KeyboardEvent) => { + // If user is focused on an input inside the scroll view, don't hijack keys + if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) { + return + } + const next = scrollKey(e) + if (!next) return + if (!isScrollKeyTarget(e.target, next)) return + if (scrollKeyOwner(viewportRef, e.target, next) !== viewportRef) return + + const scrollAmount = viewportRef.clientHeight * 0.8 + const lineAmount = 40 + + switch (next) { + case "page-down": + e.preventDefault() + viewportRef.scrollBy({ top: scrollAmount, behavior: "smooth" }) + break + case "page-up": + e.preventDefault() + viewportRef.scrollBy({ top: -scrollAmount, behavior: "smooth" }) + break + case "home": + e.preventDefault() + viewportRef.scrollTo({ top: 0, behavior: "smooth" }) + break + case "end": + e.preventDefault() + viewportRef.scrollTo({ top: viewportRef.scrollHeight, behavior: "smooth" }) + break + case "up": + e.preventDefault() + viewportRef.scrollBy({ top: -lineAmount, behavior: "smooth" }) + break + case "down": + e.preventDefault() + viewportRef.scrollBy({ top: lineAmount, behavior: "smooth" }) + break + } + } + + return ( +
{ + if (hoverRoot()) setState("isHovered", true) + }} + onPointerLeave={() => { + if (hoverRoot()) setState("isHovered", false) + }} + {...rest} + > + {/* Viewport */} +
{ + updateThumb() + markScrolling() + if (typeof events.onScroll === "function") events.onScroll(e as any) + }} + onWheel={(e) => { + markScrolling() + const handler = events.onWheel + if (typeof handler === "function") handler(e as any) + if (Array.isArray(handler)) handler[0](handler[1], e as any) + }} + onTouchStart={events.onTouchStart as any} + onTouchMove={events.onTouchMove as any} + onTouchEnd={events.onTouchEnd as any} + onTouchCancel={events.onTouchCancel as any} + onPointerDown={events.onPointerDown as any} + onClick={events.onClick as any} + tabIndex={0} + role="region" + aria-label={i18n.t("ui.scrollView.ariaLabel")} + onKeyDown={(e) => { + onKeyDown(e) + if (typeof events.onKeyDown === "function") events.onKeyDown(e as any) + }} + > + {local.children} +
+ + {/* Thumb Overlay — optionally portaled into an external track */} + + + {(mount) => {renderThumb()}} + + +
+ ) +} diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css new file mode 100644 index 0000000000000000000000000000000000000000..f78fe296f6647b875d225c18eef224ea435d26d1 --- /dev/null +++ b/packages/ui/src/components/select.css @@ -0,0 +1,201 @@ +[data-component="select"] { + [data-slot="select-select-trigger"] { + padding-block: 0; + padding-inline: 8px 4px; + box-shadow: none; + + [data-slot="select-select-trigger-value"] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + [data-slot="select-select-trigger-icon"] { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--text-weak); + transition: transform 0.1s ease-in-out; + } + + &[data-expanded] { + &[data-variant="secondary"] { + background-color: var(--button-secondary-hover); + } + &[data-variant="ghost"] { + background-color: var(--surface-raised-base-active); + } + &[data-variant="primary"] { + background-color: var(--icon-strong-active); + } + } + + &:not([data-expanded]):focus-visible { + &[data-variant="secondary"] { + background-color: var(--button-secondary-base); + } + &[data-variant="ghost"] { + background-color: var(--surface-raised-base-hover); + } + &[data-variant="primary"] { + background-color: var(--icon-strong-base); + } + } + } + + &[data-trigger-style="settings"] { + [data-slot="select-select-trigger"] { + padding-block: 6px; + padding-inline: 12px 6px; + box-shadow: none; + border-radius: 6px; + min-width: 160px; + height: 32px; + justify-content: flex-end; + gap: 12px; + background-color: transparent; + + [data-slot="select-select-trigger-value"] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: var(--font-size-base); + font-weight: var(--font-weight-regular); + } + [data-slot="select-select-trigger-icon"] { + width: 16px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--text-weak); + background-color: var(--surface-raised-base); + border-radius: 4px; + transition: transform 0.1s ease-in-out; + } + + &[data-slot="select-select-trigger"]:hover:not(:disabled), + &[data-slot="select-select-trigger"][data-expanded], + &[data-slot="select-select-trigger"][data-expanded]:hover:not(:disabled) { + background-color: var(--input-base); + box-shadow: var(--shadow-xs-border-base); + } + + &:not([data-expanded]):not(:focus-visible):focus { + background-color: transparent; + box-shadow: none; + } + } + } +} + +[data-component="select-content"] { + min-width: 104px; + max-width: 23rem; + overflow: hidden; + border-radius: var(--radius-md); + background-color: var(--surface-raised-stronger-non-alpha); + padding: 4px; + box-shadow: var(--shadow-xs-border); + z-index: 60; + + &[data-expanded] { + animation: select-open 0.15s ease-out; + } + + [data-slot="select-select-content-list"] { + overflow-y: auto; + max-height: 12rem; + white-space: nowrap; + overflow-x: hidden; + display: flex; + flex-direction: column; + + &:focus { + outline: none; + } + + > *:not([role="presentation"]) + *:not([role="presentation"]) { + margin-top: 2px; + } + } + + [data-slot="select-select-item"] { + position: relative; + display: flex; + align-items: center; + padding: 2px 8px; + gap: 12px; + border-radius: 4px; + cursor: default; + + /* text-12-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 166.667% */ + letter-spacing: var(--letter-spacing-normal); + + color: var(--text-strong); + + outline: none; + user-select: none; + + &[data-highlighted] { + background: var(--surface-raised-base-hover); + } + &[data-disabled] { + background-color: var(--surface-raised-base); + pointer-events: none; + } + [data-slot="select-select-item-indicator"] { + display: flex; + align-items: center; + justify-content: center; + margin-inline-start: auto; + width: 16px; + height: 16px; + } + &:focus { + outline: none; + } + &:hover { + background: var(--surface-raised-base-hover); + } + } +} + +[data-component="select-content"][data-trigger-style="settings"] { + min-width: 160px; + border-radius: 8px; + padding: 0; + + [data-slot="select-select-content-list"] { + padding: 4px; + } + + [data-slot="select-select-item"] { + /* text-14-regular */ + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + } +} + +@keyframes select-open { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/packages/ui/src/components/select.stories.tsx b/packages/ui/src/components/select.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1ee00ab851e49d0a4c1679eaa283f20657a38207 --- /dev/null +++ b/packages/ui/src/components/select.stories.tsx @@ -0,0 +1,113 @@ +// @ts-nocheck +import * as mod from "./select" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Select menu for choosing a single option with optional grouping. + +Use \`children\` to customize option rendering. + +### API +- Required: \`options\`. +- Optional: \`current\`, \`placeholder\`, \`value\`, \`label\`, \`groupBy\`. +- Accepts Button props for the trigger (\`variant\`, \`size\`). + +### Variants and states +- Trigger supports "settings" style via \`triggerVariant\`. + +### Behavior +- Uses Kobalte Select with optional item highlight callbacks. + +### Accessibility +- TODO: confirm keyboard navigation and aria attributes from Kobalte. + +### Theming/tokens +- Uses \`data-component="select"\` with slot attributes. + +` + +const story = create({ + title: "UI/Select", + mod, + args: { + options: ["One", "Two", "Three"], + current: "One", + placeholder: "Choose...", + variant: "secondary", + size: "normal", + }, +}) + +export default { + title: "UI/Select", + id: "components-select", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + triggerVariant: { + control: "select", + options: ["settings", undefined], + }, + }, +} + +export const Basic = story.Basic + +export const Grouped = { + render: () => { + const options = [ + { id: "alpha", label: "Alpha", group: "Group A" }, + { id: "bravo", label: "Bravo", group: "Group A" }, + { id: "delta", label: "Delta", group: "Group B" }, + ] + return ( + item.id} + label={(item) => item.label} + groupBy={(item) => item.group} + placeholder="Choose..." + variant="secondary" + /> + ) + }, +} + +export const SettingsTrigger = { + args: { + triggerVariant: "settings", + }, +} + +export const CustomRender = { + render: () => ( + + {(item) => {item}} + + ), +} + +export const CustomTriggerStyle = { + args: { + triggerStyle: { "min-width": "180px", "justify-content": "space-between" }, + }, +} + +export const Disabled = { + args: { + disabled: true, + }, +} diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx new file mode 100644 index 0000000000000000000000000000000000000000..67becf2d9c4d112a6f07167d227ab11df8f65f60 --- /dev/null +++ b/packages/ui/src/components/select.tsx @@ -0,0 +1,174 @@ +import { Select as Kobalte } from "@kobalte/core/select" +import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js" +import { pipe, groupBy, entries, map } from "remeda" +import { Button, ButtonProps } from "./button" +import { Icon } from "./icon" + +export type SelectProps = Omit>, "value" | "onSelect" | "children"> & { + placeholder?: string + options: T[] + current?: T + value?: (x: T) => string + label?: (x: T) => string + groupBy?: (x: T) => string + valueClass?: ComponentProps<"div">["class"] + onSelect?: (value: T | undefined) => void + onHighlight?: (value: T | undefined) => (() => void) | void + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] + children?: (item: T | undefined) => JSX.Element + triggerStyle?: JSX.CSSProperties + triggerVariant?: "settings" + triggerProps?: Record +} + +export function Select(props: SelectProps & Omit) { + const [local, others] = splitProps(props, [ + "class", + "classList", + "placeholder", + "options", + "current", + "value", + "label", + "groupBy", + "valueClass", + "onSelect", + "onHighlight", + "onOpenChange", + "children", + "triggerStyle", + "triggerVariant", + "triggerProps", + ]) + + const state = { + key: undefined as string | undefined, + cleanup: undefined as (() => void) | void, + } + + const stop = () => { + state.cleanup?.() + state.cleanup = undefined + state.key = undefined + } + + const keyFor = (item: T) => (local.value ? local.value(item) : (item as string)) + + const move = (item: T | undefined) => { + if (!local.onHighlight) return + if (!item) { + stop() + return + } + + const key = keyFor(item) + if (state.key === key) return + state.cleanup?.() + state.cleanup = local.onHighlight(item) + state.key = key + } + + onCleanup(stop) + + const grouped = createMemo(() => { + const result = pipe( + local.options, + groupBy((x) => (local.groupBy ? local.groupBy(x) : "")), + // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), + entries(), + map(([k, v]) => ({ category: k, options: v })), + ) + return result + }) + + return ( + // @ts-ignore + + {...others} + data-component="select" + data-trigger-style={local.triggerVariant} + placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"} + gutter={4} + value={local.current} + options={grouped()} + optionValue={(x) => (local.value ? local.value(x) : (x as string))} + optionTextValue={(x) => (local.label ? local.label(x) : (x as string))} + optionGroupChildren="options" + placeholder={local.placeholder} + sectionComponent={(local) => ( + {local.section.rawValue.category} + )} + itemComponent={(itemProps) => ( + move(itemProps.item.rawValue)} + onPointerMove={() => move(itemProps.item.rawValue)} + onFocus={() => move(itemProps.item.rawValue)} + > + + {local.children + ? local.children(itemProps.item.rawValue) + : local.label + ? local.label(itemProps.item.rawValue) + : (itemProps.item.rawValue as string)} + + + + + + )} + onChange={(v) => { + local.onSelect?.(v ?? undefined) + stop() + }} + onOpenChange={(open) => { + local.onOpenChange?.(open) + if (!open) stop() + }} + > + + data-slot="select-select-trigger-value" class={local.valueClass}> + {(state) => { + const selected = state.selectedOption() ?? local.current + if (!selected) return local.placeholder || "" + if (local.label) return local.label(selected) + return selected as string + }} + + + + + + + + + + + + ) +} diff --git a/packages/ui/src/components/spinner.css b/packages/ui/src/components/spinner.css new file mode 100644 index 0000000000000000000000000000000000000000..2ca474dc3c3e5f58c16ac2084e4585311e3c60b6 --- /dev/null +++ b/packages/ui/src/components/spinner.css @@ -0,0 +1,6 @@ +[data-component="spinner"] { + color: inherit; + flex-shrink: 0; + width: 18px; + aspect-ratio: 1; +} diff --git a/packages/ui/src/components/spinner.stories.tsx b/packages/ui/src/components/spinner.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..be6106d148674a0d4196776da360e2fef09d301a --- /dev/null +++ b/packages/ui/src/components/spinner.stories.tsx @@ -0,0 +1,53 @@ +// @ts-nocheck +import * as mod from "./spinner" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Animated loading indicator for inline or page-level loading states. + +Use with \`Button\` or in empty states. + +### API +- Accepts standard SVG props (class, style). + +### Variants and states +- Single default animation style. + +### Behavior +- Animation is CSS-driven via data attributes. + +### Accessibility +- Use alongside text or aria-live regions to convey loading state. + +### Theming/tokens +- Uses \`data-component="spinner"\` for styling hooks. + +` + +const story = create({ title: "UI/Spinner", mod }) + +export default { + title: "UI/Spinner", + id: "components-spinner", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const Sizes = { + render: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f8aacb6e29be4771a6c499bec49ff60d25f4700c --- /dev/null +++ b/packages/ui/src/components/spinner.tsx @@ -0,0 +1,52 @@ +import { ComponentProps, For } from "solid-js" + +const outerIndices = new Set([1, 2, 4, 7, 8, 11, 13, 14]) +const cornerIndices = new Set([0, 3, 12, 15]) +const squares = Array.from({ length: 16 }, (_, i) => ({ + id: i, + x: (i % 4) * 4, + y: Math.floor(i / 4) * 4, + delay: Math.random() * 1.5, + duration: 1 + Math.random() * 1, + outer: outerIndices.has(i), + corner: cornerIndices.has(i), +})) + +export function Spinner(props: { + class?: string + classList?: ComponentProps<"div">["classList"] + style?: ComponentProps<"div">["style"] +}) { + return ( + + + {(square) => ( + + )} + + + ) +} diff --git a/packages/ui/src/components/sticky-accordion-header.css b/packages/ui/src/components/sticky-accordion-header.css new file mode 100644 index 0000000000000000000000000000000000000000..68195241b9cd1393f8f6fb6d0e33950760e48d36 --- /dev/null +++ b/packages/ui/src/components/sticky-accordion-header.css @@ -0,0 +1,6 @@ +[data-component="sticky-accordion-header"] { + position: sticky; + top: calc(var(--sticky-accordion-top, 0px) + var(--sticky-accordion-offset, 0px)); + z-index: 10; + background-color: var(--background-stronger); +} diff --git a/packages/ui/src/components/sticky-accordion-header.stories.tsx b/packages/ui/src/components/sticky-accordion-header.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3f033562677e8e073a3860c7f6b4556bd7198960 --- /dev/null +++ b/packages/ui/src/components/sticky-accordion-header.stories.tsx @@ -0,0 +1,54 @@ +// @ts-nocheck +import { Accordion } from "./accordion" +import * as mod from "./sticky-accordion-header" + +const docs = `### Overview +Sticky accordion header wrapper for persistent section labels. + +Use only inside \`Accordion.Item\` with \`Accordion.Trigger\`. + +### API +- Accepts standard header props and children. + +### Variants and states +- Inherits accordion states. + +### Behavior +- Renders inside an Accordion item header. + +### Accessibility +- TODO: confirm semantics from Accordion.Header usage. + +### Theming/tokens +- Uses \`data-component="sticky-accordion-header"\`. + +` + +export default { + title: "UI/StickyAccordionHeader", + id: "components-sticky-accordion-header", + component: mod.StickyAccordionHeader, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( + + + + Sticky header + + +
Accordion content.
+
+
+
+ ), +} diff --git a/packages/ui/src/components/switch.css b/packages/ui/src/components/switch.css new file mode 100644 index 0000000000000000000000000000000000000000..86fc1432894bc9072a9db188fece1248e5ef5ca8 --- /dev/null +++ b/packages/ui/src/components/switch.css @@ -0,0 +1,136 @@ +[data-component="switch"] { + position: relative; + display: flex; + align-items: center; + gap: 8px; + cursor: default; + + [data-slot="switch-input"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + [data-slot="switch-control"] { + display: inline-flex; + align-items: center; + width: 28px; + height: 16px; + flex-shrink: 0; + border-radius: 3px; + border: 1px solid var(--border-weak-base); + background: var(--surface-base); + transition: + background-color 150ms, + border-color 150ms; + } + + [data-slot="switch-thumb"] { + width: 14px; + height: 14px; + box-sizing: content-box; + + border-radius: 2px; + border: 1px solid var(--border-base); + background: var(--icon-invert-base); + + /* shadows/shadow-xs */ + box-shadow: + 0 1px 2px -1px rgba(19, 16, 16, 0.04), + 0 1px 2px 0 rgba(19, 16, 16, 0.06), + 0 1px 3px 0 rgba(19, 16, 16, 0.08); + + transform: translateX(-1px); + transition: + transform 150ms, + background-color 150ms; + } + + [data-slot="switch-label"] { + user-select: none; + color: var(--text-base); + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="switch-description"] { + color: var(--text-base); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="switch-error"] { + color: var(--text-error); + font-family: var(--font-family-sans); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + &:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &:not([data-readonly]) [data-slot="switch-input"]:focus-visible ~ [data-slot="switch-control"] { + border-color: var(--border-focus); + box-shadow: var(--shadow-xs-border-focus); + } + + &[data-checked] [data-slot="switch-control"] { + box-sizing: border-box; + border-color: var(--icon-strong-base); + background-color: var(--icon-strong-base); + } + + &[data-checked] [data-slot="switch-thumb"] { + border: none; + transform: translateX(12px); + background-color: var(--icon-invert-base); + } + + &:dir(rtl)[data-checked] [data-slot="switch-thumb"] { + transform: translateX(-12px); + } + + &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] { + border-color: var(--border-hover); + background-color: var(--surface-hover); + } + + &[data-disabled] { + cursor: not-allowed; + } + + &[data-disabled] [data-slot="switch-control"] { + border-color: var(--border-disabled); + background-color: var(--surface-disabled); + } + + &[data-disabled] [data-slot="switch-thumb"] { + background-color: var(--icon-disabled); + } + + &[data-invalid] [data-slot="switch-control"] { + border-color: var(--border-error); + } + + &[data-readonly] { + cursor: default; + pointer-events: none; + } +} diff --git a/packages/ui/src/components/switch.tsx b/packages/ui/src/components/switch.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f4f95baf57d5e945391be16bf8c92c3e1c7630a4 --- /dev/null +++ b/packages/ui/src/components/switch.tsx @@ -0,0 +1,29 @@ +import { Switch as Kobalte } from "@kobalte/core/switch" +import { Show, splitProps } from "solid-js" +import type { ComponentProps, ParentProps } from "solid-js" + +export interface SwitchProps extends ParentProps> { + hideLabel?: boolean + description?: string +} + +export function Switch(props: SwitchProps) { + const [local, others] = splitProps(props, ["children", "class", "hideLabel", "description"]) + return ( + + + + + {local.children} + + + + {local.description} + + + + + + + ) +} diff --git a/packages/ui/src/components/tabs.stories.tsx b/packages/ui/src/components/tabs.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8e09764dcc64957dd11a14b3755ab691d2e67975 --- /dev/null +++ b/packages/ui/src/components/tabs.stories.tsx @@ -0,0 +1,179 @@ +// @ts-nocheck +import { IconButton } from "./icon-button" +import { createSignal } from "solid-js" +import * as mod from "./tabs" + +const docs = `### Overview +Tabbed navigation for switching between related panels. + +Compose \`Tabs.List\` + \`Tabs.Trigger\` + \`Tabs.Content\`. + +### API +- Root accepts Kobalte Tabs props (\`value\`, \`defaultValue\`, \`onChange\`). +- \`variant\` sets visual style: normal, alt, pill, settings. +- \`orientation\` supports horizontal or vertical layouts. +- Trigger supports \`closeButton\`, \`hideCloseButton\`, and \`onMiddleClick\`. + +### Variants and states +- Normal, alt, pill, settings variants. +- Horizontal and vertical orientations. + +### Behavior +- Uses Kobalte Tabs for roving focus and selection management. + +### Accessibility +- TODO: confirm keyboard interactions from Kobalte Tabs. + +### Theming/tokens +- Uses \`data-component="tabs"\` with variant/orientation data attributes. + +` + +export default { + title: "UI/Tabs", + id: "components-tabs", + component: mod.Tabs, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + variant: { + control: "select", + options: ["normal", "alt", "pill", "settings"], + }, + orientation: { + control: "select", + options: ["horizontal", "vertical"], + }, + }, +} + +export const Basic = { + args: { + variant: "normal", + orientation: "horizontal", + defaultValue: "overview", + }, + render: (props) => ( + + + Overview + Details + Activity + + Overview content + Details content + Activity content + + ), +} + +export const Settings = { + args: { + variant: "settings", + orientation: "horizontal", + defaultValue: "general", + }, + render: (props) => ( + + + General + Appearance + + General settings + Appearance settings + + ), +} + +export const Alt = { + args: { + variant: "alt", + orientation: "horizontal", + defaultValue: "first", + }, + render: (props) => ( + + + First + Second + + Alt content + Alt content 2 + + ), +} + +export const Vertical = { + args: { + variant: "pill", + orientation: "vertical", + defaultValue: "alpha", + }, + render: (props) => ( + + + Alpha + Beta + + Alpha content + Beta content + + ), +} + +export const Closable = { + args: { + variant: "normal", + orientation: "horizontal", + defaultValue: "tab-1", + }, + render: (props) => ( + + + } + > + Tab 1 + + Tab 2 + + Closable content + Standard content + + ), +} + +export const MiddleClick = { + args: { + variant: "normal", + orientation: "horizontal", + defaultValue: "tab-1", + }, + render: (props) => { + const [message, setMessage] = createSignal("Middle click a tab") + return ( +
+
{message()}
+ + + setMessage("Middle clicked tab-1")}> + Tab 1 + + setMessage("Middle clicked tab-2")}> + Tab 2 + + + Tab 1 content + Tab 2 content + +
+ ) + }, +} diff --git a/packages/ui/src/components/tag.tsx b/packages/ui/src/components/tag.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c54e4d474794028225bb04bee292a86318341822 --- /dev/null +++ b/packages/ui/src/components/tag.tsx @@ -0,0 +1,22 @@ +import { type ComponentProps, splitProps } from "solid-js" + +export interface TagProps extends ComponentProps<"span"> { + size?: "normal" | "large" +} + +export function Tag(props: TagProps) { + const [split, rest] = splitProps(props, ["size", "class", "classList", "children"]) + return ( + + {split.children} + + ) +} diff --git a/packages/ui/src/components/text-reveal.css b/packages/ui/src/components/text-reveal.css new file mode 100644 index 0000000000000000000000000000000000000000..f799962f0947528bfa35eae0322c26c37e997d01 --- /dev/null +++ b/packages/ui/src/components/text-reveal.css @@ -0,0 +1,150 @@ +/* + * TextReveal — mask-position wipe animation + * + * Instead of sliding text through a fixed mask (odometer style), + * the mask itself sweeps across each span to reveal/hide text. + * + * Direction: top-to-bottom. New text drops in from above, old text exits downward. + * + * Entering: gradient reveals top-to-bottom (top of text appears first). + * gradient(to bottom, white 33%, transparent 33%+edge) + * pos 0 100% = transparent covers element = hidden + * pos 0 0% = white covers element = visible + * + * Leaving: gradient hides top-to-bottom (top of text disappears first). + * gradient(to top, white 33%, transparent 33%+edge) + * pos 0 100% = white covers element = visible + * pos 0 0% = transparent covers element = hidden + * + * Both transition from 0 100% (swap) → 0 0% (settled). + */ + +[data-component="text-reveal"] { + --_edge: var(--text-reveal-edge, 17%); + --_dur: var(--text-reveal-duration, 450ms); + --_spring: var(--text-reveal-spring, cubic-bezier(0.34, 1.08, 0.64, 1)); + --_spring-soft: var(--text-reveal-spring-soft, cubic-bezier(0.34, 1, 0.64, 1)); + --_travel: var(--text-reveal-travel, 0px); + + display: inline-flex; + align-items: center; + min-width: 0; + overflow: visible; + + [data-slot="text-reveal-track"] { + display: grid; + min-height: 20px; + line-height: 20px; + justify-items: start; + align-items: center; + overflow: visible; + transition: width var(--_dur) var(--_spring-soft); + } + + [data-slot="text-reveal-entering"], + [data-slot="text-reveal-leaving"] { + grid-area: 1 / 1; + line-height: 20px; + white-space: nowrap; + justify-self: start; + text-align: start; + mask-size: 100% 300%; + -webkit-mask-size: 100% 300%; + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + transition-duration: var(--_dur); + transition-timing-function: var(--_spring); + } + + /* ── entering: reveal top-to-bottom ── + * Gradient(to top): white at bottom, transparent at top of mask. + * Settled pos 0 100% = white covers element = visible + * Swap pos 0 0% = transparent covers = hidden + * Slides from above: translateY(-travel) → translateY(0) + */ + [data-slot="text-reveal-entering"] { + mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; + transition-property: + mask-position, + -webkit-mask-position, + transform; + transform: translateY(0); + } + + /* ── leaving: hide top-to-bottom + slide downward ── + * Gradient(to bottom): white at top, transparent at bottom of mask. + * Swap pos 0 0% = white covers element = visible + * Settled pos 0 100% = transparent covers = hidden + * Slides down: translateY(0) → translateY(travel) + */ + [data-slot="text-reveal-leaving"] { + mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; + transition-property: + mask-position, + -webkit-mask-position, + transform; + transform: translateY(var(--_travel)); + } + + /* ── swapping: instant reset ── + * Snap entering to hidden (above), leaving to visible (center). + */ + &[data-swapping="true"] [data-slot="text-reveal-entering"] { + mask-position: 0 0%; + -webkit-mask-position: 0 0%; + transform: translateY(calc(var(--_travel) * -1)); + transition-duration: 0ms !important; + } + + &[data-swapping="true"] [data-slot="text-reveal-leaving"] { + mask-position: 0 0%; + -webkit-mask-position: 0 0%; + transform: translateY(0); + transition-duration: 0ms !important; + } + + /* ── not ready: kill all transitions ── */ + &[data-ready="false"] [data-slot="text-reveal-track"] { + transition-duration: 0ms !important; + } + + &[data-ready="false"] [data-slot="text-reveal-entering"], + &[data-ready="false"] [data-slot="text-reveal-leaving"] { + transition-duration: 0ms !important; + } + + &[data-truncate="true"] { + width: 100%; + } + + &[data-truncate="true"] [data-slot="text-reveal-track"] { + width: 100%; + min-width: 0; + overflow: hidden; + } + + &[data-truncate="true"] [data-slot="text-reveal-entering"], + &[data-truncate="true"] [data-slot="text-reveal-leaving"] { + min-width: 0; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="text-reveal"] [data-slot="text-reveal-track"] { + transition-duration: 0ms !important; + } + + [data-component="text-reveal"] [data-slot="text-reveal-entering"], + [data-component="text-reveal"] [data-slot="text-reveal-leaving"] { + transition-duration: 0ms !important; + } +} diff --git a/packages/ui/src/components/text-reveal.stories.tsx b/packages/ui/src/components/text-reveal.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..af25d190c75a6dfd7a427961919f400ddbb2a9fd --- /dev/null +++ b/packages/ui/src/components/text-reveal.stories.tsx @@ -0,0 +1,320 @@ +// @ts-nocheck +import { onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { TextReveal } from "./text-reveal" + +export default { + title: "UI/TextReveal", + id: "components-text-reveal", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Playground for the TextReveal text transition component. + +**Hybrid** — mask wipe + vertical slide: gradient sweeps AND text moves downward. + +**Wipe only** — pure mask wipe: gradient sweeps top-to-bottom, text stays in place.`, + }, + }, + }, +} + +const TEXTS = [ + "Refactor ToolStatusTitle DOM measurement", + "Remove inline measure nodes", + "Run typechecks and report changes", + "Verify reduced-motion behavior", + "Review diff for animation edge cases", + "Check keyboard semantics", + undefined, + "Planning key generation details", + "Analyzing error handling", + "Considering edge cases", +] + +const btn = (accent?: boolean) => + ({ + padding: "5px 12px", + "border-radius": "6px", + border: accent ? "1px solid var(--color-accent, #58f)" : "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "12px", + }) as const + +const sliderLabel = { + width: "90px", + "font-size": "12px", + color: "var(--color-text-secondary, #a3a3a3)", + "flex-shrink": "0", +} as const + +const cardStyle = { + padding: "20px 24px", + "border-radius": "10px", + border: "1px solid var(--color-divider, #333)", + background: "var(--color-fill-element, #1a1a1a)", + display: "grid", + gap: "12px", +} as const + +const cardLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", +} as const + +const previewRow = { + display: "flex", + "align-items": "center", + gap: "8px", + "font-size": "14px", + "font-weight": "500", + "line-height": "20px", + color: "var(--text-weak, #aaa)", + "min-height": "20px", + overflow: "visible", +} as const + +const headingSlot = { + "min-width": "0", + overflow: "visible", + color: "var(--text-weaker, #888)", + "font-weight": "400", +} as const + +export const Playground = { + render: () => { + const [state, setState] = createStore({ + index: 0, + cycling: false, + growOnly: true, + duration: 600, + bounce: 1.0, + bounceSoft: 1.0, + hybridTravel: 25, + hybridEdge: 17, + edge: 17, + revealTravel: 0, + }) + const index = () => state.index + const cycling = () => state.cycling + const growOnly = () => state.growOnly + const duration = () => state.duration + const bounce = () => state.bounce + const bounceSoft = () => state.bounceSoft + const hybridTravel = () => state.hybridTravel + const hybridEdge = () => state.hybridEdge + const edge = () => state.edge + const revealTravel = () => state.revealTravel + + let timer: number | undefined + const text = () => TEXTS[index()] + const next = () => setState("index", (value) => (value + 1) % TEXTS.length) + const prev = () => setState("index", (value) => (value - 1 + TEXTS.length) % TEXTS.length) + + const toggleCycle = () => { + if (cycling()) { + if (timer) clearTimeout(timer) + timer = undefined + setState("cycling", false) + return + } + setState("cycling", true) + const tick = () => { + next() + timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600)) + } + timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600)) + } + + onCleanup(() => { + if (timer) clearTimeout(timer) + }) + + const spring = () => `cubic-bezier(0.34, ${bounce()}, 0.64, 1)` + const springSoft = () => `cubic-bezier(0.34, ${bounceSoft()}, 0.64, 1)` + + return ( +
+
+
+ text-reveal (mask wipe + slide) +
+ Thinking + + + +
+
+ +
+ text-reveal (mask wipe only) +
+ Thinking + + + +
+
+
+ +
+ {TEXTS.map((t, i) => ( + + ))} +
+ +
+ + + + +
+ +
+
Hybrid (wipe + slide)
+ + + + + +
Shared
+ + + + + + + +
+ Wipe only +
+ + + + +
+ +
+ text: {text() ?? "(none)"} · growOnly: {growOnly() ? "on" : "off"} +
+
+ ) + }, +} diff --git a/packages/ui/src/components/text-shimmer.css b/packages/ui/src/components/text-shimmer.css new file mode 100644 index 0000000000000000000000000000000000000000..f042dd2d862cc1e540aa7617fa45c4a7feabd0a4 --- /dev/null +++ b/packages/ui/src/components/text-shimmer.css @@ -0,0 +1,119 @@ +[data-component="text-shimmer"] { + --text-shimmer-step: 45ms; + --text-shimmer-duration: 1200ms; + --text-shimmer-swap: 220ms; + --text-shimmer-index: 0; + --text-shimmer-angle: 90deg; + --text-shimmer-spread: 5.2ch; + --text-shimmer-size: 360%; + --text-shimmer-base-color: var(--text-weak); + --text-shimmer-peak-color: var(--text-strong); + --text-shimmer-sweep: linear-gradient( + var(--text-shimmer-angle), + transparent calc(50% - var(--text-shimmer-spread)), + var(--text-shimmer-peak-color) 50%, + transparent calc(50% + var(--text-shimmer-spread)) + ); + --text-shimmer-base: linear-gradient(var(--text-shimmer-base-color), var(--text-shimmer-base-color)); + + display: inline-flex; + align-items: baseline; + font: inherit; + letter-spacing: inherit; + line-height: inherit; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char"] { + display: inline-grid; + white-space: pre; + font: inherit; + letter-spacing: inherit; + line-height: inherit; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-base"], +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + grid-area: 1 / 1; + white-space: pre; + transition: opacity var(--text-shimmer-swap) ease-out; + font: inherit; + letter-spacing: inherit; + line-height: inherit; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-base"] { + color: inherit; + opacity: 1; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + color: var(--text-weaker); + opacity: 0; +} + +[data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char-shimmer"] { + opacity: 1; +} + +[data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"][data-run="true"] { + animation-name: text-shimmer-sweep; + animation-duration: var(--text-shimmer-duration); + animation-iteration-count: infinite; + animation-timing-function: linear; + animation-fill-mode: both; + animation-delay: calc(var(--text-shimmer-step) * var(--text-shimmer-index) * -1); + will-change: background-position; +} + +@keyframes text-shimmer-sweep { + 0% { + background-position: + 100% 0, + 0 0; + } + + 100% { + background-position: + 0% 0, + 0 0; + } +} + +@supports ((-webkit-background-clip: text) or (background-clip: text)) { + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + color: transparent; + -webkit-text-fill-color: transparent; + background-image: var(--text-shimmer-sweep), var(--text-shimmer-base); + background-size: + var(--text-shimmer-size) 100%, + 100% 100%; + background-position: + 100% 0, + 0 0; + background-repeat: no-repeat; + -webkit-background-clip: text; + background-clip: text; + } + + [data-component="text-shimmer"][data-active="true"] [data-slot="text-shimmer-char-base"] { + opacity: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"], + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + transition-duration: 0ms; + } + + [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { + animation: none !important; + color: inherit; + -webkit-text-fill-color: currentColor; + background-image: none; + } + + [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"] { + opacity: 1 !important; + } +} diff --git a/packages/ui/src/components/text-shimmer.tsx b/packages/ui/src/components/text-shimmer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3ab077d92df8cc88370401343532894c86fc2199 --- /dev/null +++ b/packages/ui/src/components/text-shimmer.tsx @@ -0,0 +1,62 @@ +import { createEffect, createMemo, createSignal, onCleanup, type ValidComponent } from "solid-js" +import { Dynamic } from "solid-js/web" + +export const TextShimmer = (props: { + text: string + class?: string + as?: T + active?: boolean + offset?: number +}) => { + const text = createMemo(() => props.text ?? "") + const active = createMemo(() => props.active ?? true) + const offset = createMemo(() => props.offset ?? 0) + const [run, setRun] = createSignal(active()) + const swap = 220 + let timer: ReturnType | undefined + + createEffect(() => { + if (timer) { + clearTimeout(timer) + timer = undefined + } + + if (active()) { + setRun(true) + return + } + + timer = setTimeout(() => { + timer = undefined + setRun(false) + }, swap) + }) + + onCleanup(() => { + if (!timer) return + clearTimeout(timer) + }) + + return ( + + + + + + + ) +} diff --git a/packages/ui/src/components/text-strikethrough.stories.tsx b/packages/ui/src/components/text-strikethrough.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ef06dbcf4fbec91bf25590def20f2d0d16b4e02 --- /dev/null +++ b/packages/ui/src/components/text-strikethrough.stories.tsx @@ -0,0 +1,279 @@ +// @ts-nocheck +import { createSignal, onMount } from "solid-js" +import { createResizeObserver } from "@solid-primitives/resize-observer" +import { createStore } from "solid-js/store" +import { useSpring } from "./motion-spring" +import { TextStrikethrough } from "./text-strikethrough" + +const TEXT_SHORT = "Remove inline measure nodes" +const TEXT_MED = "Remove inline measure nodes and keep width morph behavior intact" +const TEXT_LONG = + "Refactor ToolStatusTitle DOM measurement to offscreen global measurer (unconstrained by timeline layout)" + +const btn = (active?: boolean) => + ({ + padding: "8px 18px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #444)", + background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "14px", + "font-weight": "500", + }) as const + +const heading = { + "font-size": "11px", + "font-weight": "600", + "text-transform": "uppercase" as const, + "letter-spacing": "0.05em", + color: "var(--text-weak, #888)", + "margin-bottom": "4px", +} + +const card = { + padding: "16px 20px", + "border-radius": "10px", + border: "1px solid var(--border-weak-base, #333)", + background: "var(--surface-base, #1a1a1a)", +} + +/* ─── Variant A: scaleX pseudo-line at 50% ─── */ +function VariantA(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + + ) +} + +/* ─── Variant D: background-image line ─── */ +function VariantD(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + ) +} + +/* ─── Variant E: grid stacking + clip-path (container %) ─── */ +function VariantE(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + return ( + + {props.text} + + + ) +} + +/* ─── Variant F: grid stacking + clip-path mapped to text width ─── */ +function VariantF(props: { active: boolean; text: string }) { + const progress = useSpring( + () => (props.active ? 1 : 0), + () => ({ visualDuration: 0.35, bounce: 0 }), + ) + let baseRef: HTMLSpanElement | undefined + let containerRef: HTMLSpanElement | undefined + const [state, setState] = createStore({ + textWidth: 0, + containerWidth: 0, + }) + const textWidth = () => state.textWidth + const containerWidth = () => state.containerWidth + + const measure = () => { + if (baseRef) setState("textWidth", baseRef.scrollWidth) + if (containerRef) setState("containerWidth", containerRef.offsetWidth) + } + + onMount(measure) + createResizeObserver(() => containerRef, measure) + + const clipRight = () => { + const cw = containerWidth() + const tw = textWidth() + if (cw <= 0 || tw <= 0) return `${(1 - progress()) * 100}%` + const revealed = progress() * tw + const remaining = Math.max(0, cw - revealed) + return `${remaining}px` + } + + return ( + + + {props.text} + + + + ) +} + +export default { + title: "UI/Text Strikethrough", + id: "components-text-strikethrough", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Animated Strikethrough Variants + +- **A** — scaleX line at 50% (single line only) +- **D** — background-image line (single line only) +- **E** — grid stacking + clip-path (container %) +- **F** — grid stacking + clip-path mapped to text width (the real component)`, + }, + }, + }, +} + +export const Playground = { + render: () => { + const [active, setActive] = createSignal(false) + const toggle = () => setActive((v) => !v) + + return ( +
+ + +
+
F — grid stacking + clip mapped to text width (THE COMPONENT)
+ +
+ +
+ +
+ +
+
F (inline) — same but just inline variants
+ +
+ +
+ +
+ +
+
E — grid stacking + clip-path (container %)
+ +
+ +
+ +
+ +
+
A — scaleX line at 50%
+ +
+ +
+ +
+
D — background-image line
+ +
+ +
+
+ ) + }, +} diff --git a/packages/ui/src/components/thinking-heading.stories.tsx b/packages/ui/src/components/thinking-heading.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..12a06b4d8331d08dd2430506fdafa3067061dae4 --- /dev/null +++ b/packages/ui/src/components/thinking-heading.stories.tsx @@ -0,0 +1,854 @@ +// @ts-nocheck +import { createEffect, on, onMount, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { TextShimmer } from "./text-shimmer" +import { TextReveal } from "./text-reveal" + +export default { + title: "UI/ThinkingHeading", + id: "components-thinking-heading", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Playground for animating the secondary heading beside "Thinking". + +Uses TextReveal for the production heading animation with tunable +duration, travel, bounce, and fade controls.`, + }, + }, + }, +} + +const HEADINGS = [ + "Planning key generation details", + "Analyzing error handling", + undefined, + "Reviewing authentication flow", + "Considering edge cases", + "Evaluating performance", + "Structuring the response", + "Checking type safety", + "Designing the API surface", + "Mapping dependencies", + "Outlining test strategy", +] + +// --------------------------------------------------------------------------- +// CSS +// +// Custom properties driven by sliders: +// --h-duration transition duration (e.g. "600ms") +// --h-duration-raw unitless number for calc (e.g. "600") +// --h-blur blur radius (e.g. "4px") +// --h-travel vertical travel distance (e.g. "18px") +// --h-spring full cubic-bezier for movement (set from bounce slider) +// --h-spring-soft softer version for width transitions +// --h-mask-size fade depth at top/bottom of odometer mask +// --h-mask-pad base padding-block on odometer track +// --h-mask-height extra vertical mask area per side +// --h-mask-bg background color for fade overlays +// --------------------------------------------------------------------------- + +const STYLES = ` +/* ── shared base ────────────────────────────────────────────────── */ +[data-variant] { + display: inline-flex; + align-items: center; +} + +[data-variant] [data-slot="track"] { + display: grid; + overflow: visible; + min-height: 20px; + justify-items: start; + align-items: center; + transition: width var(--h-duration, 600ms) var(--h-spring-soft, cubic-bezier(0.34, 1.1, 0.64, 1)); +} + +[data-variant] [data-slot="entering"], +[data-variant] [data-slot="leaving"] { + grid-area: 1 / 1; + line-height: 20px; + white-space: nowrap; + justify-self: start; +} + +/* kill transitions before fonts are ready */ +[data-variant][data-ready="false"] [data-slot="track"], +[data-variant][data-ready="false"] [data-slot="entering"], +[data-variant][data-ready="false"] [data-slot="leaving"] { + transition-duration: 0ms !important; +} + + +/* ── 1. spring-up ───────────────────────────────────────────────── * + * New text rises from below, old text exits upward. */ + +[data-variant="spring-up"] [data-slot="entering"], +[data-variant="spring-up"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; +} +[data-variant="spring-up"] [data-slot="entering"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-up"] [data-slot="leaving"] { + transform: translateY(calc(var(--h-travel, 18px) * -1)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); +} +[data-variant="spring-up"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(var(--h-travel, 18px)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); + transition-duration: 0ms !important; +} +[data-variant="spring-up"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 2. spring-down ─────────────────────────────────────────────── * + * New text drops from above, old text exits downward. */ + +[data-variant="spring-down"] [data-slot="entering"], +[data-variant="spring-down"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; +} +[data-variant="spring-down"] [data-slot="entering"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-down"] [data-slot="leaving"] { + transform: translateY(var(--h-travel, 18px)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); +} +[data-variant="spring-down"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-travel, 18px) * -1)); + opacity: 0; + filter: blur(var(--h-blur, 0px)); + transition-duration: 0ms !important; +} +[data-variant="spring-down"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 3. spring-pop ──────────────────────────────────────────────── * + * Scale + slight vertical shift + blur. Playful, bouncy. */ + +[data-variant="spring-pop"] [data-slot="entering"], +[data-variant="spring-pop"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.55 * 1ms), + calc(var(--h-duration-raw, 600) * 0.55 * 1ms); + transition-timing-function: var(--h-spring), ease-out, ease-out; + transform-origin: left center; +} +[data-variant="spring-pop"] [data-slot="entering"] { + transform: translateY(0) scale(1); + opacity: 1; + filter: blur(0); +} +[data-variant="spring-pop"] [data-slot="leaving"] { + transform: translateY(calc(var(--h-travel, 18px) * -0.35)) scale(0.92); + opacity: 0; + filter: blur(var(--h-blur, 3px)); +} +[data-variant="spring-pop"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-travel, 18px) * 0.35)) scale(0.92); + opacity: 0; + filter: blur(var(--h-blur, 3px)); + transition-duration: 0ms !important; +} +[data-variant="spring-pop"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0) scale(1); + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 4. spring-blur ─────────────────────────────────────────────── * + * Pure crossfade with heavy blur. No vertical movement. * + * Width still animates with spring. */ + +[data-variant="spring-blur"] [data-slot="entering"], +[data-variant="spring-blur"] [data-slot="leaving"] { + transition-property: opacity, filter; + transition-duration: + calc(var(--h-duration-raw, 600) * 0.75 * 1ms), + var(--h-duration, 600ms); + transition-timing-function: ease-out, var(--h-spring-soft); +} +[data-variant="spring-blur"] [data-slot="entering"] { + opacity: 1; + filter: blur(0); +} +[data-variant="spring-blur"] [data-slot="leaving"] { + opacity: 0; + filter: blur(calc(var(--h-blur, 4px) * 2)); +} +[data-variant="spring-blur"][data-swapping="true"] [data-slot="entering"] { + opacity: 0; + filter: blur(calc(var(--h-blur, 4px) * 2)); + transition-duration: 0ms !important; +} +[data-variant="spring-blur"][data-swapping="true"] [data-slot="leaving"] { + opacity: 1; + filter: blur(0); + transition-duration: 0ms !important; +} + + +/* ── 5. odometer ──────────────────────────────────────────────── * + * Both texts scroll vertically through a clipped track. * + * * + * overflow:hidden clips at the padding-box edge. * + * mask-image fades to transparent at that same edge. * + * Result: content is invisible at the clip boundary → no hard * + * edge ever visible. Padding + mask height extend the clip area * + * so text has room to travel through the gradient fade zone. * + * * + * Uses transparent→white which works in both alpha & luminance * + * mask modes (transparent=hidden, white=visible in both). */ + +[data-variant="odometer"] [data-slot="track"] { + --h-mask-stop: min(var(--h-mask-size, 20px), calc(50% - 0.5px)); + --h-odo-shift: calc( + 100% + var(--h-travel, 18px) + var(--h-mask-height, 0px) + max(calc(var(--h-mask-pad, 28px) - 28px), 0px) + ); + position: relative; + align-items: stretch; + overflow: hidden; + padding-block: calc(var(--h-mask-pad, 28px) + var(--h-mask-height, 0px)); + margin-block: calc((var(--h-mask-pad, 28px) + var(--h-mask-height, 0px)) * -1); + -webkit-mask-image: linear-gradient( + to bottom, + transparent 0px, + white var(--h-mask-stop), + white calc(100% - var(--h-mask-stop)), + transparent 100% + ); + mask-image: linear-gradient( + to bottom, + transparent 0px, + white var(--h-mask-stop), + white calc(100% - var(--h-mask-stop)), + transparent 100% + ); + transition: width var(--h-duration, 600ms) var(--h-spring-soft, cubic-bezier(0.34, 1.1, 0.64, 1)); +} + +/* on swap, jump width instantly to the max of both texts */ +[data-variant="odometer"][data-swapping="true"] [data-slot="track"] { + transition-duration: 0ms !important; +} + +[data-variant="odometer"] [data-slot="entering"], +[data-variant="odometer"] [data-slot="leaving"] { + transition-property: transform; + transition-duration: var(--h-duration, 600ms); + transition-timing-function: var(--h-spring); + opacity: 1; +} +/* settled: entering in view, leaving pushed below */ +[data-variant="odometer"] [data-slot="entering"] { + transform: translateY(0); +} +[data-variant="odometer"] [data-slot="leaving"] { + transform: translateY(var(--h-odo-shift)); +} +/* swapping: snap entering above, leaving in-place */ +[data-variant="odometer"][data-swapping="true"] [data-slot="entering"] { + transform: translateY(calc(var(--h-odo-shift) * -1)); + transition-duration: 0ms !important; +} +[data-variant="odometer"][data-swapping="true"] [data-slot="leaving"] { + transform: translateY(0); + transition-duration: 0ms !important; +} + +/* ── odometer + blur ──────────────────────────────────────────── * + * Optional: adds opacity + blur transitions on top of the * + * positional odometer movement. */ + +[data-variant="odometer"][data-odo-blur="true"] [data-slot="entering"], +[data-variant="odometer"][data-odo-blur="true"] [data-slot="leaving"] { + transition-property: transform, opacity, filter; + transition-duration: + var(--h-duration, 600ms), + calc(var(--h-duration-raw, 600) * 0.6 * 1ms), + calc(var(--h-duration-raw, 600) * 0.5 * 1ms); +} +[data-variant="odometer"][data-odo-blur="true"] [data-slot="entering"] { + opacity: 1; + filter: blur(0); +} +[data-variant="odometer"][data-odo-blur="true"] [data-slot="leaving"] { + opacity: 0; + filter: blur(var(--h-blur, 4px)); +} +[data-variant="odometer"][data-odo-blur="true"][data-swapping="true"] [data-slot="entering"] { + opacity: 0; + filter: blur(var(--h-blur, 4px)); +} +[data-variant="odometer"][data-odo-blur="true"][data-swapping="true"] [data-slot="leaving"] { + opacity: 1; + filter: blur(0); +} + +/* ── debug: show fade zones ───────────────────────────────────── */ +[data-variant="odometer"][data-debug="true"] [data-slot="track"] { + outline: 1px dashed rgba(255, 0, 0, 0.6); +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::before, +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: var(--h-mask-stop); + pointer-events: none; +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::before { + top: 0; + background: linear-gradient(to bottom, rgba(255, 0, 0, 0.3), transparent); +} +[data-variant="odometer"][data-debug="true"] [data-slot="track"]::after { + bottom: 0; + background: linear-gradient(to top, rgba(255, 0, 0, 0.3), transparent); +} + + +/* ── slider styling ─────────────────────────────────────────────── */ +input[type="range"].heading-slider { + -webkit-appearance: none; + appearance: none; + width: 140px; + height: 4px; + border-radius: 2px; + background: var(--color-divider, #444); + outline: none; +} +input[type="range"].heading-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--color-accent, #58f); + cursor: pointer; + border: none; +} +` + +// --------------------------------------------------------------------------- +// Animated heading component +// +// Width is measured via scrollWidth (NOT Range.getBoundingClientRect) because +// getBoundingClientRect includes CSS transforms — so scale(0.92) during the +// swap phase would measure 92% of the real width and permanently clip text. +// scrollWidth returns the layout/intrinsic width, unaffected by transforms. +// --------------------------------------------------------------------------- + +function AnimatedHeading(props) { + const [state, setState] = createStore({ + current: props.text, + leaving: undefined, + width: "auto", + ready: false, + swapping: false, + }) + const current = () => state.current + const leaving = () => state.leaving + const width = () => state.width + const ready = () => state.ready + const swapping = () => state.swapping + let enterRef + let leaveRef + let containerRef + let frame + + const measureEnter = () => enterRef?.scrollWidth ?? 0 + const measureLeave = () => leaveRef?.scrollWidth ?? 0 + const widen = (px) => { + if (px <= 0) return + const w = Number.parseFloat(width()) + if (Number.isFinite(w) && px <= w) return + setState("width", `${px}px`) + } + + const measure = () => { + if (!current()) { + setState("width", "0px") + return + } + const px = measureEnter() + if (px > 0) setState("width", `${px}px`) + } + + createEffect( + on( + () => props.text, + (next, prev) => { + if (next === prev) return + setState("swapping", true) + setState("leaving", prev) + setState("current", next) + + if (frame) cancelAnimationFrame(frame) + frame = requestAnimationFrame(() => { + // For odometer keep width as a grow-only max so heading never shrinks. + if (props.variant === "odometer") { + const enterW = measureEnter() + const leaveW = measureLeave() + widen(Math.max(enterW, leaveW)) + containerRef?.offsetHeight // reflow with max width + swap positions + setState("swapping", false) + } else { + containerRef?.offsetHeight + setState("swapping", false) + measure() + } + frame = undefined + }) + }, + ), + ) + + onMount(() => { + measure() + void document.fonts?.ready.finally(() => { + measure() + requestAnimationFrame(() => setState("ready", true)) + }) + }) + + onCleanup(() => { + if (frame) cancelAnimationFrame(frame) + }) + + return ( + + + + {current() ?? "\u00A0"} + + + {leaving() ?? "\u00A0"} + + + + ) +} + +// --------------------------------------------------------------------------- +// Button / layout styles +// --------------------------------------------------------------------------- + +const btn = (accent) => ({ + padding: "6px 14px", + "border-radius": "6px", + border: "1px solid var(--color-divider, #333)", + background: accent ? "var(--color-danger-fill, #c33)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "13px", +}) + +const smallBtn = (active) => ({ + padding: "4px 12px", + "border-radius": "6px", + border: active ? "1px solid var(--color-accent, #58f)" : "1px solid var(--color-divider, #333)", + background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)", + color: "var(--color-text, #eee)", + cursor: "pointer", + "font-size": "12px", +}) + +const sliderLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", + "min-width": "70px", + "flex-shrink": "0", + "text-align": "right", +} + +const sliderValue = { + "font-family": "monospace", + "font-size": "11px", + color: "var(--color-text-weak, #aaa)", + "min-width": "60px", +} + +const cardLabel = { + "font-size": "11px", + "font-family": "monospace", + color: "var(--color-text-weak, #666)", +} + +const thinkingRow = { + display: "flex", + "align-items": "center", + gap: "8px", + "min-width": "0", + "font-size": "14px", + "font-weight": "500", + "line-height": "20px", + "min-height": "20px", + color: "var(--text-weak, #aaa)", +} + +const headingSlot = { + "min-width": "0", + overflow: "visible", + "white-space": "nowrap", + color: "var(--text-weaker, #888)", + "font-weight": "400", +} + +const cardStyle = { + padding: "16px 20px", + "border-radius": "10px", + border: "1px solid var(--color-divider, #333)", + background: "var(--h-mask-bg, #1a1a1a)", + display: "grid", + gap: "8px", +} + +// --------------------------------------------------------------------------- +// Variants +// --------------------------------------------------------------------------- + +const VARIANTS: { key: string; label: string }[] = [] + +// --------------------------------------------------------------------------- +// Story +// --------------------------------------------------------------------------- + +export const Playground = { + render: () => { + const [state, setState] = createStore({ + heading: HEADINGS[0], + headingIndex: 0, + active: true, + cycling: false, + duration: 550, + blur: 2, + travel: 4, + bounce: 1.35, + maskSize: 12, + maskPad: 9, + maskHeight: 0, + debug: false, + odoBlur: false, + }) + const heading = () => state.heading + const headingIndex = () => state.headingIndex + const active = () => state.active + const cycling = () => state.cycling + const duration = () => state.duration + const blur = () => state.blur + const travel = () => state.travel + const bounce = () => state.bounce + const maskSize = () => state.maskSize + const maskPad = () => state.maskPad + const maskHeight = () => state.maskHeight + const debug = () => state.debug + const odoBlur = () => state.odoBlur + let cycleTimer + + const nextHeading = () => { + const next = (headingIndex() + 1) % HEADINGS.length + setState("headingIndex", next) + setState("heading", HEADINGS[next]) + } + + const prevHeading = () => { + const prev = (headingIndex() - 1 + HEADINGS.length) % HEADINGS.length + setState("headingIndex", prev) + setState("heading", HEADINGS[prev]) + } + + const toggleCycling = () => { + if (cycling()) { + clearTimeout(cycleTimer) + cycleTimer = undefined + setState("cycling", false) + return + } + setState("cycling", true) + const tick = () => { + if (!cycling()) return + nextHeading() + cycleTimer = setTimeout(tick, 850 + Math.floor(Math.random() * 550)) + } + cycleTimer = setTimeout(tick, 850 + Math.floor(Math.random() * 550)) + } + + const clearHeading = () => { + setState("heading", undefined) + if (cycling()) { + clearTimeout(cycleTimer) + cycleTimer = undefined + setState("cycling", false) + } + } + + onCleanup(() => { + if (cycleTimer) clearTimeout(cycleTimer) + }) + + const vars = () => ({ + "--h-duration": `${duration()}ms`, + "--h-duration-raw": `${duration()}`, + "--h-blur": `${blur()}px`, + "--h-travel": `${travel()}px`, + "--h-spring": `cubic-bezier(0.34, ${bounce()}, 0.64, 1)`, + "--h-spring-soft": `cubic-bezier(0.34, ${Math.max(bounce() * 0.7, 1)}, 0.64, 1)`, + "--h-mask-size": `${maskSize()}px`, + "--h-mask-pad": `${maskPad()}px`, + "--h-mask-height": `${maskHeight()}px`, + "--h-mask-bg": "#1a1a1a", + }) + + return ( +
+ + + {/* ── Variant cards ─────────────────────────────────── */} +
+
+ TextReveal (production) + + + + + + +
+ {VARIANTS.map((v) => ( +
+ {v.label} + + + + + + +
+ ))} +
+ + {/* ── Sliders ──────────────────────────────────────── */} +
+
+ duration + setState("duration", Number(e.currentTarget.value))} + /> + {duration()}ms +
+ +
+ blur + setState("blur", Number(e.currentTarget.value))} + /> + {blur()}px +
+ +
+ travel + setState("travel", Number(e.currentTarget.value))} + /> + {travel()}px +
+ +
+ bounce + setState("bounce", Number(e.currentTarget.value))} + /> + + {bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""} + +
+ +
+ mask + setState("maskSize", Number(e.currentTarget.value))} + /> + + {maskSize()}px {maskSize() === 0 ? "(hard)" : ""} + +
+ +
+ mask pad + setState("maskPad", Number(e.currentTarget.value))} + /> + {maskPad()}px +
+ +
+ mask height + setState("maskHeight", Number(e.currentTarget.value))} + /> + {maskHeight()}px +
+
+ + {/* ── Controls ─────────────────────────────────────── */} +
+
+ + + + + + + +
+ +
+ {HEADINGS.map((h, i) => ( + + ))} +
+ +
+ heading: {heading() ?? "(none)"} · sim: {cycling() ? "on" : "off"} · bounce: {bounce().toFixed(2)} · + odo-blur: {odoBlur() ? "on" : "off"} +
+
+
+ ) + }, +} diff --git a/packages/ui/src/components/toast.css b/packages/ui/src/components/toast.css new file mode 100644 index 0000000000000000000000000000000000000000..a7b40923c82aa5f8b60447397c94231c966e1714 --- /dev/null +++ b/packages/ui/src/components/toast.css @@ -0,0 +1,236 @@ +[data-component="toast-region"] { + position: fixed; + bottom: 48px; + inset-inline-end: 32px; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 8px; + max-width: min(400px, calc(100vw - 64px)); + max-height: calc(100dvh - 96px); + width: 100%; + overflow: hidden; + pointer-events: none; + + [data-slot="toast-list"] { + display: flex; + flex-direction: column; + gap: 8px; + list-style: none; + margin: 0; + padding: 0; + max-height: 100%; + overflow-y: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } +} + +[data-component="toast"] { + position: relative; + display: flex; + align-items: flex-start; + gap: 20px; + padding: 16px 20px; + max-height: min(420px, calc(100dvh - 96px)); + overflow: hidden; + pointer-events: auto; + transition: all 150ms ease-out; + + border-radius: var(--radius-lg); + border: 1px solid var(--border-weak-base); + background: var(--surface-float-base); + color: var(--text-invert-base); + box-shadow: var(--shadow-md); + + [data-slot="toast-inner"] { + display: flex; + align-items: flex-start; + gap: 10px; + } + + &[data-opened] { + animation: toastPopIn 150ms ease-out; + } + + &[data-closed] { + animation: toastPopOut 100ms ease-in forwards; + } + + &[data-swipe="move"] { + transform: translateX(var(--kb-toast-swipe-move-x)); + } + + &[data-swipe="cancel"] { + transform: translateX(0); + transition: transform 200ms ease-out; + } + + &[data-swipe="end"] { + animation: toastSwipeOut 100ms ease-out forwards; + } + + /* &[data-variant="success"] { */ + /* border-color: var(--color-semantic-positive); */ + /* } */ + /**/ + /* &[data-variant="error"] { */ + /* border-color: var(--color-semantic-danger); */ + /* } */ + /**/ + /* &[data-variant="loading"] { */ + /* border-color: var(--color-semantic-info); */ + /* } */ + + [data-slot="toast-icon"] { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + + [data-component="icon"] { + color: var(--text-invert-stronger); + /* color: var(--icon-invert-base); */ + } + } + + [data-slot="toast-content"] { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-height: 0; + min-width: 0; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + + [data-slot="toast-title"] { + color: var(--text-invert-strong); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + /* text-14-medium */ + font-family: var(--font-family-sans); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 142.857% */ + letter-spacing: var(--letter-spacing-normal); + + margin: 0; + } + + [data-slot="toast-description"] { + color: var(--text-invert-base); + text-wrap-style: pretty; + overflow-wrap: anywhere; + word-break: break-word; + + /* text-14-regular */ + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-x-large); /* 171.429% */ + letter-spacing: var(--letter-spacing-normal); + + margin: 0; + } + + [data-slot="toast-actions"] { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-top: 8px; + } + + [data-slot="toast-action"] { + background: none; + border: none; + padding: 0; + cursor: pointer; + min-width: 0; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + color: var(--text-invert-weak); + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + + &:hover { + text-decoration: underline; + } + + &:first-child { + color: var(--text-invert-strong); + } + } + + [data-slot="toast-close-button"] { + flex-shrink: 0; + } + + [data-slot="toast-progress-track"] { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background-color: var(--surface-base); + border-radius: 0 0 var(--radius-lg) var(--radius-lg); + overflow: hidden; + } + + [data-slot="toast-progress-fill"] { + height: 100%; + width: var(--kb-toast-progress-fill-width); + background-color: var(--color-primary); + transition: width 250ms linear; + } +} + +@keyframes toastPopIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes toastPopOut { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(20px); + } +} + +@keyframes toastSwipeOut { + from { + transform: translateX(var(--kb-toast-swipe-end-x)); + } + to { + transform: translateX(100%); + } +} diff --git a/packages/ui/src/components/toast.tsx b/packages/ui/src/components/toast.tsx new file mode 100644 index 0000000000000000000000000000000000000000..599cf2a9ea71d8c9c21388cbcce5c3811bd232bc --- /dev/null +++ b/packages/ui/src/components/toast.tsx @@ -0,0 +1,185 @@ +import { Toast as Kobalte, toaster } from "@kobalte/core/toast" +import type { ToastRootProps, ToastCloseButtonProps, ToastTitleProps, ToastDescriptionProps } from "@kobalte/core/toast" +import type { ComponentProps, JSX } from "solid-js" +import { Show } from "solid-js" +import { Portal } from "solid-js/web" +import { useI18n } from "../context/i18n" +import { Icon, type IconProps } from "./icon" +import { IconButton } from "./icon-button" + +export interface ToastRegionProps extends ComponentProps {} + +function ToastRegion(props: ToastRegionProps) { + return ( + + + + + + ) +} + +export interface ToastRootComponentProps extends ToastRootProps { + class?: string + classList?: ComponentProps<"li">["classList"] + children?: JSX.Element +} + +function ToastRoot(props: ToastRootComponentProps) { + return ( + + ) +} + +function ToastIcon(props: { name: IconProps["name"] }) { + return ( +
+ +
+ ) +} + +function ToastContent(props: ComponentProps<"div">) { + return
+} + +function ToastTitle(props: ToastTitleProps & ComponentProps<"div">) { + return +} + +function ToastDescription(props: ToastDescriptionProps & ComponentProps<"div">) { + return +} + +function ToastActions(props: ComponentProps<"div">) { + return
+} + +function ToastCloseButton(props: ToastCloseButtonProps & ComponentProps<"button">) { + const i18n = useI18n() + return ( + + ) +} + +function ToastProgressTrack(props: ComponentProps) { + return +} + +function ToastProgressFill(props: ComponentProps) { + return +} + +export const Toast = Object.assign(ToastRoot, { + Region: ToastRegion, + Icon: ToastIcon, + Content: ToastContent, + Title: ToastTitle, + Description: ToastDescription, + Actions: ToastActions, + CloseButton: ToastCloseButton, + ProgressTrack: ToastProgressTrack, + ProgressFill: ToastProgressFill, +}) + +export { toaster } + +export type ToastVariant = "default" | "success" | "error" | "loading" + +export interface ToastAction { + label: string + onClick: "dismiss" | (() => void) +} + +export interface ToastOptions { + title?: string + description?: string + icon?: IconProps["name"] + variant?: ToastVariant + duration?: number + persistent?: boolean + actions?: ToastAction[] +} + +export function showToast(options: ToastOptions | string) { + const opts = typeof options === "string" ? { description: options } : options + return toaster.show((props) => ( + + + + + + + {opts.title} + + + {opts.description} + + + + {opts.actions!.map((action) => ( + + ))} + + + + + + )) +} + +export interface ToastPromiseOptions { + loading?: JSX.Element + success?: (data: T) => JSX.Element + error?: (error: U) => JSX.Element +} + +export function showPromiseToast( + promise: Promise | (() => Promise), + options: ToastPromiseOptions, +) { + return toaster.promise(promise, (props) => ( + + + + {props.state === "pending" && options.loading} + {props.state === "fulfilled" && options.success?.(props.data!)} + {props.state === "rejected" && options.error?.(props.error)} + + + + + )) +} diff --git a/packages/ui/src/components/tooltip.stories.tsx b/packages/ui/src/components/tooltip.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..efe11d92efb9298bf48b8204fdafdcc1a437a7bc --- /dev/null +++ b/packages/ui/src/components/tooltip.stories.tsx @@ -0,0 +1,64 @@ +// @ts-nocheck +import * as mod from "./tooltip" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Tooltip for contextual hints and keybind callouts. + +Use for short hints; avoid long descriptions. + +### API +- Required: \`value\` (tooltip content). +- Optional: \`inactive\`, \`forceOpen\`, placement props from Kobalte. + +### Variants and states +- Supports keybind-style tooltip via \`TooltipKeybind\`. + +### Behavior +- Opens on hover/focus; can be forced open. + +### Accessibility +- TODO: confirm trigger semantics and focus behavior. + +### Theming/tokens +- Uses \`data-component="tooltip"\` and related slots. + +` + +const story = create({ title: "UI/Tooltip", mod, args: { value: "Tooltip", children: "Hover me" } }) + +export default { + title: "UI/Tooltip", + id: "components-tooltip", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const Keybind = { + render: () => ( + + Hover for keybind + + ), +} + +export const ForcedOpen = { + args: { + forceOpen: true, + }, +} + +export const Inactive = { + args: { + inactive: true, + }, +} diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6a2f4b55d4526f393849ba4e3fb6da170f1904c4 --- /dev/null +++ b/packages/ui/src/components/tooltip.tsx @@ -0,0 +1,163 @@ +import { Tooltip as KobalteTooltip } from "@kobalte/core/tooltip" +import { createEffect, Match, onCleanup, splitProps, Switch, type JSX } from "solid-js" +import type { ComponentProps } from "solid-js" +import { createStore } from "solid-js/store" + +export interface TooltipProps extends ComponentProps { + value: JSX.Element + class?: string + contentClass?: string + contentStyle?: JSX.CSSProperties + inactive?: boolean + forceOpen?: boolean +} + +export interface TooltipKeybindProps extends Omit { + title: string + keybind: string +} + +export function TooltipKeybind(props: TooltipKeybindProps) { + const [local, others] = splitProps(props, ["title", "keybind"]) + return ( + + {local.title} + {local.keybind} +
+ } + /> + ) +} + +export function Tooltip(props: TooltipProps) { + let ref: HTMLDivElement | undefined + const [state, setState] = createStore({ + open: false, + block: false, + expand: false, + }) + const [local, others] = splitProps(props, [ + "children", + "class", + "contentClass", + "contentStyle", + "inactive", + "forceOpen", + "ignoreSafeArea", + "value", + ]) + + const close = () => setState("open", false) + + const inside = () => { + const active = document.activeElement + if (!ref || !active) return false + return ref.contains(active) + } + + const drop = (expand = state.expand) => { + if (expand) return + if (ref?.matches(":hover")) return + if (inside()) return + setState("block", false) + } + + const sync = () => { + const expand = !!ref?.querySelector('[aria-expanded="true"], [data-expanded]') + setState("expand", expand) + if (expand) { + setState("block", true) + close() + return + } + drop(expand) + } + + const arm = () => { + setState("block", true) + close() + } + + const leave = () => { + if (!inside()) close() + drop() + } + + createEffect(() => { + if (!ref) return + sync() + const obs = new MutationObserver(sync) + obs.observe(ref, { + subtree: true, + childList: true, + attributes: true, + attributeFilter: ["aria-expanded", "data-expanded"], + }) + onCleanup(() => obs.disconnect()) + }) + + let justClickedTrigger = false + + return ( + + {local.children} + + { + if (local.forceOpen) return + if (state.block && open) return + if (justClickedTrigger) { + justClickedTrigger = false + return + } + setState("open", open) + }} + > + { + if (event.key !== "Enter" && event.key !== " ") return + arm() + }} + onPointerLeave={leave} + onFocusOut={() => requestAnimationFrame(() => drop())} + > + {local.children} + + + { + if (ref === e.target || (e.target instanceof Node && ref?.contains(e.target))) { + justClickedTrigger = true + } + e.preventDefault() + }} + > + {local.value} + {/* */} + + + + + + ) +} diff --git a/packages/ui/src/components/typewriter.stories.tsx b/packages/ui/src/components/typewriter.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..880ca74894341e1912a742c738430a4d80ed8862 --- /dev/null +++ b/packages/ui/src/components/typewriter.stories.tsx @@ -0,0 +1,51 @@ +// @ts-nocheck +import * as mod from "./typewriter" +import { create } from "../storybook/scaffold" + +const docs = `### Overview +Animated typewriter text effect for short inline messages. + +Use for short status lines; avoid long paragraphs. + +### API +- Optional: \`text\` string; if absent, nothing is rendered. +- Optional: \`as\` to change the rendered element. + +### Variants and states +- Single animation style with cursor blink. + +### Behavior +- Types one character at a time with randomized delays. + +### Accessibility +- TODO: confirm if cursor should be aria-hidden in all contexts. + +### Theming/tokens +- Uses \`blinking-cursor\` class for cursor styling. + +` + +const story = create({ title: "UI/Typewriter", mod, args: { text: "Typewriter text" } }) + +export default { + title: "UI/Typewriter", + id: "components-typewriter", + component: story.meta.component, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = story.Basic + +export const Inline = { + args: { + text: "Inline typewriter", + as: "span", + }, +} diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cb76ca82897758fc8fbcbfdbc6ed018e343e4eed --- /dev/null +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -0,0 +1,237 @@ +import { createEffect, on, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { createEventListener } from "@solid-primitives/event-listener" +import { createResizeObserver } from "@solid-primitives/resize-observer" + +export interface AutoScrollOptions { + working: () => boolean + onUserInteracted?: () => void + overflowAnchor?: "none" | "auto" | "dynamic" + bottomThreshold?: number +} + +export function createAutoScroll(options: AutoScrollOptions) { + let settling = false + let settleTimer: ReturnType | undefined + let autoTimer: ReturnType | undefined + let auto: { top: number; time: number } | undefined + + const threshold = () => options.bottomThreshold ?? 10 + + const [store, setStore] = createStore({ + contentRef: undefined as HTMLElement | undefined, + scrollRef: undefined as HTMLElement | undefined, + userScrolled: false, + }) + + const active = () => options.working() || settling + + const distanceFromBottom = (el: HTMLElement) => { + return el.scrollHeight - el.clientHeight - el.scrollTop + } + + const canScroll = (el: HTMLElement) => { + return el.scrollHeight - el.clientHeight > 1 + } + + // Browsers can dispatch scroll events asynchronously. If new content arrives + // between us calling `scrollTo()` and the subsequent `scroll` event firing, + // the handler can see a non-zero `distanceFromBottom` and incorrectly assume + // the user scrolled. + const markAuto = (el: HTMLElement) => { + auto = { + top: Math.max(0, el.scrollHeight - el.clientHeight), + time: Date.now(), + } + + if (autoTimer) clearTimeout(autoTimer) + autoTimer = setTimeout(() => { + auto = undefined + autoTimer = undefined + }, 1500) + } + + const isAuto = (el: HTMLElement) => { + const a = auto + if (!a) return false + + if (Date.now() - a.time > 1500) { + auto = undefined + return false + } + + return Math.abs(el.scrollTop - a.top) < 2 + } + + const scrollToBottomNow = (behavior: ScrollBehavior) => { + const el = store.scrollRef + if (!el) return + markAuto(el) + if (behavior === "smooth") { + el.scrollTo({ top: el.scrollHeight, behavior }) + return + } + + // `scrollTop` assignment bypasses any CSS `scroll-behavior: smooth`. + el.scrollTop = el.scrollHeight + } + + const scrollToBottom = (force: boolean) => { + if (!force && !active()) return + + if (force && store.userScrolled) setStore("userScrolled", false) + + const el = store.scrollRef + if (!el) return + + if (!force && store.userScrolled) return + + const distance = distanceFromBottom(el) + if (distance < 2) { + markAuto(el) + return + } + + // For auto-following content we prefer immediate updates to avoid + // visible "catch up" animations while content is still settling. + scrollToBottomNow("auto") + } + + const stop = () => { + const el = store.scrollRef + if (!el) return + if (!canScroll(el)) { + if (store.userScrolled) setStore("userScrolled", false) + return + } + if (store.userScrolled) return + + setStore("userScrolled", true) + options.onUserInteracted?.() + } + + const handleWheel = (e: WheelEvent) => { + if (e.deltaY >= 0) return + // If the user is scrolling within a nested scrollable region (tool output, + // code block, etc), don't treat it as leaving the "follow bottom" mode. + // Those regions opt in via `data-scrollable`. + const el = store.scrollRef + const target = e.target instanceof Element ? e.target : undefined + const nested = target?.closest("[data-scrollable]") + if (el && nested && nested !== el) return + stop() + } + + const handleScroll = () => { + const el = store.scrollRef + if (!el) return + + if (!canScroll(el)) { + if (store.userScrolled) setStore("userScrolled", false) + return + } + + if (distanceFromBottom(el) < threshold()) { + if (store.userScrolled) setStore("userScrolled", false) + return + } + + // Ignore scroll events triggered by our own scrollToBottom calls. + if (!store.userScrolled && isAuto(el)) { + scrollToBottom(false) + return + } + + stop() + } + + const handleInteraction = () => { + if (!active()) return + const selection = window.getSelection() + if (selection && selection.toString().length > 0) { + stop() + } + } + + const updateOverflowAnchor = (el: HTMLElement) => { + const mode = options.overflowAnchor ?? "dynamic" + + if (mode === "none") { + el.style.overflowAnchor = "none" + return + } + + if (mode === "auto") { + el.style.overflowAnchor = "auto" + return + } + + el.style.overflowAnchor = store.userScrolled ? "auto" : "none" + } + + createResizeObserver( + () => store.contentRef, + () => { + const el = store.scrollRef + if (el && !canScroll(el)) { + if (store.userScrolled) setStore("userScrolled", false) + return + } + if (!active()) return + if (store.userScrolled) return + // ResizeObserver fires after layout, before paint. + // Keep the bottom locked in the same frame to avoid visible + // "jump up then catch up" artifacts while streaming content. + scrollToBottom(false) + }, + ) + + createEffect( + on(options.working, (working: boolean) => { + settling = false + if (settleTimer) clearTimeout(settleTimer) + settleTimer = undefined + + if (working) { + if (!store.userScrolled) scrollToBottom(true) + return + } + + settling = true + settleTimer = setTimeout(() => { + settling = false + }, 300) + }), + ) + + createEffect(() => { + // Track `userScrolled` even before `scrollRef` is attached, so we can + // update overflow anchoring once the element exists. + store.userScrolled + const el = store.scrollRef + if (!el) return + updateOverflowAnchor(el) + }) + + createEventListener(() => store.scrollRef, "wheel", handleWheel, { passive: true }) + + onCleanup(() => { + if (settleTimer) clearTimeout(settleTimer) + if (autoTimer) clearTimeout(autoTimer) + }) + + return { + scrollRef: (el: HTMLElement | undefined) => setStore("scrollRef", el), + contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el), + handleScroll, + handleInteraction, + pause: stop, + resume: () => { + if (store.userScrolled) setStore("userScrolled", false) + scrollToBottom(true) + }, + scrollToBottom: () => scrollToBottom(false), + forceScrollToBottom: () => scrollToBottom(true), + userScrolled: () => store.userScrolled, + } +} diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c90a2e493d97b08d98971fce543032d9d87b96c --- /dev/null +++ b/packages/ui/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./use-filtered-list" +export * from "./create-auto-scroll" diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..552febb68a2ecbd5eae69743aab5e68543fb5eae --- /dev/null +++ b/packages/ui/src/hooks/use-filtered-list.tsx @@ -0,0 +1,134 @@ +import fuzzysort from "fuzzysort" +import { entries, flatMap, groupBy, map, pipe } from "remeda" +import { createEffect, createMemo, createResource, on } from "solid-js" +import { createStore } from "solid-js/store" +import { createList } from "solid-list" + +export interface FilteredListProps { + items: T[] | ((filter: string) => T[] | Promise) + key: (item: T) => string + filterKeys?: string[] + current?: T + groupBy?: (x: T) => string + sortBy?: (a: T, b: T) => number + sortGroupsBy?: (a: { category: string; items: T[] }, b: { category: string; items: T[] }) => number + skipFilter?: (item: T) => boolean + onSelect?: (value: T | undefined, index: number) => void + noInitialSelection?: boolean +} + +export function useFilteredList(props: FilteredListProps) { + const [store, setStore] = createStore<{ filter: string }>({ filter: "" }) + + type Group = { category: string; items: [T, ...T[]] } + const empty: Group[] = [] + + const [grouped, { refetch }] = createResource( + () => ({ + filter: store.filter, + items: typeof props.items === "function" ? props.items(store.filter) : props.items, + }), + async ({ filter, items }) => { + const query = filter ?? "" + const needle = query.toLowerCase() + const all = (await Promise.resolve(items)) || [] + const result = pipe( + all, + (x) => { + if (!needle) return x + const skipFilter = props.skipFilter + const filterable = skipFilter ? x.filter((item) => !skipFilter(item)) : x + const skipped = skipFilter ? x.filter(skipFilter) : [] + const filtered = + !props.filterKeys && Array.isArray(filterable) && filterable.every((e) => typeof e === "string") + ? (fuzzysort.go(needle, filterable).map((x) => x.target) as T[]) + : fuzzysort.go(needle, filterable, { keys: props.filterKeys! }).map((x) => x.obj) + return skipped.length ? [...filtered, ...skipped] : filtered + }, + groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), + entries(), + map(([k, v]) => ({ category: k, items: props.sortBy ? v.sort(props.sortBy) : v })), + (groups) => (props.sortGroupsBy ? groups.sort(props.sortGroupsBy) : groups), + ) + return result + }, + { initialValue: empty }, + ) + + const flat = createMemo(() => { + return pipe( + grouped.latest || [], + flatMap((x) => x.items), + ) + }) + + function initialActive() { + if (props.noInitialSelection) return "" + if (props.current) return props.key(props.current) + + const items = flat() + if (items.length === 0) return "" + return props.key(items[0]) + } + + const list = createList({ + items: () => flat().map(props.key), + initialActive: initialActive(), + loop: true, + }) + + const reset = () => { + if (props.noInitialSelection) { + list.setActive("") + return + } + const all = flat() + if (all.length === 0) return + list.setActive(props.key(all[0])) + } + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && !event.isComposing) { + event.preventDefault() + const selectedIndex = flat().findIndex((x) => props.key(x) === list.active()) + const selected = flat()[selectedIndex] + if (selected) props.onSelect?.(selected, selectedIndex) + } else if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + if (event.key === "n" || event.key === "p") { + event.preventDefault() + const navEvent = new KeyboardEvent("keydown", { + key: event.key === "n" ? "ArrowDown" : "ArrowUp", + bubbles: true, + }) + list.onKeyDown(navEvent) + } + } else { + // Skip list navigation for text editing shortcuts (e.g., Option+Arrow, Option+Backspace on macOS) + if (event.altKey || event.metaKey) return + list.onKeyDown(event) + } + } + + createEffect( + on(grouped, () => { + reset() + }), + ) + + const onInput = (value: string) => { + setStore("filter", value) + } + + return { + grouped, + filter: () => store.filter, + flat, + reset, + refetch, + clear: () => setStore("filter", ""), + onKeyDown, + onInput, + active: list.active, + setActive: list.setActive, + } +} diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts new file mode 100644 index 0000000000000000000000000000000000000000..b23c24b5bff7f146c057e42690df29b8dcc44ae9 --- /dev/null +++ b/packages/ui/src/i18n/ar.ts @@ -0,0 +1,233 @@ +export const dict = { + "ui.sessionReview.title": "تغييرات الجلسة", + "ui.sessionReview.title.git": "تغييرات Git", + "ui.sessionReview.title.branch": "تغييرات الفرع", + "ui.sessionReview.title.lastTurn": "تغييرات آخر دور", + "ui.sessionReview.diffStyle.unified": "موحد", + "ui.sessionReview.diffStyle.split": "منقسم", + "ui.sessionReview.openFile": "فتح ملف", + "ui.sessionReview.selection.line": "سطر {{line}}", + "ui.sessionReview.selection.lines": "الأسطر {{start}}-{{end}}", + "ui.sessionReview.expandAll": "توسيع الكل", + "ui.sessionReview.collapseAll": "طي الكل", + "ui.sessionReview.change.added": "مضاف", + "ui.sessionReview.change.removed": "محذوف", + "ui.sessionReview.change.modified": "معدل", + "ui.sessionReview.image.loading": "جارٍ التحميل...", + "ui.sessionReview.image.placeholder": "صورة", + "ui.sessionReview.largeDiff.title": "الفروقات أكبر من أن تُعرض", + "ui.sessionReview.largeDiff.meta": "الحد: {{limit}} سطرًا معدّلًا. حاليًا: {{current}} سطرًا معدّلًا.", + "ui.sessionReview.largeDiff.renderAnyway": "اعرض على أي حال", + "ui.sessionReviewV2.expandMode": "توسيع الفروقات أو طيها", + "ui.sessionReviewV2.filterFiles": "تصفية الملفات", + "ui.sessionReviewV2.toggleSidebar": "تبديل شجرة الملفات", + "ui.sessionReviewV2.showAllLines": "إظهار جميع الأسطر", + "ui.sessionReviewV2.hideNonDiffLines": "إخفاء الأسطر خارج الفروقات", + "ui.sessionReviewV2.unifiedDiff": "فروقات موحدة", + "ui.sessionReviewV2.splitDiff": "فروقات منقسمة", + "ui.sessionReviewV2.previousFile": "الملف السابق", + "ui.sessionReviewV2.nextFile": "الملف التالي", + "ui.sessionReviewV2.diffView": "عرض الفروقات", + "ui.sessionReviewV2.empty.noGit.title": "لا توجد تغييرات متتبعة", + "ui.sessionReviewV2.empty.noGit.description": "تتبع التغييرات في هذا المشروع وراجعها وتراجع عنها", + "ui.sessionReviewV2.empty.noGit.action": "إنشاء مستودع Git", + "ui.sessionReviewV2.empty.noGit.actionLoading": "جارٍ إنشاء مستودع Git...", + "ui.sessionReviewV2.empty.changes.title": "لا توجد تغييرات في الملفات بعد", + "ui.sessionReviewV2.empty.changes.description": "ستظهر تغييرات المشروع هنا", + "ui.fileMedia.kind.image": "صورة", + "ui.fileMedia.kind.audio": "صوت", + "ui.fileMedia.state.removed": "تمت إزالة {{kind}}", + "ui.fileMedia.state.loading": "جارٍ تحميل {{kind}}...", + "ui.fileMedia.state.error": "تعذر تحميل {{kind}}.", + "ui.fileMedia.state.unavailable": "معاينة {{kind}} غير متاحة.", + "ui.fileMedia.binary.title": "ملف ثنائي", + "ui.fileMedia.binary.description.path": "{{path}} عبارة عن ملف ثنائي ولا يمكن عرضه.", + "ui.fileMedia.binary.description.default": "هذا ملف ثنائي ولا يمكن عرضه.", + + "ui.lineComment.label.prefix": "تعليق على ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "جارٍ التعليق على ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "أضف تعليقًا", + "ui.lineComment.contextPlaceholder": "أضف سياقًا لهذا التغيير", + "ui.lineComment.submit": "تعليق", + "ui.lineComment.cancel": "إلغاء", + + "ui.sessionTurn.steps.show": "إظهار الخطوات", + "ui.sessionTurn.steps.hide": "إخفاء الخطوات", + "ui.sessionTurn.summary.response": "استجابة", + "ui.sessionTurn.diff.showMore": "إظهار المزيد من التغييرات ({{count}})", + "ui.sessionTurn.diffs.changed.one": "ملف معدل: {{count}}", + "ui.sessionTurn.diffs.changed.zero": "الملفات المعدلة: {{count}}", + "ui.sessionTurn.diffs.changed.two": "عدد الملفات المعدلة: {{count}}", + "ui.sessionTurn.diffs.changed.few": "الملفات المعدلة: {{count}}", + "ui.sessionTurn.diffs.changed.many": "الملفات المعدلة: {{count}}", + "ui.sessionTurn.diffs.changed.other": "الملفات المعدلة: {{count}}", + "ui.sessionTurn.diffs.showAll": "إظهار الكل", + "ui.sessionTurn.diffs.showLess": "إظهار عدد أقل", + "ui.sessionTurn.diffs.more": "ملفات إضافية: {{count}}", + + "ui.sessionTurn.retry.retrying": "إعادة المحاولة", + "ui.sessionTurn.retry.inSeconds": "بعد {{seconds}} ث", + "ui.sessionTurn.retry.attempt": "المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.geminiHot": "Gemini مزدحم جدًا حاليًا", + "ui.sessionTurn.error.freeUsageExceeded": "تم تجاوز حد الاستخدام المجاني", + "ui.sessionTurn.error.addCredits": "إضافة رصيد", + + "dialog.usageExceeded.freeTier.title": "تم الوصول إلى الحد المجاني", + "dialog.usageExceeded.freeTier.description": + "اشترك في OpenCode Go مقابل $10/شهر للحصول على وصول موثوق إلى أفضل النماذج مفتوحة المصدر.", + "dialog.usageExceeded.freeTier.actionLabel": "اشترك", + "dialog.usageExceeded.accountRateLimit.title": "تم الوصول إلى حد Go", + "dialog.usageExceeded.accountRateLimit.description": + "تم الوصول إلى حد الاستخدام. لمتابعة استخدام هذا النموذج الآن، قم بتفعيل الاستخدام من رصيدك المتاح", + "dialog.usageExceeded.accountRateLimit.actionLabel": "فتح الإعدادات", + + "ui.sessionTurn.status.delegating": "جارٍ تفويض العمل", + "ui.sessionTurn.status.planning": "تخطيط الخطوات التالية", + "ui.sessionTurn.status.gatheringContext": "استكشاف", + "ui.sessionTurn.status.gatheredContext": "تم الاستكشاف", + "ui.sessionTurn.status.searchingCodebase": "البحث في قاعدة التعليمات البرمجية", + "ui.sessionTurn.status.searchingWeb": "البحث في الويب", + "ui.sessionTurn.status.makingEdits": "إجراء تعديلات", + "ui.sessionTurn.status.runningCommands": "تشغيل الأوامر", + "ui.sessionTurn.status.thinking": "تفكير", + "ui.sessionTurn.status.thinkingWithTopic": "تفكير - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "جمع الأفكار", + "ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية", + + "ui.messagePart.questions.dismissed": "تم إهمال الأسئلة", + "ui.messagePart.compaction": "تم اختصار الجلسة", + "ui.messagePart.context.read.one": "{{count}} قراءة", + "ui.messagePart.context.read.zero": "{{count}} قراءة", + "ui.messagePart.context.read.two": "عدد القراءات: {{count}}", + "ui.messagePart.context.read.few": "{{count}} قراءات", + "ui.messagePart.context.read.many": "{{count}} قراءةً", + "ui.messagePart.context.read.other": "{{count}} قراءات", + "ui.messagePart.context.search.one": "{{count}} بحث", + "ui.messagePart.context.search.zero": "{{count}} عملية بحث", + "ui.messagePart.context.search.two": "عدد عمليات البحث: {{count}}", + "ui.messagePart.context.search.few": "{{count}} عمليات بحث", + "ui.messagePart.context.search.many": "{{count}} عملية بحث", + "ui.messagePart.context.search.other": "{{count}} عمليات بحث", + "ui.messagePart.context.list.one": "{{count}} عملية سرد", + "ui.messagePart.context.list.zero": "{{count}} عملية سرد", + "ui.messagePart.context.list.two": "عدد عمليات السرد: {{count}}", + "ui.messagePart.context.list.few": "{{count}} عمليات سرد", + "ui.messagePart.context.list.many": "{{count}} عملية سرد", + "ui.messagePart.context.list.other": "{{count}} عمليات سرد", + "ui.messagePart.diagnostic.error": "خطأ", + "ui.messagePart.title.edit": "تحرير", + "ui.messagePart.title.write": "كتابة", + "ui.messagePart.option.typeOwnAnswer": "اكتب إجابتك الخاصة", + "ui.messagePart.review.title": "مراجعة إجاباتك", + + "ui.list.loading": "جارٍ التحميل", + "ui.list.empty": "لا توجد نتائج", + "ui.list.clearFilter": "مسح عامل التصفية", + "ui.list.emptyWithFilter.prefix": "لا توجد نتائج لـ", + "ui.list.emptyWithFilter.suffix": "", + + "ui.messageNav.newMessage": "رسالة جديدة", + + "ui.promptInput.noMatchingItems": "لا توجد عناصر مطابقة", + "ui.promptInput.commands": "الأوامر", + "ui.promptInput.dropFiles": "أفلت الملفات لإرفاقها", + "ui.promptInput.removeAttachment": "إزالة المرفق", + "ui.promptInput.label": "الموجّه", + "ui.promptInput.placeholder.shell": "أدخل أمر shell...", + "ui.promptInput.placeholder.normal": "اسأل عن أي شيء، {{slash}} للأوامر، {{at}} للسياق...", + "ui.promptInput.add": "إضافة صور وملفات", + "ui.promptInput.attachments": "الصور والملفات", + "ui.promptInput.context": "السياق", + "ui.promptInput.shell": "أمر shell", + "ui.promptInput.chooseAgent": "اختيار وكيل", + "ui.promptInput.chooseModel": "اختيار نموذج", + "ui.promptInput.chooseVariant": "اختيار متغير النموذج", + "ui.promptInput.send": "إرسال", + "ui.promptInput.stop": "إيقاف", + + "ui.tabs.close": "إغلاق علامة التبويب", + + "ui.textField.copyToClipboard": "نسخ إلى الحافظة", + "ui.textField.copyLink": "نسخ الرابط", + "ui.textField.copied": "تم النسخ", + + "ui.imagePreview.alt": "معاينة الصورة", + "ui.scrollView.ariaLabel": "محتوى قابل للتمرير", + + "ui.tool.read": "قراءة", + "ui.tool.loaded": "تم التحميل", + "ui.tool.list": "سرد", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.webfetch": "جلب الويب", + "ui.tool.websearch": "بحث الويب", + "ui.tool.websearch.provider": "{{provider}} بحث الويب", + "ui.tool.shell": "Shell", + "ui.tool.patch": "تطبيق رقعة", + "ui.tool.todos": "المهام", + "ui.tool.todos.read": "قراءة المهام", + "ui.tool.questions": "أسئلة", + "ui.tool.questions.numbered": "أسئلة {{number}}", + "ui.tool.agent": "وكيل {{type}}", + "ui.tool.agent.default": "وكيل", + + "ui.common.file.one": "ملف", + "ui.common.file.other": "ملفات", + "ui.common.question.one": "سؤال", + "ui.common.question.other": "أسئلة", + + "ui.common.add": "إضافة", + "ui.common.clear": "مسح", + "ui.common.file": "ملف", + "ui.common.back": "رجوع", + "ui.common.cancel": "إلغاء", + "ui.common.confirm": "تأكيد", + "ui.common.dismiss": "إهمال", + "ui.common.close": "إغلاق", + "ui.common.next": "التالي", + "ui.common.submit": "إرسال", + "ui.common.showMore": "عرض المزيد", + + "ui.permission.deny": "رفض", + "ui.permission.allowAlways": "السماح دائمًا", + "ui.permission.allowOnce": "السماح مرة واحدة", + + "ui.message.expand": "توسيع الرسالة", + "ui.message.collapse": "طي الرسالة", + "ui.message.copy": "نسخ", + "ui.message.copyMessage": "نسخ الرسالة", + "ui.message.forkMessage": "تشعب إلى جلسة جديدة", + "ui.message.revertMessage": "التراجع عن الرسالة", + "ui.message.copyResponse": "نسخ الرد", + "ui.message.copied": "تم النسخ!", + "ui.message.interrupted": "تمت المقاطعة", + "ui.message.queued": "في قائمة الانتظار", + "ui.message.attachment.alt": "مرفق", + + "ui.patch.action.deleted": "محذوف", + "ui.patch.action.created": "تم الإنشاء", + "ui.patch.action.moved": "منقول", + "ui.patch.action.patched": "تم تطبيق رقعة", + + "ui.question.subtitle.answered": "تمت الإجابة عن {{count}}", + "ui.question.answer.none": "(لا توجد إجابة)", + "ui.question.review.notAnswered": "(لم تتم الإجابة)", + "ui.question.multiHint": "حدد كل ما ينطبق", + "ui.question.singleHint": "حدد إجابة واحدة", + "ui.question.custom.placeholder": "اكتب إجابتك...", + + "ui.fileSearch.placeholder": "بحث", + "ui.fileSearch.previousMatch": "المطابقة السابقة", + "ui.fileSearch.nextMatch": "المطابقة التالية", + "ui.fileSearch.close": "إغلاق البحث", + "ui.tool.task": "مهمة", + "ui.tool.skill": "مهارة", + "ui.basicTool.called": "تم استدعاء `{{tool}}`", + "ui.toolErrorCard.failed": "فشل", + "ui.toolErrorCard.copyError": "نسخ الخطأ", + "ui.message.duration.seconds": "{{count}}ث", + "ui.message.duration.minutesSeconds": "{{minutes}}د {{seconds}}ث", +} diff --git a/packages/ui/src/i18n/ca.ts b/packages/ui/src/i18n/ca.ts new file mode 100644 index 0000000000000000000000000000000000000000..1141fa404e171a0c86ad953e9f053f8112f05a64 --- /dev/null +++ b/packages/ui/src/i18n/ca.ts @@ -0,0 +1,201 @@ +export const dict: Record = { + "ui.sessionReview.title": "Canvis de sessió", + "ui.sessionReview.title.git": "Canvis de Git", + "ui.sessionReview.title.branch": "Canvis de branca", + "ui.sessionReview.title.lastTurn": "Canvis de l'últim torn", + "ui.sessionReview.diffStyle.unified": "Unificat", + "ui.sessionReview.diffStyle.split": "Dividit", + "ui.sessionReview.expandAll": "Ampliar-ho tot", + "ui.sessionReview.collapseAll": "Col·lapsa-ho tot", + "ui.sessionReview.change.added": "Afegit", + "ui.sessionReview.change.removed": "Eliminat", + "ui.sessionReview.change.modified": "Modificat", + "ui.sessionReview.image.loading": "Carregant...", + "ui.sessionReview.image.placeholder": "Imatge", + "ui.sessionReview.largeDiff.title": "La diferència és massa gran per renderitzar-la", + "ui.sessionReview.largeDiff.meta": "Límit: {{limit}} línies canviades. Actual: {{current}} línies canviades.", + "ui.sessionReview.largeDiff.renderAnyway": "Renderitza de totes maneres", + "ui.sessionReviewV2.expandMode": "Amplia o redueix la diferència", + "ui.sessionReviewV2.filterFiles": "Filtrar fitxers", + "ui.sessionReviewV2.toggleSidebar": "Commuta l'arbre de fitxers", + "ui.sessionReviewV2.showAllLines": "Mostra totes les línies", + "ui.sessionReviewV2.hideNonDiffLines": "Amaga les línies sense canvis", + "ui.sessionReviewV2.unifiedDiff": "Diferència unificada", + "ui.sessionReviewV2.splitDiff": "Diff dividit", + "ui.sessionReviewV2.previousFile": "Fitxer anterior", + "ui.sessionReviewV2.nextFile": "Fitxer següent", + "ui.sessionReviewV2.diffView": "Vista del diff", + "ui.sessionReviewV2.empty.noGit.title": "No hi ha canvis de seguiment", + "ui.sessionReviewV2.empty.noGit.description": "Feu el seguiment, reviseu i desfeu els canvis d'aquest projecte", + "ui.sessionReviewV2.empty.noGit.action": "Crea un repositori Git", + "ui.sessionReviewV2.empty.noGit.actionLoading": "S'està creant un repositori Git...", + "ui.sessionReviewV2.empty.changes.title": "Encara no hi ha canvis de fitxer", + "ui.sessionReviewV2.empty.changes.description": "Els canvis del projecte apareixeran aquí", + "ui.sessionReview.openFile": "Obre el fitxer", + "ui.sessionReview.selection.line": "línia {{line}}", + "ui.sessionReview.selection.lines": "línies {{start}}-{{end}}", + "ui.fileMedia.kind.image": "imatge", + "ui.fileMedia.kind.audio": "àudio", + "ui.fileMedia.state.removed": "S'ha eliminat el fitxer {{kind}}.", + "ui.fileMedia.state.loading": "S'està carregant {{kind}}...", + "ui.fileMedia.state.error": "No es pot carregar {{kind}}.", + "ui.fileMedia.state.unavailable": "{{kind}} previsualització no disponible.", + "ui.fileMedia.binary.title": "Fitxer binari", + "ui.fileMedia.binary.description.path": "{{path}} és binari.", + "ui.fileMedia.binary.description.default": "Contingut binari", + "ui.lineComment.label.prefix": "Comenta ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "Comentant ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "Afegeix un comentari", + "ui.lineComment.contextPlaceholder": "Afegeix context per a aquest canvi", + "ui.lineComment.submit": "Comenta", + "ui.lineComment.cancel": "Cancel·la", + "ui.sessionTurn.steps.show": "Mostra els passos", + "ui.sessionTurn.steps.hide": "Amaga els passos", + "ui.sessionTurn.summary.response": "Resposta", + "ui.sessionTurn.diff.showMore": "Mostra més canvis ({{count}})", + "ui.sessionTurn.diffs.changed.one": "{{count}} S'ha canviat el fitxer", + "ui.sessionTurn.diffs.changed.other": "{{count}} Fitxers canviats", + "ui.sessionTurn.diffs.changed.many": "{{count}} fitxers canviats", + "ui.sessionTurn.diffs.showAll": "Mostra-ho tot", + "ui.sessionTurn.diffs.showLess": "Mostra menys", + "ui.sessionTurn.diffs.more": "+{{count}} fitxers més", + "ui.sessionTurn.retry.retrying": "tornant a provar", + "ui.sessionTurn.retry.inSeconds": "en {{seconds}}s", + "ui.sessionTurn.retry.attempt": "intent #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - intent #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "Gemini està massa saturat ara mateix", + "ui.sessionTurn.error.freeUsageExceeded": "S'ha superat l'ús gratuït", + "ui.sessionTurn.error.addCredits": "Afegeix crèdits", + "dialog.usageExceeded.freeTier.title": "S'ha arribat al límit gratuït", + "dialog.usageExceeded.freeTier.description": + "Subscriviu-vos a OpenCode Go per 10 dòlars al mes i obteniu accés fiable als millors models de codi obert.", + "dialog.usageExceeded.freeTier.actionLabel": "Subscriu-te", + "dialog.usageExceeded.accountRateLimit.title": "S'ha assolit el límit de Go", + "dialog.usageExceeded.accountRateLimit.description": + "S'ha arribat al límit d'ús. Per continuar utilitzant aquest model ara, activeu l'ús des del vostre saldo disponible", + "dialog.usageExceeded.accountRateLimit.actionLabel": "Obre la configuració", + "ui.sessionTurn.status.delegating": "Delegació de feina", + "ui.sessionTurn.status.planning": "Planificant els propers passos", + "ui.sessionTurn.status.gatheringContext": "Explorant", + "ui.sessionTurn.status.gatheredContext": "Explorat", + "ui.sessionTurn.status.searchingCodebase": "Buscant la base de codi", + "ui.sessionTurn.status.searchingWeb": "Buscant a la web", + "ui.sessionTurn.status.makingEdits": "Fent edicions", + "ui.sessionTurn.status.runningCommands": "Execució d'ordres", + "ui.sessionTurn.status.thinking": "Pensant", + "ui.sessionTurn.status.thinkingWithTopic": "Pensant - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "Recollint pensaments", + "ui.sessionTurn.status.consideringNextSteps": "Considerant els propers passos", + "ui.messagePart.diagnostic.error": "Error", + "ui.messagePart.title.edit": "Edita", + "ui.messagePart.title.write": "Escriu", + "ui.messagePart.option.typeOwnAnswer": "Escriu la teva pròpia resposta", + "ui.messagePart.review.title": "Revisa les teves respostes", + "ui.messagePart.questions.dismissed": "Preguntes desestimades", + "ui.messagePart.compaction": "Sessió compactada", + "ui.messagePart.context.read.one": "{{count}} llegir", + "ui.messagePart.context.read.other": "{{count}} llegeix", + "ui.messagePart.context.read.many": "{{count}} lectures", + "ui.messagePart.context.search.one": "{{count}} cerca", + "ui.messagePart.context.search.other": "{{count}} cerques", + "ui.messagePart.context.search.many": "{{count}} cerques", + "ui.messagePart.context.list.one": "{{count}} llista", + "ui.messagePart.context.list.other": "{{count}} llistes", + "ui.messagePart.context.list.many": "{{count}} llistes", + "ui.list.loading": "Carregant", + "ui.list.empty": "Sense resultats", + "ui.list.clearFilter": "Esborra el filtre", + "ui.list.emptyWithFilter.prefix": "No hi ha resultats per a", + "ui.list.emptyWithFilter.suffix": "", + "ui.fileSearch.placeholder": "Troba", + "ui.fileSearch.previousMatch": "Coincidència anterior", + "ui.fileSearch.nextMatch": "Coincidència següent", + "ui.fileSearch.close": "Tanca la cerca", + "ui.messageNav.newMessage": "Nou missatge", + "ui.promptInput.noMatchingItems": "No hi ha elements coincidents", + "ui.promptInput.commands": "Ordres", + "ui.promptInput.dropFiles": "Deixeu fitxers per adjuntar-los", + "ui.promptInput.removeAttachment": "Elimina el fitxer adjunt", + "ui.promptInput.label": "Indicació", + "ui.promptInput.placeholder.shell": "Introduïu una ordre de l'intèrpret d'ordres...", + "ui.promptInput.placeholder.normal": "Pregunteu qualsevol cosa, {{slash}} per ordres, {{at}} per context...", + "ui.promptInput.add": "Afegeix imatges i fitxers", + "ui.promptInput.attachments": "Imatges i fitxers", + "ui.promptInput.context": "Context", + "ui.promptInput.shell": "Ordre de l'intèrpret d'ordres", + "ui.promptInput.chooseAgent": "Trieu un agent", + "ui.promptInput.chooseModel": "Tria el model", + "ui.promptInput.chooseVariant": "Trieu la variant del model", + "ui.promptInput.send": "Enviar", + "ui.promptInput.stop": "Atureu-vos", + "ui.tabs.close": "Tanca la pestanya", + "ui.textField.copyToClipboard": "Copia al porta-retalls", + "ui.textField.copyLink": "Copia l'enllaç", + "ui.textField.copied": "Copiat", + "ui.imagePreview.alt": "Vista prèvia de la imatge", + "ui.scrollView.ariaLabel": "contingut desplaçable", + "ui.tool.read": "Llegeix", + "ui.tool.loaded": "Carregat", + "ui.tool.list": "Llista", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.task": "Tasca", + "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Cerca web", + "ui.tool.websearch.provider": "{{provider}} Cerca web", + "ui.tool.shell": "Shell", + "ui.tool.patch": "Pedaç", + "ui.tool.todos": "Coses pendents", + "ui.tool.todos.read": "Llegir tasques pendents", + "ui.tool.questions": "Preguntes", + "ui.tool.questions.numbered": "Preguntes {{number}}", + "ui.tool.agent": "{{type}} Agent", + "ui.tool.agent.default": "Agent", + "ui.tool.skill": "Habilitat", + "ui.basicTool.called": "Es diu `{{tool}}`", + "ui.toolErrorCard.failed": "Ha fallat", + "ui.toolErrorCard.copyError": "Error de còpia", + "ui.common.file.one": "fitxer", + "ui.common.file.other": "fitxers", + "ui.common.question.one": "pregunta", + "ui.common.question.other": "preguntes", + "ui.common.add": "Afegeix", + "ui.common.clear": "Neteja", + "ui.common.file": "Fitxer", + "ui.common.back": "Enrere", + "ui.common.cancel": "Cancel·la", + "ui.common.confirm": "Confirmeu", + "ui.common.dismiss": "Descartar", + "ui.common.close": "Tancar", + "ui.common.next": "A continuació", + "ui.common.submit": "Envia", + "ui.common.showMore": "Mostra'n més", + "ui.permission.deny": "Negar", + "ui.permission.allowAlways": "Permetre sempre", + "ui.permission.allowOnce": "Permet una vegada", + "ui.message.expand": "Amplia el missatge", + "ui.message.collapse": "Replega el missatge", + "ui.message.copy": "Còpia", + "ui.message.copyMessage": "Copia el missatge", + "ui.message.forkMessage": "Bifurca en una sessió nova", + "ui.message.revertMessage": "Reverteix el missatge", + "ui.message.copyResponse": "Copia la resposta", + "ui.message.copied": "Copiat", + "ui.message.duration.seconds": "{{count}}s", + "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s", + "ui.message.interrupted": "Interromput", + "ui.message.queued": "En cua", + "ui.message.attachment.alt": "adjunt", + "ui.patch.action.deleted": "S'ha suprimit", + "ui.patch.action.created": "Creat", + "ui.patch.action.moved": "Mogut", + "ui.patch.action.patched": "Aplicat el pedaç", + "ui.question.subtitle.answered": "{{count}} va respondre", + "ui.question.answer.none": "(sense resposta)", + "ui.question.review.notAnswered": "(no contestat)", + "ui.question.multiHint": "Seleccioneu totes les respostes aplicables", + "ui.question.singleHint": "Seleccioneu una resposta", + "ui.question.custom.placeholder": "Escriu la teva resposta...", +} diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3f256c7b4ffd0f431f3301307348b16d5908e21 --- /dev/null +++ b/packages/ui/src/i18n/da.ts @@ -0,0 +1,216 @@ +export const dict = { + "ui.sessionReview.title": "Sessionsændringer", + "ui.sessionReview.title.git": "Git-ændringer", + "ui.sessionReview.title.branch": "Grenændringer", + "ui.sessionReview.title.lastTurn": "Ændringer fra sidste tur", + "ui.sessionReview.diffStyle.unified": "Samlet", + "ui.sessionReview.diffStyle.split": "Opdelt", + "ui.sessionReview.openFile": "Åbn fil", + "ui.sessionReview.selection.line": "linje {{line}}", + "ui.sessionReview.selection.lines": "linjer {{start}}-{{end}}", + "ui.sessionReview.expandAll": "Udvid alle", + "ui.sessionReview.collapseAll": "Skjul alle", + + "ui.sessionReview.change.added": "Tilføjet", + "ui.sessionReview.change.removed": "Fjernet", + "ui.sessionReview.change.modified": "Ændret", + "ui.sessionReview.image.loading": "Indlæser...", + "ui.sessionReview.image.placeholder": "Billede", + "ui.sessionReview.largeDiff.title": "Diffen er for stor til at blive vist", + "ui.sessionReview.largeDiff.meta": "Grænse: {{limit}} ændrede linjer. Nuværende: {{current}} ændrede linjer.", + "ui.sessionReview.largeDiff.renderAnyway": "Vis alligevel", + "ui.sessionReviewV2.expandMode": "Udvid eller skjul diff", + "ui.sessionReviewV2.filterFiles": "Filtrer filer", + "ui.sessionReviewV2.toggleSidebar": "Skift filtræ", + "ui.sessionReviewV2.showAllLines": "Vis alle linjer", + "ui.sessionReviewV2.hideNonDiffLines": "Skjul linjer uden ændringer", + "ui.sessionReviewV2.unifiedDiff": "Samlet diff", + "ui.sessionReviewV2.splitDiff": "Opdelt diff", + "ui.sessionReviewV2.previousFile": "Forrige fil", + "ui.sessionReviewV2.nextFile": "Næste fil", + "ui.sessionReviewV2.diffView": "Diffvisning", + "ui.sessionReviewV2.empty.noGit.title": "Ingen sporede ændringer", + "ui.sessionReviewV2.empty.noGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt", + "ui.sessionReviewV2.empty.noGit.action": "Opret et Git-repository", + "ui.sessionReviewV2.empty.noGit.actionLoading": "Opretter Git-repository...", + "ui.sessionReviewV2.empty.changes.title": "Ingen filændringer endnu", + "ui.sessionReviewV2.empty.changes.description": "Projektændringer vises her", + "ui.fileMedia.kind.image": "billede", + "ui.fileMedia.kind.audio": "lyd", + "ui.fileMedia.state.removed": "{{kind}} fjernet", + "ui.fileMedia.state.loading": "Indlæser {{kind}}...", + "ui.fileMedia.state.error": "Kunne ikke indlæse {{kind}}", + "ui.fileMedia.state.unavailable": "Forhåndsvisning af {{kind}} er ikke tilgængelig", + "ui.fileMedia.binary.title": "Binær fil", + "ui.fileMedia.binary.description.path": "{{path}} kan ikke vises, fordi det er en binær fil.", + "ui.fileMedia.binary.description.default": "Denne fil kan ikke vises, fordi det er en binær fil.", + "ui.lineComment.label.prefix": "Skriv en kommentar til ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "Skriver en kommentar til ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "Tilføj kommentar", + "ui.lineComment.submit": "Kommenter", + "ui.lineComment.cancel": "Annuller", + "ui.sessionTurn.steps.show": "Vis trin", + "ui.sessionTurn.steps.hide": "Skjul trin", + "ui.sessionTurn.summary.response": "Svar", + "ui.sessionTurn.diff.showMore": "Vis flere ændringer ({{count}})", + "ui.sessionTurn.diffs.changed.one": "Ændret fil: {{count}}", + "ui.sessionTurn.diffs.changed.other": "Ændrede filer: {{count}}", + "ui.sessionTurn.diffs.showAll": "Vis alle", + "ui.sessionTurn.diffs.showLess": "Vis færre", + "ui.sessionTurn.diffs.more": "+{{count}} mere", + + "ui.sessionTurn.retry.retrying": "prøver igen", + "ui.sessionTurn.retry.inSeconds": "om {{seconds}}s", + "ui.sessionTurn.retry.attempt": "forsøg #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - forsøg #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "Gemini er meget overbelastet lige nu", + "ui.sessionTurn.error.freeUsageExceeded": "Grænsen for gratis forbrug er overskredet", + "ui.sessionTurn.error.addCredits": "Tilføj kreditter", + + "dialog.usageExceeded.freeTier.title": "Gratis grænse nået", + "dialog.usageExceeded.freeTier.description": + "Abonnér på OpenCode Go for $10/måned, og få pålidelig adgang til de bedste open source-modeller.", + "dialog.usageExceeded.freeTier.actionLabel": "Abonnér", + "dialog.usageExceeded.accountRateLimit.title": "Go-grænse nået", + "dialog.usageExceeded.accountRateLimit.description": + "Forbrugsgrænse nået. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo", + "dialog.usageExceeded.accountRateLimit.actionLabel": "Åbn indstillinger", + + "ui.sessionTurn.status.delegating": "Delegerer arbejde", + "ui.sessionTurn.status.planning": "Planlægger næste trin", + "ui.sessionTurn.status.gatheringContext": "Udforsker", + "ui.sessionTurn.status.gatheredContext": "Udforsket", + "ui.sessionTurn.status.searchingCodebase": "Søger i koden", + "ui.sessionTurn.status.searchingWeb": "Søger på nettet", + "ui.sessionTurn.status.makingEdits": "Laver ændringer", + "ui.sessionTurn.status.runningCommands": "Kører kommandoer", + "ui.sessionTurn.status.thinking": "Tænker", + "ui.sessionTurn.status.thinkingWithTopic": "Tænker - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "Samler tanker", + "ui.sessionTurn.status.consideringNextSteps": "Overvejer næste skridt", + + "ui.messagePart.questions.dismissed": "Spørgsmål afvist", + "ui.messagePart.compaction": "Session komprimeret", + "ui.messagePart.context.read.one": "{{count}} læsning", + "ui.messagePart.context.read.other": "{{count}} læsninger", + "ui.messagePart.context.search.one": "{{count}} søgning", + "ui.messagePart.context.search.other": "{{count}} søgninger", + "ui.messagePart.context.list.one": "{{count}} liste", + "ui.messagePart.context.list.other": "{{count}} lister", + "ui.messagePart.diagnostic.error": "Fejl", + "ui.messagePart.title.edit": "Rediger", + "ui.messagePart.title.write": "Skriv", + "ui.messagePart.option.typeOwnAnswer": "Skriv dit eget svar", + "ui.messagePart.review.title": "Gennemgå dine svar", + + "ui.list.loading": "Indlæser", + "ui.list.empty": "Ingen resultater", + "ui.list.clearFilter": "Ryd filter", + "ui.list.emptyWithFilter.prefix": "Ingen resultater for", + "ui.list.emptyWithFilter.suffix": "", + + "ui.messageNav.newMessage": "Ny besked", + + "ui.textField.copyToClipboard": "Kopier til udklipsholder", + "ui.textField.copyLink": "Kopier link", + "ui.textField.copied": "Kopieret", + + "ui.imagePreview.alt": "Billedforhåndsvisning", + "ui.scrollView.ariaLabel": "rulbart indhold", + + "ui.tool.read": "Læs", + "ui.tool.loaded": "Indlæst", + "ui.tool.list": "Liste", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.webfetch": "Webhentning", + "ui.tool.websearch": "Websøgning", + "ui.tool.websearch.provider": "{{provider}} Websøgning", + "ui.tool.shell": "Shell", + "ui.tool.patch": "Patch", + "ui.tool.todos": "Opgaver", + "ui.tool.todos.read": "Læs opgaver", + "ui.tool.questions": "Spørgsmål", + "ui.tool.questions.numbered": "Spørgsmål {{number}}", + "ui.tool.agent": "{{type}}-agent", + "ui.tool.agent.default": "Agent", + + "ui.common.file.one": "fil", + "ui.common.file.other": "filer", + "ui.common.question.one": "spørgsmål", + "ui.common.question.other": "spørgsmål", + + "ui.common.add": "Tilføj", + "ui.common.clear": "Ryd", + "ui.common.file": "Fil", + "ui.common.back": "Tilbage", + "ui.common.cancel": "Annuller", + "ui.common.confirm": "Bekræft", + "ui.common.dismiss": "Afvis", + "ui.common.close": "Luk", + "ui.common.next": "Næste", + "ui.common.submit": "Indsend", + "ui.common.showMore": "Vis mere", + + "ui.permission.deny": "Afvis", + "ui.permission.allowAlways": "Tillad altid", + "ui.permission.allowOnce": "Tillad én gang", + + "ui.message.expand": "Udvid besked", + "ui.message.collapse": "Skjul besked", + "ui.message.copy": "Kopier", + "ui.message.copyMessage": "Kopier besked", + "ui.message.forkMessage": "Forgren til ny session", + "ui.message.revertMessage": "Nulstil til dette punkt", + "ui.message.copyResponse": "Kopier svar", + "ui.message.copied": "Kopieret!", + "ui.message.interrupted": "Afbrudt", + "ui.message.queued": "I kø", + "ui.message.attachment.alt": "vedhæftning", + + "ui.patch.action.deleted": "Slettet", + "ui.patch.action.created": "Oprettet", + "ui.patch.action.moved": "Flyttet", + "ui.patch.action.patched": "Patchet", + + "ui.question.subtitle.answered": "{{count}} besvaret", + "ui.question.answer.none": "(intet svar)", + "ui.question.review.notAnswered": "(ikke besvaret)", + "ui.question.multiHint": "Vælg alle der gælder", + "ui.question.singleHint": "Vælg ét svar", + "ui.question.custom.placeholder": "Skriv dit svar...", + + "ui.fileSearch.placeholder": "Find", + "ui.fileSearch.previousMatch": "Forrige match", + "ui.fileSearch.nextMatch": "Næste match", + "ui.fileSearch.close": "Luk søgning", + "ui.tool.task": "Opgave", + "ui.tool.skill": "Færdighed", + "ui.basicTool.called": "Kaldte værktøjet `{{tool}}`", + "ui.toolErrorCard.failed": "Mislykkedes", + "ui.toolErrorCard.copyError": "Kopier fejl", + "ui.message.duration.seconds": "{{count}}s", + "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s", + "ui.lineComment.contextPlaceholder": "Tilføj kontekst til denne ændring", + + "ui.promptInput.noMatchingItems": "Ingen matchende elementer", + "ui.promptInput.commands": "Kommandoer", + "ui.promptInput.dropFiles": "Slip filer for at vedhæfte dem", + "ui.promptInput.removeAttachment": "Fjern vedhæftet fil", + "ui.promptInput.label": "Prompt", + "ui.promptInput.placeholder.shell": "Indtast shell-kommando...", + "ui.promptInput.placeholder.normal": "Spørg om hvad som helst, {{slash}} for kommandoer, {{at}} for kontekst...", + "ui.promptInput.add": "Tilføj billeder og filer", + "ui.promptInput.attachments": "Billeder og filer", + "ui.promptInput.context": "Kontekst", + "ui.promptInput.shell": "Shell-kommando", + "ui.promptInput.chooseAgent": "Vælg agent", + "ui.promptInput.chooseModel": "Vælg model", + "ui.promptInput.chooseVariant": "Vælg modelvariant", + "ui.promptInput.send": "Send", + "ui.promptInput.stop": "Stop", + + "ui.tabs.close": "Luk fane", +} diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca86cba28ce7024eac599d019b46555251fa599a --- /dev/null +++ b/packages/ui/src/i18n/de.ts @@ -0,0 +1,221 @@ +import { dict as en } from "./en" + +type Keys = keyof typeof en + +export const dict = { + "ui.sessionReview.title": "Sitzungsänderungen", + "ui.sessionReview.title.git": "Git-Änderungen", + "ui.sessionReview.title.branch": "Branch-Änderungen", + "ui.sessionReview.title.lastTurn": "Änderungen der letzten Runde", + "ui.sessionReview.diffStyle.unified": "Vereinheitlicht", + "ui.sessionReview.diffStyle.split": "Geteilt", + "ui.sessionReview.openFile": "Datei öffnen", + "ui.sessionReview.selection.line": "Zeile {{line}}", + "ui.sessionReview.selection.lines": "Zeilen {{start}}-{{end}}", + "ui.sessionReview.expandAll": "Alle ausklappen", + "ui.sessionReview.collapseAll": "Alle einklappen", + + "ui.sessionReview.change.added": "Hinzugefügt", + "ui.sessionReview.change.removed": "Entfernt", + "ui.sessionReview.change.modified": "Geändert", + "ui.sessionReview.image.loading": "Wird geladen…", + "ui.sessionReview.image.placeholder": "Bild", + "ui.sessionReview.largeDiff.title": "Diff zu groß zum Rendern", + "ui.sessionReview.largeDiff.meta": "Limit: {{limit}} geänderte Zeilen. Aktuell: {{current}} geänderte Zeilen.", + "ui.sessionReview.largeDiff.renderAnyway": "Trotzdem rendern", + "ui.sessionReviewV2.expandMode": "Diff ein- oder ausklappen", + "ui.sessionReviewV2.filterFiles": "Dateien filtern", + "ui.sessionReviewV2.toggleSidebar": "Dateibaum umschalten", + "ui.sessionReviewV2.showAllLines": "Alle Zeilen anzeigen", + "ui.sessionReviewV2.hideNonDiffLines": "Zeilen außerhalb des Diffs ausblenden", + "ui.sessionReviewV2.unifiedDiff": "Vereinheitlichter Diff", + "ui.sessionReviewV2.splitDiff": "Geteilter Diff", + "ui.sessionReviewV2.previousFile": "Vorherige Datei", + "ui.sessionReviewV2.nextFile": "Nächste Datei", + "ui.sessionReviewV2.diffView": "Diff-Ansicht", + "ui.sessionReviewV2.empty.noGit.title": "Keine verfolgten Änderungen", + "ui.sessionReviewV2.empty.noGit.description": + "Änderungen in diesem Projekt verfolgen, überprüfen und rückgängig machen", + "ui.sessionReviewV2.empty.noGit.action": "Git-Repository erstellen", + "ui.sessionReviewV2.empty.noGit.actionLoading": "Git-Repository wird erstellt…", + "ui.sessionReviewV2.empty.changes.title": "Noch keine Dateiänderungen", + "ui.sessionReviewV2.empty.changes.description": "Projektänderungen werden hier angezeigt", + "ui.fileMedia.kind.image": "Bild", + "ui.fileMedia.kind.audio": "Audio", + "ui.fileMedia.state.removed": "{{kind}} entfernt.", + "ui.fileMedia.state.loading": "{{kind}} wird geladen…", + "ui.fileMedia.state.error": "{{kind}} konnte nicht geladen werden.", + "ui.fileMedia.state.unavailable": "Vorschau für {{kind}} nicht verfügbar.", + "ui.fileMedia.binary.title": "Binärdatei", + "ui.fileMedia.binary.description.path": + "{{path}} kann nicht angezeigt werden, da es sich um eine Binärdatei handelt.", + "ui.fileMedia.binary.description.default": + "Diese Datei kann nicht angezeigt werden, da es sich um eine Binärdatei handelt.", + "ui.lineComment.label.prefix": "Kommentar zu ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "Kommentiere ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "Kommentar hinzufügen", + "ui.lineComment.submit": "Kommentieren", + "ui.lineComment.cancel": "Abbrechen", + "ui.sessionTurn.steps.show": "Schritte anzeigen", + "ui.sessionTurn.steps.hide": "Schritte ausblenden", + "ui.sessionTurn.summary.response": "Antwort", + "ui.sessionTurn.diff.showMore": "Weitere Änderungen anzeigen ({{count}})", + "ui.sessionTurn.diffs.changed.one": "Geänderte Datei: {{count}}", + "ui.sessionTurn.diffs.changed.other": "Geänderte Dateien: {{count}}", + "ui.sessionTurn.diffs.showAll": "Alle anzeigen", + "ui.sessionTurn.diffs.showLess": "Weniger anzeigen", + "ui.sessionTurn.diffs.more": "+{{count}} weitere", + + "ui.sessionTurn.retry.retrying": "Erneuter Versuch", + "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", + "ui.sessionTurn.retry.attempt": "Versuch #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} – Versuch #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "Gemini ist gerade stark überlastet", + "ui.sessionTurn.error.freeUsageExceeded": "Kostenloses Nutzungslimit überschritten", + "ui.sessionTurn.error.addCredits": "Guthaben aufladen", + + "dialog.usageExceeded.freeTier.title": "Kostenloses Limit erreicht", + "dialog.usageExceeded.freeTier.description": + "OpenCode Go für 10 $ pro Monat abonnieren und zuverlässigen Zugriff auf die besten Open-Source-Modelle erhalten.", + "dialog.usageExceeded.freeTier.actionLabel": "Abonnieren", + "dialog.usageExceeded.accountRateLimit.title": "Go-Limit erreicht", + "dialog.usageExceeded.accountRateLimit.description": + "Nutzungslimit erreicht. Um dieses Modell jetzt weiter zu nutzen, die Nutzung über das verfügbare Guthaben aktivieren", + "dialog.usageExceeded.accountRateLimit.actionLabel": "Einstellungen öffnen", + + "ui.sessionTurn.status.delegating": "Arbeit wird delegiert", + "ui.sessionTurn.status.planning": "Nächste Schritte werden geplant", + "ui.sessionTurn.status.gatheringContext": "Wird erkundet", + "ui.sessionTurn.status.gatheredContext": "Erkundung abgeschlossen", + "ui.sessionTurn.status.searchingCodebase": "Codebasis wird durchsucht", + "ui.sessionTurn.status.searchingWeb": "Web wird durchsucht", + "ui.sessionTurn.status.makingEdits": "Änderungen werden vorgenommen", + "ui.sessionTurn.status.runningCommands": "Befehle werden ausgeführt", + "ui.sessionTurn.status.thinking": "Denkt nach", + "ui.sessionTurn.status.thinkingWithTopic": "Denkt nach: {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "Gedanken werden gesammelt", + "ui.sessionTurn.status.consideringNextSteps": "Nächste Schritte werden erwogen", + + "ui.messagePart.questions.dismissed": "Fragen verworfen", + "ui.messagePart.compaction": "Sitzung komprimiert", + "ui.messagePart.context.read.one": "{{count}} Lesevorgang", + "ui.messagePart.context.read.other": "{{count}} Lesevorgänge", + "ui.messagePart.context.search.one": "{{count}} Suche", + "ui.messagePart.context.search.other": "{{count}} Suchen", + "ui.messagePart.context.list.one": "{{count}} Liste", + "ui.messagePart.context.list.other": "{{count}} Listen", + "ui.messagePart.diagnostic.error": "Fehler", + "ui.messagePart.title.edit": "Bearbeiten", + "ui.messagePart.title.write": "Schreiben", + "ui.messagePart.option.typeOwnAnswer": "Eigene Antwort eingeben", + "ui.messagePart.review.title": "Antworten überprüfen", + + "ui.list.loading": "Laden", + "ui.list.empty": "Keine Ergebnisse", + "ui.list.clearFilter": "Filter löschen", + "ui.list.emptyWithFilter.prefix": "Keine Ergebnisse für", + "ui.list.emptyWithFilter.suffix": "", + + "ui.messageNav.newMessage": "Neue Nachricht", + + "ui.textField.copyToClipboard": "In die Zwischenablage kopieren", + "ui.textField.copyLink": "Link kopieren", + "ui.textField.copied": "Kopiert", + + "ui.imagePreview.alt": "Bildvorschau", + "ui.scrollView.ariaLabel": "Scrollbarer Inhalt", + + "ui.tool.read": "Lesen", + "ui.tool.loaded": "Geladen", + "ui.tool.list": "Auflisten", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.webfetch": "Web-Abruf", + "ui.tool.websearch": "Web-Suche", + "ui.tool.websearch.provider": "{{provider}} Web-Suche", + "ui.tool.shell": "Shell", + "ui.tool.patch": "Patch", + "ui.tool.todos": "Aufgaben", + "ui.tool.todos.read": "Aufgaben lesen", + "ui.tool.questions": "Fragen", + "ui.tool.questions.numbered": "Fragen {{number}}", + "ui.tool.agent": "{{type}}-Agent", + "ui.tool.agent.default": "Agent", + + "ui.common.file.one": "Datei", + "ui.common.file.other": "Dateien", + "ui.common.question.one": "Frage", + "ui.common.question.other": "Fragen", + + "ui.common.add": "Hinzufügen", + "ui.common.clear": "Leeren", + "ui.common.file": "Datei", + "ui.common.back": "Zurück", + "ui.common.cancel": "Abbrechen", + "ui.common.confirm": "Bestätigen", + "ui.common.dismiss": "Verwerfen", + "ui.common.close": "Schließen", + "ui.common.next": "Weiter", + "ui.common.submit": "Absenden", + "ui.common.showMore": "Mehr anzeigen", + + "ui.permission.deny": "Verweigern", + "ui.permission.allowAlways": "Immer erlauben", + "ui.permission.allowOnce": "Einmal erlauben", + + "ui.message.expand": "Nachricht ausklappen", + "ui.message.collapse": "Nachricht einklappen", + "ui.message.copy": "Kopieren", + "ui.message.copyMessage": "Nachricht kopieren", + "ui.message.forkMessage": "In neue Sitzung abzweigen", + "ui.message.revertMessage": "Auf diesen Punkt zurücksetzen", + "ui.message.copyResponse": "Antwort kopieren", + "ui.message.copied": "Kopiert!", + "ui.message.interrupted": "Unterbrochen", + "ui.message.queued": "In der Warteschlange", + "ui.message.attachment.alt": "Anhang", + + "ui.patch.action.deleted": "Gelöscht", + "ui.patch.action.created": "Erstellt", + "ui.patch.action.moved": "Verschoben", + "ui.patch.action.patched": "Gepatched", + + "ui.question.subtitle.answered": "{{count}} beantwortet", + "ui.question.answer.none": "(keine Antwort)", + "ui.question.review.notAnswered": "(nicht beantwortet)", + "ui.question.multiHint": "Alle zutreffenden auswählen", + "ui.question.singleHint": "Eine Antwort auswählen", + "ui.question.custom.placeholder": "Eigene Antwort eingeben…", + + "ui.fileSearch.placeholder": "Suchen", + "ui.fileSearch.previousMatch": "Vorheriges Ergebnis", + "ui.fileSearch.nextMatch": "Nächstes Ergebnis", + "ui.fileSearch.close": "Suche schließen", + "ui.tool.task": "Aufgabe", + "ui.tool.skill": "Skill", + "ui.basicTool.called": "`{{tool}}` aufgerufen", + "ui.toolErrorCard.failed": "Fehlgeschlagen", + "ui.toolErrorCard.copyError": "Fehler kopieren", + "ui.message.duration.seconds": "{{count}}s", + "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s", + "ui.lineComment.contextPlaceholder": "Kontext zu dieser Änderung hinzufügen", + "ui.promptInput.noMatchingItems": "Keine passenden Einträge", + "ui.promptInput.commands": "Befehle", + "ui.promptInput.dropFiles": "Dateien zum Anhängen hier ablegen", + "ui.promptInput.removeAttachment": "Anhang entfernen", + "ui.promptInput.label": "Prompt", + "ui.promptInput.placeholder.shell": "Shell-Befehl eingeben…", + "ui.promptInput.placeholder.normal": "Beliebige Frage stellen, {{slash}} für Befehle, {{at}} für Kontext…", + "ui.promptInput.add": "Bilder und Dateien hinzufügen", + "ui.promptInput.attachments": "Bilder und Dateien", + "ui.promptInput.context": "Kontext", + "ui.promptInput.shell": "Shell-Befehl", + "ui.promptInput.chooseAgent": "Agenten auswählen", + "ui.promptInput.chooseModel": "Modell auswählen", + "ui.promptInput.chooseVariant": "Modellvariante auswählen", + "ui.promptInput.send": "Senden", + "ui.promptInput.stop": "Stoppen", + "ui.tabs.close": "Tab schließen", +} satisfies Partial> diff --git a/packages/ui/src/i18n/fa.ts b/packages/ui/src/i18n/fa.ts new file mode 100644 index 0000000000000000000000000000000000000000..54b356d10e316e2f6c5487c07c77bed07fd27c10 --- /dev/null +++ b/packages/ui/src/i18n/fa.ts @@ -0,0 +1,197 @@ +export const dict: Record = { + "ui.sessionReview.title": "تغییرات جلسه", + "ui.sessionReview.title.git": "تغییرات Git", + "ui.sessionReview.title.branch": "تغییر شعبه", + "ui.sessionReview.title.lastTurn": "آخرین نوبت تغییر می کند", + "ui.sessionReview.diffStyle.unified": "متحد", + "ui.sessionReview.diffStyle.split": "تقسیم کنید", + "ui.sessionReview.expandAll": "همه را گسترش دهید", + "ui.sessionReview.collapseAll": "همه را جمع کن", + "ui.sessionReview.change.added": "اضافه شد", + "ui.sessionReview.change.removed": "حذف شد", + "ui.sessionReview.change.modified": "اصلاح شد", + "ui.sessionReview.image.loading": "در حال بارگیری...", + "ui.sessionReview.image.placeholder": "تصویر", + "ui.sessionReview.largeDiff.title": "تفاوت برای ارائه خیلی بزرگ است", + "ui.sessionReview.largeDiff.meta": "محدودیت: {{limit}} خطوط تغییر کرده است. فعلی: {{current}} خطوط تغییر کرده است.", + "ui.sessionReview.largeDiff.renderAnyway": "به هر حال رندر کنید", + "ui.sessionReviewV2.expandMode": "بزرگ کردن یا کوچک کردن تفاوت", + "ui.sessionReviewV2.filterFiles": "فایل ها را فیلتر کنید", + "ui.sessionReviewV2.toggleSidebar": "درخت فایل را تغییر دهید", + "ui.sessionReviewV2.showAllLines": "نمایش تمام خطوط", + "ui.sessionReviewV2.hideNonDiffLines": "پنهان کردن خطوط بدون تفاوت", + "ui.sessionReviewV2.unifiedDiff": "تفاوت یکپارچه", + "ui.sessionReviewV2.splitDiff": "تقسیم تفاوت", + "ui.sessionReviewV2.previousFile": "فایل قبلی", + "ui.sessionReviewV2.nextFile": "فایل بعدی", + "ui.sessionReviewV2.diffView": "نمای تفاوت", + "ui.sessionReviewV2.empty.noGit.title": "هیچ تغییری ردیابی نشده است", + "ui.sessionReviewV2.empty.noGit.description": "ردیابی، بررسی، و لغو تغییرات در این پروژه", + "ui.sessionReviewV2.empty.noGit.action": "مخزن Git ایجاد کنید", + "ui.sessionReviewV2.empty.noGit.actionLoading": "ایجاد مخزن Git...", + "ui.sessionReviewV2.empty.changes.title": "هنوز فایلی تغییر نکرده است", + "ui.sessionReviewV2.empty.changes.description": "تغییرات پروژه در اینجا ظاهر می شود", + "ui.sessionReview.openFile": "باز کردن فایل", + "ui.sessionReview.selection.line": "خط {{line}}", + "ui.sessionReview.selection.lines": "خطوط {{start}}-{{end}}", + "ui.fileMedia.kind.image": "تصویر", + "ui.fileMedia.kind.audio": "صوتی", + "ui.fileMedia.state.removed": "فایل {{kind}} حذف شد.", + "ui.fileMedia.state.loading": "در حال بارگیری {{kind}}...", + "ui.fileMedia.state.error": "بارگیری {{kind}} ممکن نیست.", + "ui.fileMedia.state.unavailable": "پیش نمایش {{kind}} در دسترس نیست.", + "ui.fileMedia.binary.title": "فایل باینری", + "ui.fileMedia.binary.description.path": "{{path}} باینری است.", + "ui.fileMedia.binary.description.default": "محتوای باینری", + "ui.lineComment.label.prefix": "نظر دهید ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "در حال اظهار نظر ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "نظر اضافه کنید", + "ui.lineComment.contextPlaceholder": "برای این تغییر زمینه اضافه کنید", + "ui.lineComment.submit": "نظر دهید", + "ui.lineComment.cancel": "لغو کنید", + "ui.sessionTurn.steps.show": "نمایش مراحل", + "ui.sessionTurn.steps.hide": "پنهان کردن مراحل", + "ui.sessionTurn.summary.response": "پاسخ", + "ui.sessionTurn.diff.showMore": "نمایش تغییرات بیشتر ({{count}})", + "ui.sessionTurn.diffs.changed.one": "{{count}} فایل تغییر یافته است", + "ui.sessionTurn.diffs.changed.other": "{{count}} فایل های تغییر یافته است", + "ui.sessionTurn.diffs.showAll": "نمایش همه", + "ui.sessionTurn.diffs.showLess": "کمتر نشان دادن", + "ui.sessionTurn.diffs.more": "+{{count}} فایل های بیشتر", + "ui.sessionTurn.retry.retrying": "تلاش مجدد", + "ui.sessionTurn.retry.inSeconds": "در {{seconds}}s", + "ui.sessionTurn.retry.attempt": "تلاش #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - تلاش #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "جمینی در حال حاضر خیلی داغ است", + "ui.sessionTurn.error.freeUsageExceeded": "استفاده رایگان بیش از حد است", + "ui.sessionTurn.error.addCredits": "اعتبار اضافه کنید", + "dialog.usageExceeded.freeTier.title": "به حد مجاز رایگان رسیده است", + "dialog.usageExceeded.freeTier.description": + "برای دسترسی مطمئن به بهترین مدل‌های منبع باز، با قیمت 10 دلار در ماه در OpenCode Go مشترک شوید.", + "dialog.usageExceeded.freeTier.actionLabel": "مشترک شوید", + "dialog.usageExceeded.accountRateLimit.title": "به حد مجاز رفتن رسید", + "dialog.usageExceeded.accountRateLimit.description": + "به حد مجاز استفاده رسیده است. برای ادامه استفاده از این مدل اکنون، استفاده را از موجودی موجود خود فعال کنید", + "dialog.usageExceeded.accountRateLimit.actionLabel": "تنظیمات را باز کنید", + "ui.sessionTurn.status.delegating": "تفویض کار", + "ui.sessionTurn.status.planning": "برنامه ریزی مراحل بعدی", + "ui.sessionTurn.status.gatheringContext": "کاوش", + "ui.sessionTurn.status.gatheredContext": "کاوش کرد", + "ui.sessionTurn.status.searchingCodebase": "جستجو در پایگاه کد", + "ui.sessionTurn.status.searchingWeb": "جستجو در وب", + "ui.sessionTurn.status.makingEdits": "انجام ویرایش ها", + "ui.sessionTurn.status.runningCommands": "اجرای دستورات", + "ui.sessionTurn.status.thinking": "فکر کردن", + "ui.sessionTurn.status.thinkingWithTopic": "تفکر - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "جمع آوری افکار", + "ui.sessionTurn.status.consideringNextSteps": "در نظر گرفتن مراحل بعدی", + "ui.messagePart.diagnostic.error": "خطا", + "ui.messagePart.title.edit": "ویرایش کنید", + "ui.messagePart.title.write": "بنویسید", + "ui.messagePart.option.typeOwnAnswer": "پاسخ خود را تایپ کنید", + "ui.messagePart.review.title": "پاسخ های خود را مرور کنید", + "ui.messagePart.questions.dismissed": "سوالات رد شد", + "ui.messagePart.compaction": "جلسه فشرده شده است", + "ui.messagePart.context.read.one": "{{count}} خوانده شود", + "ui.messagePart.context.read.other": "{{count}} می خواند", + "ui.messagePart.context.search.one": "جستجوی {{count}}", + "ui.messagePart.context.search.other": "{{count}} جستجو می کند", + "ui.messagePart.context.list.one": "لیست {{count}}", + "ui.messagePart.context.list.other": "لیست های {{count}}", + "ui.list.loading": "در حال بارگذاری", + "ui.list.empty": "هیچ نتیجه ای وجود ندارد", + "ui.list.clearFilter": "فیلتر را پاک کنید", + "ui.list.emptyWithFilter.prefix": "هیچ نتیجه ای برای", + "ui.list.emptyWithFilter.suffix": "", + "ui.fileSearch.placeholder": "پیدا کنید", + "ui.fileSearch.previousMatch": "مسابقه قبلی", + "ui.fileSearch.nextMatch": "مسابقه بعدی", + "ui.fileSearch.close": "جستجو را ببندید", + "ui.messageNav.newMessage": "پیام جدید", + "ui.promptInput.noMatchingItems": "هیچ مورد منطبقی وجود ندارد", + "ui.promptInput.commands": "دستورات", + "ui.promptInput.dropFiles": "فایل ها را برای پیوست رها کنید", + "ui.promptInput.removeAttachment": "حذف پیوست", + "ui.promptInput.label": "پرامپت", + "ui.promptInput.placeholder.shell": "دستور پوسته Enter...", + "ui.promptInput.placeholder.normal": "هر چیزی را بپرسید، {{slash}} برای دستورات، {{at}} برای زمینه...", + "ui.promptInput.add": "تصاویر و فایل ها را اضافه کنید", + "ui.promptInput.attachments": "تصاویر و فایل ها", + "ui.promptInput.context": "زمینه", + "ui.promptInput.shell": "دستور پوسته", + "ui.promptInput.chooseAgent": "عامل را انتخاب کنید", + "ui.promptInput.chooseModel": "مدل را انتخاب کنید", + "ui.promptInput.chooseVariant": "نوع مدل را انتخاب کنید", + "ui.promptInput.send": "ارسال کنید", + "ui.promptInput.stop": "توقف کنید", + "ui.tabs.close": "بستن برگه", + "ui.textField.copyToClipboard": "در کلیپ بورد کپی کنید", + "ui.textField.copyLink": "لینک را کپی کنید", + "ui.textField.copied": "کپی شد", + "ui.imagePreview.alt": "پیش نمایش تصویر", + "ui.scrollView.ariaLabel": "محتوای قابل پیمایش", + "ui.tool.read": "بخوانید", + "ui.tool.loaded": "بارگیری شد", + "ui.tool.list": "فهرست کنید", + "ui.tool.glob": "گلوب", + "ui.tool.grep": "گرپ", + "ui.tool.task": "وظیفه", + "ui.tool.webfetch": "واکشی وب", + "ui.tool.websearch": "جستجوی وب", + "ui.tool.websearch.provider": "جستجوی وب {{provider}}", + "ui.tool.shell": "پوسته", + "ui.tool.patch": "پچ", + "ui.tool.todos": "کارها", + "ui.tool.todos.read": "کارها را بخوانید", + "ui.tool.questions": "سوالات", + "ui.tool.questions.numbered": "سوالات {{number}}", + "ui.tool.agent": "عامل {{type}}", + "ui.tool.agent.default": "عامل", + "ui.tool.skill": "مهارت", + "ui.basicTool.called": "`{{tool}}` فراخوانی شد", + "ui.toolErrorCard.failed": "شکست خورد", + "ui.toolErrorCard.copyError": "خطای کپی", + "ui.common.file.one": "فایل", + "ui.common.file.other": "فایل ها", + "ui.common.question.one": "سوال", + "ui.common.question.other": "سوالات", + "ui.common.add": "اضافه کنید", + "ui.common.clear": "پاک کردن", + "ui.common.file": "فایل", + "ui.common.back": "برگشت", + "ui.common.cancel": "لغو کنید", + "ui.common.confirm": "تایید کنید", + "ui.common.dismiss": "رد کردن", + "ui.common.close": "بستن", + "ui.common.next": "بعدی", + "ui.common.submit": "ارسال کنید", + "ui.common.showMore": "نمایش بیشتر", + "ui.permission.deny": "تکذیب کنید", + "ui.permission.allowAlways": "همیشه اجازه بده", + "ui.permission.allowOnce": "یک بار اجازه دهید", + "ui.message.expand": "گسترش پیام", + "ui.message.collapse": "کوچک کردن پیام", + "ui.message.copy": "کپی کنید", + "ui.message.copyMessage": "کپی پیام", + "ui.message.forkMessage": "فورک به جلسه جدید", + "ui.message.revertMessage": "پیام را برگردانید", + "ui.message.copyResponse": "پاسخ را کپی کنید", + "ui.message.copied": "کپی شد", + "ui.message.duration.seconds": "{{count}}s", + "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s", + "ui.message.interrupted": "قطع شد", + "ui.message.queued": "در صف", + "ui.message.attachment.alt": "پیوست", + "ui.patch.action.deleted": "حذف‌شده", + "ui.patch.action.created": "ایجاد شد", + "ui.patch.action.moved": "منتقل شد", + "ui.patch.action.patched": "وصله شده", + "ui.question.subtitle.answered": "{{count}} پاسخ داد", + "ui.question.answer.none": "(بدون جواب)", + "ui.question.review.notAnswered": "(پاسخ داده نشد)", + "ui.question.multiHint": "همه پاسخ هایی که اعمال می شوند را انتخاب کنید", + "ui.question.singleHint": "یک پاسخ را انتخاب کنید", + "ui.question.custom.placeholder": "پاسخ خود را تایپ کنید...", +} diff --git a/packages/ui/src/i18n/pa.ts b/packages/ui/src/i18n/pa.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ecb6b67b720bbd31d8859740d135429cebb17d8 --- /dev/null +++ b/packages/ui/src/i18n/pa.ts @@ -0,0 +1,198 @@ +export const dict: Record = { + "ui.sessionReview.title": "سیشن تبدیلیاں", + "ui.sessionReview.title.git": "Git تبدیلیاں", + "ui.sessionReview.title.branch": "برانچ تبدیلیاں", + "ui.sessionReview.title.lastTurn": "آخری واری دیاں تبدیلیاں", + "ui.sessionReview.diffStyle.unified": "متحد", + "ui.sessionReview.diffStyle.split": "ونڈیا ہویا", + "ui.sessionReview.expandAll": "سب نوں ودھاؤ", + "ui.sessionReview.collapseAll": "سارے سمیٹ دیو", + "ui.sessionReview.change.added": "شامل کیتا گیا", + "ui.sessionReview.change.removed": "ہٹا دتا گیا", + "ui.sessionReview.change.modified": "ترمیم شدہ", + "ui.sessionReview.image.loading": "لوڈ ہو رہیا اے...", + "ui.sessionReview.image.placeholder": "تصویر", + "ui.sessionReview.largeDiff.title": "رینڈر کرن لئی فرق بہوں وڈا اے", + "ui.sessionReview.largeDiff.meta": + "حد: {{limit}} تبدیل کیتیاں گئیاں لائناں۔ موجودہ: {{current}} بدلیاں گئیاں لائناں۔", + "ui.sessionReview.largeDiff.renderAnyway": "ویسے وی رینڈر کرو", + "ui.sessionReviewV2.expandMode": "فرق ودھاؤ یا منہدم کرو", + "ui.sessionReviewV2.filterFiles": "فائلاں نو فلٹر کرو", + "ui.sessionReviewV2.toggleSidebar": "فائل ٹری نوں ٹوگل کرو", + "ui.sessionReviewV2.showAllLines": "ساریاں لائناں وکھاؤ", + "ui.sessionReviewV2.hideNonDiffLines": "بناں تبدیلی والیاں لائناں لُکاؤ", + "ui.sessionReviewV2.unifiedDiff": "متحدہ فرق", + "ui.sessionReviewV2.splitDiff": "ونڈیا ہویا فرق", + "ui.sessionReviewV2.previousFile": "پچھلی فائل", + "ui.sessionReviewV2.nextFile": "اگلی فائل", + "ui.sessionReviewV2.diffView": "فرقاں دا نظارہ", + "ui.sessionReviewV2.empty.noGit.title": "کوئی ٹریک کیتی گئی تبدیلی نئیں", + "ui.sessionReviewV2.empty.noGit.description": "اس منصوبے چ تبدیلیاں نو ٹریک کرو، جائزہ لوو تے کالعدم کرو", + "ui.sessionReviewV2.empty.noGit.action": "Git ریپازٹری بناؤ", + "ui.sessionReviewV2.empty.noGit.actionLoading": "Git ریپازٹری بنانا...", + "ui.sessionReviewV2.empty.changes.title": "ہلے تیکر کوئی فائل نئیں بدلی", + "ui.sessionReviewV2.empty.changes.description": "پروجیکٹ دیاں تبدیلیاں ایتھے ظاہر ہون گیاں", + "ui.sessionReview.openFile": "فائل کھولو", + "ui.sessionReview.selection.line": "لائن {{line}}", + "ui.sessionReview.selection.lines": "لائناں {{start}}-{{end}}", + "ui.fileMedia.kind.image": "تصویر", + "ui.fileMedia.kind.audio": "آڈیو", + "ui.fileMedia.state.removed": "{{kind}} فائل ہٹا دتی گئی۔", + "ui.fileMedia.state.loading": "لوڈ ہو رہیا اے {{kind}}...", + "ui.fileMedia.state.error": "{{kind}} لوڈ کرن توں قاصر۔", + "ui.fileMedia.state.unavailable": "{{kind}} پیش نظارہ دستیاب نئیں اے۔", + "ui.fileMedia.binary.title": "بائنری فائل", + "ui.fileMedia.binary.description.path": "{{path}} بائنری اے۔", + "ui.fileMedia.binary.description.default": "بائنری مواد", + "ui.lineComment.label.prefix": "ایتھے تبصرہ کرو: ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "ایتھے تبصرہ ہو رہیا اے: ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "تبصرہ شامل کرو", + "ui.lineComment.contextPlaceholder": "اس تبدیلی لئی تناظر شامل کرو", + "ui.lineComment.submit": "تبصرہ کرو", + "ui.lineComment.cancel": "منسوخ کرو", + "ui.sessionTurn.steps.show": "اقدامات وکھاؤ", + "ui.sessionTurn.steps.hide": "قدماں نوں لُکاؤ", + "ui.sessionTurn.summary.response": "جواب", + "ui.sessionTurn.diff.showMore": "ہور تبدیلیاں وکھاؤ ({{count}})", + "ui.sessionTurn.diffs.changed.one": "{{count}} فائل بدل دتی گئی", + "ui.sessionTurn.diffs.changed.other": "{{count}} فائلاں بدلیاں گئیاں", + "ui.sessionTurn.diffs.showAll": "سب وکھاؤ", + "ui.sessionTurn.diffs.showLess": "گھٹ وکھاؤ", + "ui.sessionTurn.diffs.more": "+{{count}} ہور فائلاں", + "ui.sessionTurn.retry.retrying": "دوبارہ کوشش ہو رہی اے", + "ui.sessionTurn.retry.inSeconds": "{{seconds}}s پچھوں", + "ui.sessionTurn.retry.attempt": "کوشش #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - کوشش #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "Gemini ایس ویلے حدوں ودھ مصروف اے", + "ui.sessionTurn.error.freeUsageExceeded": "مفت استعمال توں ودھ گیا", + "ui.sessionTurn.error.addCredits": "کریڈٹ شامل کرو", + "dialog.usageExceeded.freeTier.title": "مفت حد پوری ہو گئی", + "dialog.usageExceeded.freeTier.description": + "$10/مہینہ وچ بہترین اوپن سورس ماڈلاں تک بھروسے جوگی رسائی لئی OpenCode Go دی رکنیت لوو۔", + "dialog.usageExceeded.freeTier.actionLabel": "سبسکرائب کرو", + "dialog.usageExceeded.accountRateLimit.title": "Go دی حد پوری ہو گئی", + "dialog.usageExceeded.accountRateLimit.description": + "ورتوں دی حد پوری ہو گئی۔ ہن ایہہ ماڈل ورتدے رہن لئی اپنے دستیاب بیلنس توں ورتوں چالو کرو", + "dialog.usageExceeded.accountRateLimit.actionLabel": "ترتیبات کھولو", + "ui.sessionTurn.status.delegating": "کم سونپیا جا رہیا اے", + "ui.sessionTurn.status.planning": "اگلے قدماں دی ویونت بندی ہو رہی اے", + "ui.sessionTurn.status.gatheringContext": "کھوج ہو رہی اے", + "ui.sessionTurn.status.gatheredContext": "کھوج ہو گئی", + "ui.sessionTurn.status.searchingCodebase": "کوڈ بیس وچ لبھیا جا رہیا اے", + "ui.sessionTurn.status.searchingWeb": "ویب تے لبھیا جا رہیا اے", + "ui.sessionTurn.status.makingEdits": "ترمیماں ہو رہیاں نیں", + "ui.sessionTurn.status.runningCommands": "کمانڈاں چل رہیاں نیں", + "ui.sessionTurn.status.thinking": "سوچ رہیا اے", + "ui.sessionTurn.status.thinkingWithTopic": "سوچ رہیا اے - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "خیال اکٹھے ہو رہے نیں", + "ui.sessionTurn.status.consideringNextSteps": "اگلے قدماں بارے سوچیا جا رہیا اے", + "ui.messagePart.diagnostic.error": "نقص", + "ui.messagePart.title.edit": "ترمیم کرو", + "ui.messagePart.title.write": "لکھو", + "ui.messagePart.option.typeOwnAnswer": "اپنا جواب آپ ٹائپ کرو", + "ui.messagePart.review.title": "اپنے جواباں دا جائزہ لوو", + "ui.messagePart.questions.dismissed": "سوال خارج کر دتے گئے", + "ui.messagePart.compaction": "سیشن کمپیکٹ کیتا گیا", + "ui.messagePart.context.read.one": "{{count}} پڑھت", + "ui.messagePart.context.read.other": "{{count}} پڑھتاں", + "ui.messagePart.context.search.one": "{{count}} تلاش", + "ui.messagePart.context.search.other": "{{count}} تلاشاں", + "ui.messagePart.context.list.one": "{{count}} لسٹ", + "ui.messagePart.context.list.other": "{{count}} فہرستاں", + "ui.list.loading": "لوڈ ہو رہیا اے", + "ui.list.empty": "کوئی نتیجہ نئیں نکلیا", + "ui.list.clearFilter": "فلٹر صاف کرو", + "ui.list.emptyWithFilter.prefix": "لئی کوئی نتیجہ نئیں", + "ui.list.emptyWithFilter.suffix": "", + "ui.fileSearch.placeholder": "لبھو", + "ui.fileSearch.previousMatch": "پچھلا میل", + "ui.fileSearch.nextMatch": "اگلا میل", + "ui.fileSearch.close": "تلاش بند کرو", + "ui.messageNav.newMessage": "نواں سنیہا", + "ui.promptInput.noMatchingItems": "کوئی میل کھاندی چیز نئیں", + "ui.promptInput.commands": "کمانڈاں", + "ui.promptInput.dropFiles": "نال جوڑن لئی فائلاں ایتھے چھڈو", + "ui.promptInput.removeAttachment": "منسلکہ ہٹاؤ", + "ui.promptInput.label": "پرامپٹ", + "ui.promptInput.placeholder.shell": "شیل کمانڈ درج کرو...", + "ui.promptInput.placeholder.normal": "کجھ وی پُچھو، {{slash}} کمانڈاں لئی، {{at}} تناظر لئی...", + "ui.promptInput.add": "تصویراں تے فائلاں شامل کرو", + "ui.promptInput.attachments": "تصویراں تے فائلاں", + "ui.promptInput.context": "تناظر", + "ui.promptInput.shell": "شیل کمانڈ", + "ui.promptInput.chooseAgent": "ایجنٹ چنو", + "ui.promptInput.chooseModel": "ماڈل چنو", + "ui.promptInput.chooseVariant": "ماڈل دی قسم چنو", + "ui.promptInput.send": "گھلو", + "ui.promptInput.stop": "روکو", + "ui.tabs.close": "ٹیب بند کرو", + "ui.textField.copyToClipboard": "کلپ بورڈ تے کاپی کرو", + "ui.textField.copyLink": "لنک کاپی کرو", + "ui.textField.copied": "کاپی کیتا گیا", + "ui.imagePreview.alt": "تصویر دا پیش نظارہ", + "ui.scrollView.ariaLabel": "سکرول کرن آلا مواد", + "ui.tool.read": "پڑھو", + "ui.tool.loaded": "لوڈ ہو گیا", + "ui.tool.list": "فہرست", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.task": "کم", + "ui.tool.webfetch": "Webfetch", + "ui.tool.websearch": "Web Search", + "ui.tool.websearch.provider": "{{provider}} Web Search", + "ui.tool.shell": "Shell", + "ui.tool.patch": "Patch", + "ui.tool.todos": "ٹوڈوس", + "ui.tool.todos.read": "ٹوڈوس پڑھو", + "ui.tool.questions": "سوال", + "ui.tool.questions.numbered": "سوال {{number}}", + "ui.tool.agent": "{{type}} Agent", + "ui.tool.agent.default": "Agent", + "ui.tool.skill": "Skill", + "ui.basicTool.called": "`{{tool}}` نوں سڈیا گیا", + "ui.toolErrorCard.failed": "ناکام ہویا", + "ui.toolErrorCard.copyError": "کاپی دی غلطی", + "ui.common.file.one": "فائل", + "ui.common.file.other": "فائلاں", + "ui.common.question.one": "سوال", + "ui.common.question.other": "سوال", + "ui.common.add": "شامل کرو", + "ui.common.clear": "صاف کرو", + "ui.common.file": "فائل", + "ui.common.back": "پچھے", + "ui.common.cancel": "منسوخ کرو", + "ui.common.confirm": "پکا کرو", + "ui.common.dismiss": "برخاست کرو", + "ui.common.close": "بند کرو", + "ui.common.next": "اگلا", + "ui.common.submit": "جمع کراؤ", + "ui.common.showMore": "مزید وکھاؤ", + "ui.permission.deny": "منع کرو", + "ui.permission.allowAlways": "ہمیشہ اجازت دیو", + "ui.permission.allowOnce": "اک واری اجازت دیو", + "ui.message.expand": "سنیہا ودھاؤ", + "ui.message.collapse": "سنیہا سمیٹو", + "ui.message.copy": "کاپی کرو", + "ui.message.copyMessage": "پیغام کاپی کرو", + "ui.message.forkMessage": "نویں سیشن تے فورک کرو", + "ui.message.revertMessage": "سنیہا واپس کرو", + "ui.message.copyResponse": "جواب کاپی کرو", + "ui.message.copied": "کاپی کیتا گیا", + "ui.message.duration.seconds": "{{count}}s", + "ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s", + "ui.message.interrupted": "رکاوٹ پائی گئی", + "ui.message.queued": "قطار چ", + "ui.message.attachment.alt": "منسلکہ", + "ui.patch.action.deleted": "مٹا دتا گیا", + "ui.patch.action.created": "بنایا گیا", + "ui.patch.action.moved": "منتقل ہو گیا", + "ui.patch.action.patched": "پیچ کیتا گیا", + "ui.question.subtitle.answered": "{{count}} جواب دتا گیا", + "ui.question.answer.none": "(کوئی جواب نئیں)", + "ui.question.review.notAnswered": "(جواب نئیں دتا گیا)", + "ui.question.multiHint": "لاگو ہون آلے سارے جواباں نوں چنو", + "ui.question.singleHint": "اک جواب چنو", + "ui.question.custom.placeholder": "اپنا جواب ٹائپ کرو...", +} diff --git a/packages/ui/src/styles/tailwind/colors.css b/packages/ui/src/styles/tailwind/colors.css new file mode 100644 index 0000000000000000000000000000000000000000..9935cb4037da41abb4a89648504cd031286c4703 --- /dev/null +++ b/packages/ui/src/styles/tailwind/colors.css @@ -0,0 +1,286 @@ +/* Generated by script/tailwind.ts */ +/* Do not edit this file manually */ + +@theme { + --color-*: initial; + --color-background-base: var(--background-base); + --color-background-weak: var(--background-weak); + --color-background-strong: var(--background-strong); + --color-background-stronger: var(--background-stronger); + --color-surface-base: var(--surface-base); + --color-base: var(--base); + --color-surface-base-hover: var(--surface-base-hover); + --color-surface-base-active: var(--surface-base-active); + --color-surface-base-interactive-active: var(--surface-base-interactive-active); + --color-base2: var(--base2); + --color-base3: var(--base3); + --color-surface-inset-base: var(--surface-inset-base); + --color-surface-inset-base-hover: var(--surface-inset-base-hover); + --color-surface-inset-strong: var(--surface-inset-strong); + --color-surface-inset-strong-hover: var(--surface-inset-strong-hover); + --color-surface-raised-base: var(--surface-raised-base); + --color-surface-float-base: var(--surface-float-base); + --color-surface-float-base-hover: var(--surface-float-base-hover); + --color-surface-raised-base-hover: var(--surface-raised-base-hover); + --color-surface-raised-base-active: var(--surface-raised-base-active); + --color-surface-raised-strong: var(--surface-raised-strong); + --color-surface-raised-strong-hover: var(--surface-raised-strong-hover); + --color-surface-raised-stronger: var(--surface-raised-stronger); + --color-surface-raised-stronger-hover: var(--surface-raised-stronger-hover); + --color-surface-weak: var(--surface-weak); + --color-surface-weaker: var(--surface-weaker); + --color-surface-strong: var(--surface-strong); + --color-surface-raised-stronger-non-alpha: var(--surface-raised-stronger-non-alpha); + --color-surface-brand-base: var(--surface-brand-base); + --color-surface-brand-hover: var(--surface-brand-hover); + --color-surface-interactive-base: var(--surface-interactive-base); + --color-surface-interactive-hover: var(--surface-interactive-hover); + --color-surface-interactive-weak: var(--surface-interactive-weak); + --color-surface-interactive-weak-hover: var(--surface-interactive-weak-hover); + --color-surface-success-base: var(--surface-success-base); + --color-surface-success-weak: var(--surface-success-weak); + --color-surface-success-strong: var(--surface-success-strong); + --color-surface-warning-base: var(--surface-warning-base); + --color-surface-warning-weak: var(--surface-warning-weak); + --color-surface-warning-strong: var(--surface-warning-strong); + --color-surface-critical-base: var(--surface-critical-base); + --color-surface-critical-weak: var(--surface-critical-weak); + --color-surface-critical-strong: var(--surface-critical-strong); + --color-surface-info-base: var(--surface-info-base); + --color-surface-info-weak: var(--surface-info-weak); + --color-surface-info-strong: var(--surface-info-strong); + --color-surface-diff-unchanged-base: var(--surface-diff-unchanged-base); + --color-surface-diff-skip-base: var(--surface-diff-skip-base); + --color-surface-diff-hidden-base: var(--surface-diff-hidden-base); + --color-surface-diff-hidden-weak: var(--surface-diff-hidden-weak); + --color-surface-diff-hidden-weaker: var(--surface-diff-hidden-weaker); + --color-surface-diff-hidden-strong: var(--surface-diff-hidden-strong); + --color-surface-diff-hidden-stronger: var(--surface-diff-hidden-stronger); + --color-surface-diff-add-base: var(--surface-diff-add-base); + --color-surface-diff-add-weak: var(--surface-diff-add-weak); + --color-surface-diff-add-weaker: var(--surface-diff-add-weaker); + --color-surface-diff-add-strong: var(--surface-diff-add-strong); + --color-surface-diff-add-stronger: var(--surface-diff-add-stronger); + --color-surface-diff-delete-base: var(--surface-diff-delete-base); + --color-surface-diff-delete-weak: var(--surface-diff-delete-weak); + --color-surface-diff-delete-weaker: var(--surface-diff-delete-weaker); + --color-surface-diff-delete-strong: var(--surface-diff-delete-strong); + --color-surface-diff-delete-stronger: var(--surface-diff-delete-stronger); + --color-text-base: var(--text-base); + --color-input-base: var(--input-base); + --color-input-hover: var(--input-hover); + --color-input-active: var(--input-active); + --color-input-selected: var(--input-selected); + --color-input-focus: var(--input-focus); + --color-input-disabled: var(--input-disabled); + --color-text-weak: var(--text-weak); + --color-text-weaker: var(--text-weaker); + --color-text-strong: var(--text-strong); + --color-text-interactive-base: var(--text-interactive-base); + --color-text-on-brand-base: var(--text-on-brand-base); + --color-text-on-interactive-base: var(--text-on-interactive-base); + --color-text-on-interactive-weak: var(--text-on-interactive-weak); + --color-text-on-success-base: var(--text-on-success-base); + --color-text-on-critical-base: var(--text-on-critical-base); + --color-text-on-critical-weak: var(--text-on-critical-weak); + --color-text-on-critical-strong: var(--text-on-critical-strong); + --color-text-on-warning-base: var(--text-on-warning-base); + --color-text-on-info-base: var(--text-on-info-base); + --color-text-diff-add-base: var(--text-diff-add-base); + --color-text-diff-delete-base: var(--text-diff-delete-base); + --color-text-diff-delete-strong: var(--text-diff-delete-strong); + --color-text-diff-add-strong: var(--text-diff-add-strong); + --color-text-on-info-weak: var(--text-on-info-weak); + --color-text-on-info-strong: var(--text-on-info-strong); + --color-text-on-warning-weak: var(--text-on-warning-weak); + --color-text-on-warning-strong: var(--text-on-warning-strong); + --color-text-on-success-weak: var(--text-on-success-weak); + --color-text-on-success-strong: var(--text-on-success-strong); + --color-text-on-brand-weak: var(--text-on-brand-weak); + --color-text-on-brand-weaker: var(--text-on-brand-weaker); + --color-text-on-brand-strong: var(--text-on-brand-strong); + --color-button-secondary-base: var(--button-secondary-base); + --color-button-secondary-hover: var(--button-secondary-hover); + --color-border-base: var(--border-base); + --color-border-hover: var(--border-hover); + --color-border-active: var(--border-active); + --color-border-selected: var(--border-selected); + --color-border-disabled: var(--border-disabled); + --color-border-focus: var(--border-focus); + --color-border-weak-base: var(--border-weak-base); + --color-border-strong-base: var(--border-strong-base); + --color-border-strong-hover: var(--border-strong-hover); + --color-border-strong-active: var(--border-strong-active); + --color-border-strong-selected: var(--border-strong-selected); + --color-border-strong-disabled: var(--border-strong-disabled); + --color-border-strong-focus: var(--border-strong-focus); + --color-border-weak-hover: var(--border-weak-hover); + --color-border-weak-active: var(--border-weak-active); + --color-border-weak-selected: var(--border-weak-selected); + --color-border-weak-disabled: var(--border-weak-disabled); + --color-border-weak-focus: var(--border-weak-focus); + --color-border-weaker-base: var(--border-weaker-base); + --color-border-interactive-base: var(--border-interactive-base); + --color-border-interactive-hover: var(--border-interactive-hover); + --color-border-interactive-active: var(--border-interactive-active); + --color-border-interactive-selected: var(--border-interactive-selected); + --color-border-interactive-disabled: var(--border-interactive-disabled); + --color-border-interactive-focus: var(--border-interactive-focus); + --color-border-success-base: var(--border-success-base); + --color-border-success-hover: var(--border-success-hover); + --color-border-success-selected: var(--border-success-selected); + --color-border-warning-base: var(--border-warning-base); + --color-border-warning-hover: var(--border-warning-hover); + --color-border-warning-selected: var(--border-warning-selected); + --color-border-critical-base: var(--border-critical-base); + --color-border-critical-hover: var(--border-critical-hover); + --color-border-critical-selected: var(--border-critical-selected); + --color-border-info-base: var(--border-info-base); + --color-border-info-hover: var(--border-info-hover); + --color-border-info-selected: var(--border-info-selected); + --color-icon-base: var(--icon-base); + --color-icon-hover: var(--icon-hover); + --color-icon-active: var(--icon-active); + --color-icon-selected: var(--icon-selected); + --color-icon-disabled: var(--icon-disabled); + --color-icon-focus: var(--icon-focus); + --color-icon-invert-base: var(--icon-invert-base); + --color-icon-weak-base: var(--icon-weak-base); + --color-icon-weak-hover: var(--icon-weak-hover); + --color-icon-weak-active: var(--icon-weak-active); + --color-icon-weak-selected: var(--icon-weak-selected); + --color-icon-weak-disabled: var(--icon-weak-disabled); + --color-icon-weak-focus: var(--icon-weak-focus); + --color-icon-strong-base: var(--icon-strong-base); + --color-icon-strong-hover: var(--icon-strong-hover); + --color-icon-strong-active: var(--icon-strong-active); + --color-icon-strong-selected: var(--icon-strong-selected); + --color-icon-strong-disabled: var(--icon-strong-disabled); + --color-icon-strong-focus: var(--icon-strong-focus); + --color-icon-brand-base: var(--icon-brand-base); + --color-icon-interactive-base: var(--icon-interactive-base); + --color-icon-success-base: var(--icon-success-base); + --color-icon-success-hover: var(--icon-success-hover); + --color-icon-success-active: var(--icon-success-active); + --color-icon-warning-base: var(--icon-warning-base); + --color-icon-warning-hover: var(--icon-warning-hover); + --color-icon-warning-active: var(--icon-warning-active); + --color-icon-critical-base: var(--icon-critical-base); + --color-icon-critical-hover: var(--icon-critical-hover); + --color-icon-critical-active: var(--icon-critical-active); + --color-icon-info-base: var(--icon-info-base); + --color-icon-info-hover: var(--icon-info-hover); + --color-icon-info-active: var(--icon-info-active); + --color-icon-on-brand-base: var(--icon-on-brand-base); + --color-icon-on-brand-hover: var(--icon-on-brand-hover); + --color-icon-on-brand-selected: var(--icon-on-brand-selected); + --color-icon-on-interactive-base: var(--icon-on-interactive-base); + --color-icon-agent-plan-base: var(--icon-agent-plan-base); + --color-icon-agent-docs-base: var(--icon-agent-docs-base); + --color-icon-agent-ask-base: var(--icon-agent-ask-base); + --color-icon-agent-build-base: var(--icon-agent-build-base); + --color-icon-on-success-base: var(--icon-on-success-base); + --color-icon-on-success-hover: var(--icon-on-success-hover); + --color-icon-on-success-selected: var(--icon-on-success-selected); + --color-icon-on-warning-base: var(--icon-on-warning-base); + --color-icon-on-warning-hover: var(--icon-on-warning-hover); + --color-icon-on-warning-selected: var(--icon-on-warning-selected); + --color-icon-on-critical-base: var(--icon-on-critical-base); + --color-icon-on-critical-hover: var(--icon-on-critical-hover); + --color-icon-on-critical-selected: var(--icon-on-critical-selected); + --color-icon-on-info-base: var(--icon-on-info-base); + --color-icon-on-info-hover: var(--icon-on-info-hover); + --color-icon-on-info-selected: var(--icon-on-info-selected); + --color-icon-diff-add-base: var(--icon-diff-add-base); + --color-icon-diff-add-hover: var(--icon-diff-add-hover); + --color-icon-diff-add-active: var(--icon-diff-add-active); + --color-icon-diff-delete-base: var(--icon-diff-delete-base); + --color-icon-diff-delete-hover: var(--icon-diff-delete-hover); + --color-syntax-comment: var(--syntax-comment); + --color-syntax-regexp: var(--syntax-regexp); + --color-syntax-string: var(--syntax-string); + --color-syntax-keyword: var(--syntax-keyword); + --color-syntax-primitive: var(--syntax-primitive); + --color-syntax-operator: var(--syntax-operator); + --color-syntax-variable: var(--syntax-variable); + --color-syntax-property: var(--syntax-property); + --color-syntax-type: var(--syntax-type); + --color-syntax-constant: var(--syntax-constant); + --color-syntax-punctuation: var(--syntax-punctuation); + --color-syntax-object: var(--syntax-object); + --color-syntax-success: var(--syntax-success); + --color-syntax-warning: var(--syntax-warning); + --color-syntax-critical: var(--syntax-critical); + --color-syntax-info: var(--syntax-info); + --color-syntax-diff-add: var(--syntax-diff-add); + --color-syntax-diff-delete: var(--syntax-diff-delete); + --color-syntax-diff-unknown: var(--syntax-diff-unknown); + --color-markdown-heading: var(--markdown-heading); + --color-markdown-text: var(--markdown-text); + --color-markdown-link: var(--markdown-link); + --color-markdown-link-text: var(--markdown-link-text); + --color-markdown-code: var(--markdown-code); + --color-markdown-block-quote: var(--markdown-block-quote); + --color-markdown-emph: var(--markdown-emph); + --color-markdown-strong: var(--markdown-strong); + --color-markdown-horizontal-rule: var(--markdown-horizontal-rule); + --color-markdown-list-item: var(--markdown-list-item); + --color-markdown-list-enumeration: var(--markdown-list-enumeration); + --color-markdown-image: var(--markdown-image); + --color-markdown-image-text: var(--markdown-image-text); + --color-markdown-code-block: var(--markdown-code-block); + --color-border-color: var(--border-color); + --color-button-ghost-hover: var(--button-ghost-hover); + --color-button-ghost-hover2: var(--button-ghost-hover2); + --color-v2-background-bg-base: var(--v2-background-bg-base); + --color-v2-background-bg-deep: var(--v2-background-bg-deep); + --color-v2-background-bg-layer-01: var(--v2-background-bg-layer-01); + --color-v2-background-bg-layer-02: var(--v2-background-bg-layer-02); + --color-v2-background-bg-layer-03: var(--v2-background-bg-layer-03); + --color-v2-background-bg-layer-04: var(--v2-background-bg-layer-04); + --color-v2-background-bg-inverse: var(--v2-background-bg-inverse); + --color-v2-background-bg-contrast: var(--v2-background-bg-contrast); + --color-v2-background-bg-button-neutral: var(--v2-background-bg-button-neutral); + --color-v2-background-bg-accent: var(--v2-background-bg-accent); + --color-v2-text-text-base: var(--v2-text-text-base); + --color-v2-text-text-muted: var(--v2-text-text-muted); + --color-v2-text-text-faint: var(--v2-text-text-faint); + --color-v2-text-text-inverse: var(--v2-text-text-inverse); + --color-v2-text-text-contrast: var(--v2-text-text-contrast); + --color-v2-text-text-accent: var(--v2-text-text-accent); + --color-v2-text-text-accent-hover: var(--v2-text-text-accent-hover); + --color-v2-text-text-code-accent: var(--v2-text-text-code-accent); + --color-v2-icon-icon-base: var(--v2-icon-icon-base); + --color-v2-icon-icon-muted: var(--v2-icon-icon-muted); + --color-v2-icon-icon-inverse: var(--v2-icon-icon-inverse); + --color-v2-icon-icon-contrast: var(--v2-icon-icon-contrast); + --color-v2-icon-icon-accent: var(--v2-icon-icon-accent); + --color-v2-icon-icon-accent-hover: var(--v2-icon-icon-accent-hover); + --color-v2-border-border-muted: var(--v2-border-border-muted); + --color-v2-border-border-base: var(--v2-border-border-base); + --color-v2-border-border-strong: var(--v2-border-border-strong); + --color-v2-border-border-inverse: var(--v2-border-border-inverse); + --color-v2-border-border-focus: var(--v2-border-border-focus); + --color-v2-overlay-simple-overlay-hover: var(--v2-overlay-simple-overlay-hover); + --color-v2-overlay-simple-overlay-pressed: var(--v2-overlay-simple-overlay-pressed); + --color-v2-overlay-simple-overlay-contrast-hover: var(--v2-overlay-simple-overlay-contrast-hover); + --color-v2-overlay-simple-overlay-contrast-pressed: var(--v2-overlay-simple-overlay-contrast-pressed); + --color-v2-overlay-simple-overlay-scrim: var(--v2-overlay-simple-overlay-scrim); + --color-v2-overlay-gradient-depth-overlay-depth-top: var(--v2-overlay-gradient-depth-overlay-depth-top); + --color-v2-overlay-gradient-depth-overlay-depth-bot: var(--v2-overlay-gradient-depth-overlay-depth-bot); + --color-v2-overlay-simple-tab-active-scrim: var(--v2-overlay-simple-tab-active-scrim); + --color-v2-overlay-simple-tab-hover-scrim: var(--v2-overlay-simple-tab-hover-scrim); + --color-v2-overlay-simple-tab-scrim: var(--v2-overlay-simple-tab-scrim); + --color-v2-state-bg-success: var(--v2-state-bg-success); + --color-v2-state-fg-success: var(--v2-state-fg-success); + --color-v2-state-border-success: var(--v2-state-border-success); + --color-v2-state-bg-warning: var(--v2-state-bg-warning); + --color-v2-state-fg-warning: var(--v2-state-fg-warning); + --color-v2-state-border-warning: var(--v2-state-border-warning); + --color-v2-state-bg-danger: var(--v2-state-bg-danger); + --color-v2-state-fg-danger: var(--v2-state-fg-danger); + --color-v2-state-border-danger: var(--v2-state-border-danger); + --color-v2-state-bg-info: var(--v2-state-bg-info); + --color-v2-state-fg-info: var(--v2-state-fg-info); + --color-v2-state-border-info: var(--v2-state-border-info); +} diff --git a/packages/ui/src/styles/tailwind/index.css b/packages/ui/src/styles/tailwind/index.css new file mode 100644 index 0000000000000000000000000000000000000000..d8b0b2a1a0c4db9f4c005da4463c2bd559d7fb4b --- /dev/null +++ b/packages/ui/src/styles/tailwind/index.css @@ -0,0 +1,78 @@ +@layer theme, base, components, utilities; + +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/utilities.css" layer(utilities) source("../../../../"); +@import "./utilities.css"; + +@import "../index.css"; + +@theme { + --*: initial; + + --spacing: 0.25rem; + /* --spacing: var(--spacing); */ + + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + --breakpoint-3xl: 112rem; + --breakpoint-4xl: 128rem; + --breakpoint-5xl: 144rem; + + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + + --font-sans: var(--font-family-sans); + --font-sans--font-feature-settings: var(--font-family-sans--font-feature-settings); + --font-mono: var(--font-family-mono); + --font-mono--font-feature-settings: var(--font-family-mono--font-feature-settings); + + --text-sm: var(--font-size-small); + --text-base: var(--font-size-base); + --text-lg: var(--font-size-large); + --text-xl: var(--font-size-x-large); + + --font-weight-regular: var(--font-weight-regular); + --font-weight-medium: var(--font-weight-medium); + + --leading-lg: var(--line-height-large); + --leading-xl: var(--line-height-x-large); + --leading-2xl: var(--line-height-2x-large); + + --tracking-normal: var(--letter-spacing-normal); + --tracking-tight: var(--letter-spacing-tight); + --tracking-tightest: var(--letter-spacing-tightest); + + --radius-xs: 0.125rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.625rem; + + --shadow-xs: var(--shadow-xs); + --shadow-md: var(--shadow-md); + --shadow-xs-border: var(--shadow-xs-border); + --shadow-xs-border-base: var(--shadow-xs-border-base); + --shadow-xs-border-select: var(--shadow-xs-border-select); + --shadow-xs-border-focus: var(--shadow-xs-border-focus); + --shadow-xs-border-hover: var(--shadow-xs-border-hover); + --shadow-xs-border-critical-base: var(--shadow-xs-border-critical-base); + --shadow-lg-border-base: var(--shadow-lg-border-base); + + --animate-pulse: var(--animate-pulse); +} + +@import "./colors.css"; diff --git a/packages/ui/src/styles/tailwind/utilities.css b/packages/ui/src/styles/tailwind/utilities.css new file mode 100644 index 0000000000000000000000000000000000000000..2c88136b6974dc9abcbe350d76ffa3e1d4a50ba3 --- /dev/null +++ b/packages/ui/src/styles/tailwind/utilities.css @@ -0,0 +1,131 @@ +@utility no-scrollbar { + &::-webkit-scrollbar { + display: none; + } + & { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + +@utility hover-reveal { + @apply opacity-0 transition-opacity; + + @media (hover: none) { + opacity: 1; + } +} + +@utility badge-mask { + -webkit-mask-image: radial-gradient(circle 5px at calc(100% - 4px) 4px, transparent 5px, black 5.5px); + mask-image: radial-gradient(circle 5px at calc(100% - 4px) 4px, transparent 5px, black 5.5px); +} + +@utility badge-mask-tight { + -webkit-mask-image: radial-gradient(circle 5px at calc(100% - 2px) 2px, transparent 5px, black 5.5px); + mask-image: radial-gradient(circle 5px at calc(100% - 2px) 2px, transparent 5px, black 5.5px); +} + +@utility truncate-start { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + direction: rtl; + text-align: left; +} + +@utility fade-up-text { + animation: fadeUp 0.4s ease-out forwards; + opacity: 0; + + &:nth-child(1) { + animation-delay: 0.1s; + } + &:nth-child(2) { + animation-delay: 0.2s; + } + &:nth-child(3) { + animation-delay: 0.3s; + } + &:nth-child(4) { + animation-delay: 0.4s; + } + &:nth-child(5) { + animation-delay: 0.5s; + } + &:nth-child(6) { + animation-delay: 0.6s; + } + &:nth-child(7) { + animation-delay: 0.7s; + } + &:nth-child(8) { + animation-delay: 0.8s; + } + &:nth-child(9) { + animation-delay: 0.9s; + } + &:nth-child(10) { + animation-delay: 1s; + } + &:nth-child(11) { + animation-delay: 1.1s; + } + &:nth-child(12) { + animation-delay: 1.2s; + } + &:nth-child(13) { + animation-delay: 1.3s; + } + &:nth-child(14) { + animation-delay: 1.4s; + } + &:nth-child(15) { + animation-delay: 1.5s; + } + &:nth-child(16) { + animation-delay: 1.6s; + } + &:nth-child(17) { + animation-delay: 1.7s; + } + &:nth-child(18) { + animation-delay: 1.8s; + } + &:nth-child(19) { + animation-delay: 1.9s; + } + &:nth-child(20) { + animation-delay: 2s; + } + &:nth-child(21) { + animation-delay: 2.1s; + } + &:nth-child(22) { + animation-delay: 2.2s; + } + &:nth-child(23) { + animation-delay: 2.3s; + } + &:nth-child(24) { + animation-delay: 2.4s; + } + &:nth-child(25) { + animation-delay: 2.5s; + } + &:nth-child(26) { + animation-delay: 2.6s; + } + &:nth-child(27) { + animation-delay: 2.7s; + } + &:nth-child(28) { + animation-delay: 2.8s; + } + &:nth-child(29) { + animation-delay: 2.9s; + } + &:nth-child(30) { + animation-delay: 3s; + } +} diff --git a/packages/ui/src/theme/color.ts b/packages/ui/src/theme/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..730642b1bb02c8c2091e5c4eef2b6e7e02b52cd4 --- /dev/null +++ b/packages/ui/src/theme/color.ts @@ -0,0 +1,299 @@ +import type { HexColor, OklchColor } from "./types" + +function clamp(v: number, min: number, max: number) { + return Math.max(min, Math.min(max, v)) +} + +function hue(v: number) { + return ((v % 360) + 360) % 360 +} + +export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } { + const h = hex.replace("#", "") + const full = + h.length === 3 || h.length === 4 + ? h + .split("") + .map((c) => c + c) + .join("") + : h + const rgb = full.length === 8 ? full.slice(0, 6) : full + + const num = parseInt(rgb, 16) + return { + r: ((num >> 16) & 255) / 255, + g: ((num >> 8) & 255) / 255, + b: (num & 255) / 255, + } +} + +export function rgbToHex(r: number, g: number, b: number): HexColor { + const toHex = (v: number) => { + const clamped = clamp(v, 0, 1) + const int = Math.round(clamped * 255) + return int.toString(16).padStart(2, "0") + } + return `#${toHex(r)}${toHex(g)}${toHex(b)}` +} + +function linearToSrgb(c: number): number { + if (c <= 0.0031308) return c * 12.92 + return 1.055 * Math.pow(c, 1 / 2.4) - 0.055 +} + +function srgbToLinear(c: number): number { + if (c <= 0.04045) return c / 12.92 + return Math.pow((c + 0.055) / 1.055, 2.4) +} + +export function rgbToOklch(r: number, g: number, b: number): OklchColor { + const lr = srgbToLinear(r) + const lg = srgbToLinear(g) + const lb = srgbToLinear(b) + + const l_ = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb + const m_ = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb + const s_ = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb + + const l = Math.cbrt(l_) + const m = Math.cbrt(m_) + const s = Math.cbrt(s_) + + const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s + const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s + const bOk = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s + + const C = Math.sqrt(a * a + bOk * bOk) + let H = Math.atan2(bOk, a) * (180 / Math.PI) + if (H < 0) H += 360 + + return { l: L, c: C, h: H } +} + +export function oklchToRgb(oklch: OklchColor): { r: number; g: number; b: number } { + const { l: L, c: C, h: H } = oklch + + const a = C * Math.cos((H * Math.PI) / 180) + const b = C * Math.sin((H * Math.PI) / 180) + + const l = L + 0.3963377774 * a + 0.2158037573 * b + const m = L - 0.1055613458 * a - 0.0638541728 * b + const s = L - 0.0894841775 * a - 1.291485548 * b + + const l3 = l * l * l + const m3 = m * m * m + const s3 = s * s * s + + const lr = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3 + const lg = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3 + const lb = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.707614701 * s3 + + return { + r: linearToSrgb(lr), + g: linearToSrgb(lg), + b: linearToSrgb(lb), + } +} + +export function hexToOklch(hex: HexColor): OklchColor { + const { r, g, b } = hexToRgb(hex) + return rgbToOklch(r, g, b) +} + +export function fitOklch(oklch: OklchColor): OklchColor { + const base = { + l: clamp(oklch.l, 0, 1), + c: Math.max(0, oklch.c), + h: hue(oklch.h), + } + + const rgb = oklchToRgb(base) + if (rgb.r >= 0 && rgb.r <= 1 && rgb.g >= 0 && rgb.g <= 1 && rgb.b >= 0 && rgb.b <= 1) { + return base + } + + let c = base.c + for (let i = 0; i < 24; i++) { + c *= 0.9 + const next = { ...base, c } + const out = oklchToRgb(next) + if (out.r >= 0 && out.r <= 1 && out.g >= 0 && out.g <= 1 && out.b >= 0 && out.b <= 1) { + return next + } + } + + return { ...base, c: 0 } +} + +export function oklchToHex(oklch: OklchColor): HexColor { + const { r, g, b } = oklchToRgb(fitOklch(oklch)) + return rgbToHex(r, g, b) +} + +export function generateScale(seed: HexColor, isDark: boolean): HexColor[] { + const base = hexToOklch(seed) + const scale: HexColor[] = [] + + const lightSteps = isDark + ? [ + 0.118, + 0.138, + 0.167, + 0.202, + 0.246, + 0.304, + 0.378, + 0.468, + clamp(base.l * 0.825, 0.53, 0.705), + clamp(base.l * 0.89, 0.61, 0.79), + clamp(base.l + 0.033, 0.868, 0.943), + 0.984, + ] + : [0.993, 0.983, 0.962, 0.936, 0.906, 0.866, 0.811, 0.74, base.l, Math.max(0, base.l - 0.036), 0.49, 0.27] + + const chromaMultipliers = isDark + ? [0.52, 0.68, 0.86, 1.02, 1.14, 1.24, 1.36, 1.48, 1.56, 1.64, 1.62, 1.15] + : [0.12, 0.24, 0.46, 0.68, 0.84, 0.98, 1.08, 1.16, 1.22, 1.26, 1.18, 0.98] + + for (let i = 0; i < 12; i++) { + scale.push( + oklchToHex({ + l: lightSteps[i], + c: base.c * chromaMultipliers[i], + h: base.h, + }), + ) + } + + return scale +} + +export function generateNeutralScale(seed: HexColor, isDark: boolean, ink?: HexColor): HexColor[] { + if (ink) { + const base = hexToOklch(seed) + const lift = (tone: number) => + oklchToHex({ + l: base.l + (1 - base.l) * tone, + c: base.c * Math.max(0, 1 - tone), + h: base.h, + }) + const sink = (tone: number) => + oklchToHex({ + l: base.l * (1 - tone), + c: base.c * Math.max(0, 1 - tone * (isDark ? 0.12 : 0.3)), + h: base.h, + }) + const bg = isDark + ? sink(clamp(0.19 + Math.max(0, base.l - 0.12) * 0.33 + base.c * 1.95, 0.17, 0.27)) + : base.l < 0.82 + ? lift(0.86) + : lift(clamp(0.1 + base.c * 3.2 + Math.max(0, 0.95 - base.l) * 0.35, 0.1, 0.28)) + const steps = isDark + ? [0, 0.018, 0.039, 0.064, 0.097, 0.143, 0.212, 0.31, 0.46, 0.649, 0.845, 0.984] + : [0, 0.022, 0.042, 0.068, 0.102, 0.146, 0.208, 0.296, 0.432, 0.61, 0.81, 0.965] + return steps.map((step) => mixColors(bg, ink, step)) + } + + const base = hexToOklch(seed) + const scale: HexColor[] = [] + const neutralChroma = Math.min(base.c, isDark ? 0.068 : 0.04) + + const lightSteps = isDark + ? [0.138, 0.156, 0.178, 0.202, 0.232, 0.272, 0.326, 0.404, clamp(base.l * 0.83, 0.43, 0.55), 0.596, 0.719, 0.956] + : [0.991, 0.979, 0.964, 0.946, 0.931, 0.913, 0.891, 0.83, base.l, 0.617, 0.542, 0.205] + + for (let i = 0; i < 12; i++) { + scale.push( + oklchToHex({ + l: lightSteps[i], + c: neutralChroma, + h: base.h, + }), + ) + } + + return scale +} + +export function generateAlphaScale(scale: HexColor[], isDark: boolean): HexColor[] { + const alphas = isDark + ? [0.02, 0.04, 0.08, 0.12, 0.16, 0.2, 0.26, 0.36, 0.44, 0.52, 0.76, 0.96] + : [0.01, 0.03, 0.06, 0.09, 0.12, 0.15, 0.2, 0.28, 0.48, 0.56, 0.64, 0.88] + + return scale.map((hex, i) => { + const { r, g, b } = hexToRgb(hex) + const a = alphas[i] + + const bg = isDark ? 0 : 1 + const blendedR = r * a + bg * (1 - a) + const blendedG = g * a + bg * (1 - a) + const blendedB = b * a + bg * (1 - a) + + return rgbToHex(blendedR, blendedG, blendedB) + }) +} + +export function mixColors(color1: HexColor, color2: HexColor, amount: number): HexColor { + const c1 = hexToOklch(color1) + const c2 = hexToOklch(color2) + const delta = ((((c2.h - c1.h) % 360) + 540) % 360) - 180 + + return oklchToHex({ + l: c1.l + (c2.l - c1.l) * amount, + c: c1.c + (c2.c - c1.c) * amount, + h: c1.h + delta * amount, + }) +} + +export function shift(color: HexColor, value: { l?: number; c?: number; h?: number }): HexColor { + const base = hexToOklch(color) + return oklchToHex({ + l: base.l + (value.l ?? 0), + c: base.c * (value.c ?? 1), + h: base.h + (value.h ?? 0), + }) +} + +export function contrastRatio(foreground: HexColor, background: HexColor) { + const linear = (c: number) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4) + const luminance = (hex: HexColor) => { + const { r, g, b } = hexToRgb(hex) + return 0.2126 * linear(r) + 0.587 * linear(g) + 0.0722 * linear(b) + } + const fg = luminance(foreground) + const bg = luminance(background) + const lighter = Math.max(fg, bg) + const darker = Math.min(fg, bg) + return (lighter + 0.05) / (darker + 0.05) +} + +export function blend(color: HexColor, background: HexColor, alpha: number): HexColor { + const fg = hexToRgb(color) + const bg = hexToRgb(background) + return rgbToHex( + fg.r * alpha + bg.r * (1 - alpha), + fg.g * alpha + bg.g * (1 - alpha), + fg.b * alpha + bg.b * (1 - alpha), + ) +} + +export function lighten(color: HexColor, amount: number): HexColor { + const oklch = hexToOklch(color) + return oklchToHex({ + ...oklch, + l: clamp(oklch.l + amount, 0, 1), + }) +} + +export function darken(color: HexColor, amount: number): HexColor { + const oklch = hexToOklch(color) + return oklchToHex({ + ...oklch, + l: clamp(oklch.l - amount, 0, 1), + }) +} + +export function withAlpha(color: HexColor, alpha: number): string { + const { r, g, b } = hexToRgb(color) + return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${alpha})` +} diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f225ee33ac08e891fbbe404be86e5ad39d39d988 --- /dev/null +++ b/packages/ui/src/theme/context.tsx @@ -0,0 +1,373 @@ +// @refresh reload + +import { createEffect, onMount } from "solid-js" +import { createStore } from "solid-js/store" +import { makeEventListener } from "@solid-primitives/event-listener" +import { createSimpleContext } from "../context/helper" +import oc2ThemeJson from "./themes/oc-2.json" +import { resolveThemeVariant, themeToCss } from "./resolve" +import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve" +import type { DesktopTheme } from "./types" + +export type ColorScheme = "light" | "dark" | "system" + +const STORAGE_KEYS = { + THEME_ID: "opencode-theme-id", + COLOR_SCHEME: "opencode-color-scheme", + THEME_CSS_LIGHT: "opencode-theme-css-light", + THEME_CSS_DARK: "opencode-theme-css-dark", +} as const + +const THEME_STYLE_ID = "oc-theme" +let files: Record Promise<{ default: DesktopTheme }>> | undefined +let ids: string[] | undefined +let known: Set | undefined + +function getFiles() { + if (files) return files + files = import.meta.glob<{ default: DesktopTheme }>("./themes/*.json") + return files +} + +function themeIDs() { + if (ids) return ids + ids = Object.keys(getFiles()) + .map((path) => path.slice("./themes/".length, -".json".length)) + .sort() + return ids +} + +function knownThemes() { + if (known) return known + known = new Set(themeIDs()) + return known +} + +const names: Record = { + "oc-2": "OC-2", + amoled: "AMOLED", + aura: "Aura", + ayu: "Ayu", + carbonfox: "Carbonfox", + catppuccin: "Catppuccin", + "catppuccin-frappe": "Catppuccin Frappe", + "catppuccin-macchiato": "Catppuccin Macchiato", + cobalt2: "Cobalt2", + cursor: "Cursor", + dracula: "Dracula", + everforest: "Everforest", + flexoki: "Flexoki", + github: "GitHub", + gruvbox: "Gruvbox", + kanagawa: "Kanagawa", + "lucent-orng": "Lucent Orng", + material: "Material", + matrix: "Matrix", + mercury: "Mercury", + monokai: "Monokai", + nightowl: "Night Owl", + nord: "Nord", + "one-dark": "One Dark", + onedarkpro: "One Dark Pro", + opencode: "OpenCode", + orng: "Orng", + "osaka-jade": "Osaka Jade", + palenight: "Palenight", + rosepine: "Rose Pine", + shadesofpurple: "Shades of Purple", + solarized: "Solarized", + synthwave84: "Synthwave '84", + tokyonight: "Tokyonight", + vercel: "Vercel", + vesper: "Vesper", + zenburn: "Zenburn", +} +const oc2Theme = oc2ThemeJson as DesktopTheme + +function normalize(id: string | null | undefined) { + return id === "oc-1" ? "oc-2" : id +} + +function read(key: string) { + if (typeof localStorage !== "object") return null + try { + return localStorage.getItem(key) + } catch { + return null + } +} + +function write(key: string, value: string) { + if (typeof localStorage !== "object") return + try { + localStorage.setItem(key, value) + } catch {} +} + +function drop(key: string) { + if (typeof localStorage !== "object") return + try { + localStorage.removeItem(key) + } catch {} +} + +function clear() { + drop(STORAGE_KEYS.THEME_CSS_LIGHT) + drop(STORAGE_KEYS.THEME_CSS_DARK) +} + +function ensureThemeStyleElement(): HTMLStyleElement { + const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null + if (existing) return existing + const element = document.createElement("style") + element.id = THEME_STYLE_ID + document.head.appendChild(element) + return element +} + +function getSystemMode(): "light" | "dark" { + if (typeof window !== "object") return "light" + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" +} + +function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark") { + const isDark = mode === "dark" + const variant = isDark ? theme.dark : theme.light + const tokens = resolveThemeVariant(variant, isDark) + const css = themeToCss(tokens) + const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark)) + + if (themeId !== "oc-2") { + write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`) + } + + const fullCss = `:root { + color-scheme: ${mode}; + --text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"}; + ${css} + ${v2} +}` + + document.getElementById("oc-theme-preload")?.remove() + ensureThemeStyleElement().textContent = fullCss + document.documentElement.dataset.theme = themeId + document.documentElement.dataset.colorScheme = mode + document.documentElement.style.backgroundColor = isDark ? "#080808" : "#fafafa" + + // Update theme-color meta tag to match light/dark mode + const meta = document.querySelector('meta[name="theme-color"]') + if (meta) meta.setAttribute("content", isDark ? "#080808" : "#fafafa") +} + +function cacheThemeVariants(theme: DesktopTheme, themeId: string) { + if (themeId === "oc-2") return + for (const mode of ["light", "dark"] as const) { + const isDark = mode === "dark" + const variant = isDark ? theme.dark : theme.light + const tokens = resolveThemeVariant(variant, isDark) + const css = themeToCss(tokens) + const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark)) + write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`) + } +} + +export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ + name: "Theme", + init: (props: { + defaultTheme?: string + onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark", scheme: ColorScheme) => void + }) => { + const themeId = normalize(read(STORAGE_KEYS.THEME_ID) ?? props.defaultTheme) ?? "oc-2" + const colorScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system" + const mode = colorScheme === "system" ? getSystemMode() : colorScheme + const [store, setStore] = createStore({ + themes: { + "oc-2": oc2Theme, + } as Record, + themeId, + colorScheme, + mode, + previewThemeId: null as string | null, + previewScheme: null as ColorScheme | null, + }) + + const loads = new Map>() + + const load = (id: string) => { + const next = normalize(id) + if (!next) return Promise.resolve(undefined) + const hit = store.themes[next] + if (hit) return Promise.resolve(hit) + const pending = loads.get(next) + if (pending) return pending + const file = getFiles()[`./themes/${next}.json`] + if (!file) return Promise.resolve(undefined) + const task = file() + .then((mod) => { + const theme = mod.default + setStore("themes", next, theme) + return theme + }) + .finally(() => { + loads.delete(next) + }) + loads.set(next, task) + return task + } + + const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark", scheme: ColorScheme) => { + applyThemeCss(theme, themeId, mode) + props.onThemeApplied?.(theme, mode, scheme) + } + + const ids = () => { + const extra = Object.keys(store.themes) + .filter((id) => !knownThemes().has(id)) + .sort() + const all = themeIDs() + if (extra.length === 0) return all + return [...all, ...extra] + } + + const loadThemes = () => Promise.all(themeIDs().map(load)).then(() => store.themes) + + const onStorage = (e: StorageEvent) => { + if (e.key === STORAGE_KEYS.THEME_ID && e.newValue) { + const next = normalize(e.newValue) + if (!next) return + if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) return + setStore("themeId", next) + if (next === "oc-2") { + clear() + return + } + void load(next).then((theme) => { + if (!theme || store.themeId !== next) return + cacheThemeVariants(theme, next) + }) + } + if (e.key === STORAGE_KEYS.COLOR_SCHEME && e.newValue) { + setStore("colorScheme", e.newValue as ColorScheme) + setStore("mode", e.newValue === "system" ? getSystemMode() : (e.newValue as "light" | "dark")) + } + } + + onMount(() => { + makeEventListener(window, "storage", onStorage) + + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + const onMedia = () => { + if (store.colorScheme !== "system") return + setStore("mode", getSystemMode()) + } + makeEventListener(mediaQuery, "change", onMedia) + + const rawTheme = read(STORAGE_KEYS.THEME_ID) + const savedTheme = normalize(rawTheme ?? props.defaultTheme) ?? "oc-2" + const savedScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system" + if (rawTheme && rawTheme !== savedTheme) { + write(STORAGE_KEYS.THEME_ID, savedTheme) + clear() + } + if (savedTheme !== store.themeId) setStore("themeId", savedTheme) + if (savedScheme !== store.colorScheme) setStore("colorScheme", savedScheme) + setStore("mode", savedScheme === "system" ? getSystemMode() : savedScheme) + void load(savedTheme).then((theme) => { + if (!theme || store.themeId !== savedTheme) return + cacheThemeVariants(theme, savedTheme) + }) + }) + + createEffect(() => { + const theme = store.themes[store.themeId] + if (!theme) return + applyTheme(theme, store.themeId, store.mode, store.colorScheme) + }) + + const setTheme = (id: string) => { + const next = normalize(id) + if (!next) { + console.warn(`Theme "${id}" not found`) + return + } + if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) { + console.warn(`Theme "${id}" not found`) + return + } + setStore("themeId", next) + if (next === "oc-2") { + write(STORAGE_KEYS.THEME_ID, next) + clear() + return + } + void load(next).then((theme) => { + if (!theme || store.themeId !== next) return + cacheThemeVariants(theme, next) + write(STORAGE_KEYS.THEME_ID, next) + }) + } + + const setColorScheme = (scheme: ColorScheme) => { + setStore("colorScheme", scheme) + write(STORAGE_KEYS.COLOR_SCHEME, scheme) + setStore("mode", scheme === "system" ? getSystemMode() : scheme) + } + + return { + themeId: () => store.themeId, + colorScheme: () => store.colorScheme, + mode: () => store.mode, + ids, + name: (id: string) => store.themes[id]?.name ?? names[id] ?? id, + loadThemes, + themes: () => store.themes, + setTheme, + setColorScheme, + registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme), + previewTheme: (id: string) => { + const next = normalize(id) + if (!next) return + if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) return + setStore("previewThemeId", next) + void load(next).then((theme) => { + if (!theme || store.previewThemeId !== next) return + const mode = store.previewScheme + ? store.previewScheme === "system" + ? getSystemMode() + : store.previewScheme + : store.mode + applyTheme(theme, next, mode, store.previewScheme ?? store.colorScheme) + }) + }, + previewColorScheme: (scheme: ColorScheme) => { + setStore("previewScheme", scheme) + const mode = scheme === "system" ? getSystemMode() : scheme + const id = store.previewThemeId ?? store.themeId + void load(id).then((theme) => { + if (!theme) return + if ((store.previewThemeId ?? store.themeId) !== id) return + if (store.previewScheme !== scheme) return + applyTheme(theme, id, mode, scheme) + }) + }, + commitPreview: () => { + if (store.previewThemeId) { + setTheme(store.previewThemeId) + } + if (store.previewScheme) { + setColorScheme(store.previewScheme) + } + setStore("previewThemeId", null) + setStore("previewScheme", null) + }, + cancelPreview: () => { + setStore("previewThemeId", null) + setStore("previewScheme", null) + void load(store.themeId).then((theme) => { + if (!theme) return + applyTheme(theme, store.themeId, store.mode, store.colorScheme) + }) + }, + } + }, +}) diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts new file mode 100644 index 0000000000000000000000000000000000000000..c141989558121ae60b94dc86b3834c169c1ef759 --- /dev/null +++ b/packages/ui/src/theme/default-themes.ts @@ -0,0 +1,116 @@ +import type { DesktopTheme } from "./types" +import oc2ThemeJson from "./themes/oc-2.json" +import amoledThemeJson from "./themes/amoled.json" +import auraThemeJson from "./themes/aura.json" +import ayuThemeJson from "./themes/ayu.json" +import carbonfoxThemeJson from "./themes/carbonfox.json" +import catppuccinThemeJson from "./themes/catppuccin.json" +import catppuccinFrappeThemeJson from "./themes/catppuccin-frappe.json" +import catppuccinMacchiatoThemeJson from "./themes/catppuccin-macchiato.json" +import cobalt2ThemeJson from "./themes/cobalt2.json" +import cursorThemeJson from "./themes/cursor.json" +import draculaThemeJson from "./themes/dracula.json" +import everforestThemeJson from "./themes/everforest.json" +import flexokiThemeJson from "./themes/flexoki.json" +import githubThemeJson from "./themes/github.json" +import gruvboxThemeJson from "./themes/gruvbox.json" +import kanagawaThemeJson from "./themes/kanagawa.json" +import lucentOrngThemeJson from "./themes/lucent-orng.json" +import materialThemeJson from "./themes/material.json" +import matrixThemeJson from "./themes/matrix.json" +import mercuryThemeJson from "./themes/mercury.json" +import monokaiThemeJson from "./themes/monokai.json" +import nightowlThemeJson from "./themes/nightowl.json" +import nordThemeJson from "./themes/nord.json" +import oneDarkThemeJson from "./themes/one-dark.json" +import oneDarkProThemeJson from "./themes/onedarkpro.json" +import opencodeThemeJson from "./themes/opencode.json" +import orngThemeJson from "./themes/orng.json" +import osakaJadeThemeJson from "./themes/osaka-jade.json" +import palenightThemeJson from "./themes/palenight.json" +import rosepineThemeJson from "./themes/rosepine.json" +import shadesOfPurpleThemeJson from "./themes/shadesofpurple.json" +import solarizedThemeJson from "./themes/solarized.json" +import synthwave84ThemeJson from "./themes/synthwave84.json" +import tokyonightThemeJson from "./themes/tokyonight.json" +import vercelThemeJson from "./themes/vercel.json" +import vesperThemeJson from "./themes/vesper.json" +import zenburnThemeJson from "./themes/zenburn.json" + +export const oc2Theme = oc2ThemeJson as DesktopTheme +export const amoledTheme = amoledThemeJson as DesktopTheme +export const auraTheme = auraThemeJson as DesktopTheme +export const ayuTheme = ayuThemeJson as DesktopTheme +export const carbonfoxTheme = carbonfoxThemeJson as DesktopTheme +export const catppuccinTheme = catppuccinThemeJson as DesktopTheme +export const catppuccinFrappeTheme = catppuccinFrappeThemeJson as DesktopTheme +export const catppuccinMacchiatoTheme = catppuccinMacchiatoThemeJson as DesktopTheme +export const cobalt2Theme = cobalt2ThemeJson as DesktopTheme +export const cursorTheme = cursorThemeJson as DesktopTheme +export const draculaTheme = draculaThemeJson as DesktopTheme +export const everforestTheme = everforestThemeJson as DesktopTheme +export const flexokiTheme = flexokiThemeJson as DesktopTheme +export const githubTheme = githubThemeJson as DesktopTheme +export const gruvboxTheme = gruvboxThemeJson as DesktopTheme +export const kanagawaTheme = kanagawaThemeJson as DesktopTheme +export const lucentOrngTheme = lucentOrngThemeJson as DesktopTheme +export const materialTheme = materialThemeJson as DesktopTheme +export const matrixTheme = matrixThemeJson as DesktopTheme +export const mercuryTheme = mercuryThemeJson as DesktopTheme +export const monokaiTheme = monokaiThemeJson as DesktopTheme +export const nightowlTheme = nightowlThemeJson as DesktopTheme +export const nordTheme = nordThemeJson as DesktopTheme +export const oneDarkTheme = oneDarkThemeJson as DesktopTheme +export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme +export const opencodeTheme = opencodeThemeJson as DesktopTheme +export const orngTheme = orngThemeJson as DesktopTheme +export const osakaJadeTheme = osakaJadeThemeJson as DesktopTheme +export const palenightTheme = palenightThemeJson as DesktopTheme +export const rosepineTheme = rosepineThemeJson as DesktopTheme +export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme +export const solarizedTheme = solarizedThemeJson as DesktopTheme +export const synthwave84Theme = synthwave84ThemeJson as DesktopTheme +export const tokyonightTheme = tokyonightThemeJson as DesktopTheme +export const vercelTheme = vercelThemeJson as DesktopTheme +export const vesperTheme = vesperThemeJson as DesktopTheme +export const zenburnTheme = zenburnThemeJson as DesktopTheme + +export const DEFAULT_THEMES: Record = { + "oc-2": oc2Theme, + amoled: amoledTheme, + aura: auraTheme, + ayu: ayuTheme, + carbonfox: carbonfoxTheme, + catppuccin: catppuccinTheme, + "catppuccin-frappe": catppuccinFrappeTheme, + "catppuccin-macchiato": catppuccinMacchiatoTheme, + cobalt2: cobalt2Theme, + cursor: cursorTheme, + dracula: draculaTheme, + everforest: everforestTheme, + flexoki: flexokiTheme, + github: githubTheme, + gruvbox: gruvboxTheme, + kanagawa: kanagawaTheme, + "lucent-orng": lucentOrngTheme, + material: materialTheme, + matrix: matrixTheme, + mercury: mercuryTheme, + monokai: monokaiTheme, + nightowl: nightowlTheme, + nord: nordTheme, + "one-dark": oneDarkTheme, + onedarkpro: oneDarkProTheme, + opencode: opencodeTheme, + orng: orngTheme, + "osaka-jade": osakaJadeTheme, + palenight: palenightTheme, + rosepine: rosepineTheme, + shadesofpurple: shadesOfPurpleTheme, + solarized: solarizedTheme, + synthwave84: synthwave84Theme, + tokyonight: tokyonightTheme, + vercel: vercelTheme, + vesper: vesperTheme, + zenburn: zenburnTheme, +} diff --git a/packages/ui/src/theme/desktop-theme.schema.json b/packages/ui/src/theme/desktop-theme.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..368f337ba1c2ddd29fdf6b0cdf188984c7fade0c --- /dev/null +++ b/packages/ui/src/theme/desktop-theme.schema.json @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://opencode.ai/desktop-theme.json", + "title": "OpenCode Desktop Theme", + "description": "A theme definition for the OpenCode desktop application", + "type": "object", + "required": ["name", "id", "light", "dark"], + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema reference" + }, + "name": { + "type": "string", + "description": "Human-readable theme name" + }, + "id": { + "type": "string", + "description": "Unique theme identifier (slug)", + "pattern": "^[a-z0-9-]+$" + }, + "light": { + "$ref": "#/definitions/ThemeVariant", + "description": "Light mode color variant" + }, + "dark": { + "$ref": "#/definitions/ThemeVariant", + "description": "Dark mode color variant" + } + }, + "definitions": { + "HexColor": { + "type": "string", + "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$", + "description": "A hex color value like #fff, #ffff, #ffffff, or #ffffffff" + }, + "ColorValue": { + "type": "string", + "pattern": "^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|var\\(--[a-z0-9-]+\\))$", + "description": "Either a hex color value (#rgb/#rgba/#rrggbb/#rrggbbaa) or a CSS variable reference" + }, + "ThemeSeedColors": { + "type": "object", + "description": "The legacy semantic seed set used to generate a theme", + "additionalProperties": false, + "required": ["neutral", "primary", "success", "warning", "error", "info", "interactive", "diffAdd", "diffDelete"], + "properties": { + "neutral": { + "$ref": "#/definitions/HexColor", + "description": "Base neutral color for generating the gray scale" + }, + "primary": { + "$ref": "#/definitions/HexColor", + "description": "Primary brand/accent color" + }, + "success": { + "$ref": "#/definitions/HexColor", + "description": "Success state color (typically green)" + }, + "warning": { + "$ref": "#/definitions/HexColor", + "description": "Warning state color (typically yellow/orange)" + }, + "error": { + "$ref": "#/definitions/HexColor", + "description": "Error/critical state color (typically red)" + }, + "info": { + "$ref": "#/definitions/HexColor", + "description": "Informational state color (typically purple/blue)" + }, + "interactive": { + "$ref": "#/definitions/HexColor", + "description": "Interactive element color (links, buttons)" + }, + "diffAdd": { + "$ref": "#/definitions/HexColor", + "description": "Color for diff additions" + }, + "diffDelete": { + "$ref": "#/definitions/HexColor", + "description": "Color for diff deletions" + } + } + }, + "ThemePaletteColors": { + "type": "object", + "description": "A compact semantic palette used to derive the full theme programmatically", + "additionalProperties": false, + "required": ["neutral", "ink", "primary", "success", "warning", "error", "info"], + "properties": { + "neutral": { + "$ref": "#/definitions/HexColor", + "description": "Base neutral color for generating the gray scale" + }, + "ink": { + "$ref": "#/definitions/HexColor", + "description": "Foreground or chrome color used to derive text and border tones" + }, + "primary": { + "$ref": "#/definitions/HexColor", + "description": "Primary brand color used for brand surfaces and strong emphasis" + }, + "success": { + "$ref": "#/definitions/HexColor", + "description": "Success state color" + }, + "warning": { + "$ref": "#/definitions/HexColor", + "description": "Warning state color" + }, + "error": { + "$ref": "#/definitions/HexColor", + "description": "Error or critical state color" + }, + "info": { + "$ref": "#/definitions/HexColor", + "description": "Informational state color" + }, + "accent": { + "$ref": "#/definitions/HexColor", + "description": "Optional extra expressive accent for syntax and rich content" + }, + "interactive": { + "$ref": "#/definitions/HexColor", + "description": "Optional dedicated interactive color; falls back to primary" + }, + "diffAdd": { + "$ref": "#/definitions/HexColor", + "description": "Optional diff-add seed; falls back to a softened success color" + }, + "diffDelete": { + "$ref": "#/definitions/HexColor", + "description": "Optional diff-delete seed; falls back to error" + } + } + }, + "ThemeVariant": { + "type": "object", + "description": "A theme variant (light or dark) with either a compact palette or legacy seeds and optional overrides", + "oneOf": [{ "required": ["seeds"] }, { "required": ["palette"] }], + "properties": { + "seeds": { + "$ref": "#/definitions/ThemeSeedColors", + "description": "Legacy seed colors used to generate the full palette" + }, + "palette": { + "$ref": "#/definitions/ThemePaletteColors", + "description": "Compact palette used to derive the full token set" + }, + "overrides": { + "type": "object", + "description": "Optional direct overrides for any CSS variable (without -- prefix)", + "additionalProperties": { + "$ref": "#/definitions/ColorValue" + } + }, + "v2Overrides": { + "type": "object", + "description": "Optional direct overrides for any v2 CSS variable (without -- prefix)", + "additionalProperties": { + "type": "string" + } + } + } + } + } +} diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..deb66b6829942400d975cf293e99ebe0d30309c5 --- /dev/null +++ b/packages/ui/src/theme/index.ts @@ -0,0 +1,78 @@ +export type { + DesktopTheme, + ThemePaletteColors, + ThemeSeedColors, + ThemeVariant, + HexColor, + OklchColor, + ResolvedTheme, + ColorValue, + CssVarRef, + V2ColorValue, + ResolvedV2Theme, +} from "./types" + +export { + hexToRgb, + rgbToHex, + hexToOklch, + oklchToHex, + rgbToOklch, + oklchToRgb, + generateScale, + generateNeutralScale, + generateAlphaScale, + fitOklch, + blend, + mixColors, + shift, + lighten, + darken, + withAlpha, +} from "./color" + +export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve" +export { resolveThemeVariantV2, resolveThemeV2, themeV2ToCss, generateV2Primitives } from "./v2/resolve" +export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader" +export { ThemeProvider, useTheme, type ColorScheme } from "./context" + +export { + DEFAULT_THEMES, + oc2Theme, + amoledTheme, + auraTheme, + ayuTheme, + carbonfoxTheme, + catppuccinTheme, + catppuccinFrappeTheme, + catppuccinMacchiatoTheme, + cobalt2Theme, + cursorTheme, + draculaTheme, + everforestTheme, + flexokiTheme, + githubTheme, + gruvboxTheme, + kanagawaTheme, + lucentOrngTheme, + materialTheme, + matrixTheme, + mercuryTheme, + monokaiTheme, + nightowlTheme, + nordTheme, + oneDarkTheme, + oneDarkProTheme, + opencodeTheme, + orngTheme, + osakaJadeTheme, + palenightTheme, + rosepineTheme, + shadesOfPurpleTheme, + solarizedTheme, + synthwave84Theme, + tokyonightTheme, + vercelTheme, + vesperTheme, + zenburnTheme, +} from "./default-themes" diff --git a/packages/ui/src/theme/loader.ts b/packages/ui/src/theme/loader.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddf9ca09eb41daf0bc496bf704a674ab0cb491c2 --- /dev/null +++ b/packages/ui/src/theme/loader.ts @@ -0,0 +1,112 @@ +import type { DesktopTheme, ResolvedTheme, ResolvedV2Theme } from "./types" +import { resolveThemeVariant, themeToCss } from "./resolve" +import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve" + +let activeTheme: DesktopTheme | null = null +const THEME_STYLE_ID = "opencode-theme" + +function ensureLoaderStyleElement(): HTMLStyleElement { + const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null + if (existing) { + return existing + } + const element = document.createElement("style") + element.id = THEME_STYLE_ID + document.head.appendChild(element) + return element +} + +export function applyTheme(theme: DesktopTheme, themeId?: string): void { + activeTheme = theme + const lightTokens = resolveThemeVariant(theme.light, false) + const darkTokens = resolveThemeVariant(theme.dark, true) + const lightV2Tokens = resolveThemeVariantV2(theme.light, false) + const darkV2Tokens = resolveThemeVariantV2(theme.dark, true) + const targetThemeId = themeId ?? theme.id + const css = buildThemeCss(lightTokens, darkTokens, lightV2Tokens, darkV2Tokens, targetThemeId) + const themeStyleElement = ensureLoaderStyleElement() + themeStyleElement.textContent = css + document.documentElement.setAttribute("data-theme", targetThemeId) +} + +function buildThemeCss( + light: ResolvedTheme, + dark: ResolvedTheme, + lightV2: ResolvedV2Theme, + darkV2: ResolvedV2Theme, + themeId: string, +): string { + const isDefaultTheme = themeId === "oc-2" + const lightCss = `${themeToCss(light)}\n ${themeV2ToCss(lightV2)}` + const darkCss = `${themeToCss(dark)}\n ${themeV2ToCss(darkV2)}` + + if (isDefaultTheme) { + return ` +:root { + color-scheme: light; + --text-mix-blend-mode: multiply; + + ${lightCss} + + @media (prefers-color-scheme: dark) { + color-scheme: dark; + --text-mix-blend-mode: plus-lighter; + + ${darkCss} + } +} +` + } + + return ` +html[data-theme="${themeId}"] { + color-scheme: light; + --text-mix-blend-mode: multiply; + + ${lightCss} + + @media (prefers-color-scheme: dark) { + color-scheme: dark; + --text-mix-blend-mode: plus-lighter; + + ${darkCss} + } +} +` +} + +export async function loadThemeFromUrl(url: string): Promise { + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Failed to load theme from ${url}: ${response.statusText}`) + } + return response.json() +} + +export function getActiveTheme(): DesktopTheme | null { + const activeId = document.documentElement.getAttribute("data-theme") + if (!activeId) { + return null + } + if (activeTheme?.id === activeId) { + return activeTheme + } + return null +} + +export function removeTheme(): void { + activeTheme = null + const existingElement = document.getElementById(THEME_STYLE_ID) + if (existingElement) { + existingElement.remove() + } + document.documentElement.removeAttribute("data-theme") +} + +export function setColorScheme(scheme: "light" | "dark" | "auto"): void { + if (scheme === "auto") { + document.documentElement.style.removeProperty("color-scheme") + } else { + document.documentElement.style.setProperty("color-scheme", scheme) + } +} diff --git a/packages/ui/src/theme/resolve.ts b/packages/ui/src/theme/resolve.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cf6711e69ab61e0650d828875db7ec306357e96 --- /dev/null +++ b/packages/ui/src/theme/resolve.ts @@ -0,0 +1,540 @@ +import type { ColorValue, DesktopTheme, HexColor, ResolvedTheme, ThemeVariant } from "./types" +import { blend, generateNeutralScale, generateScale, hexToOklch, hexToRgb, shift, withAlpha } from "./color" + +export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): ResolvedTheme { + const colors = getColors(variant) + const { overrides = {} } = variant + + const neutral = generateNeutralScale(colors.neutral, isDark, colors.ink) + const primary = generateScale(colors.primary, isDark) + const accent = generateScale(colors.accent, isDark) + const success = generateScale(colors.success, isDark) + const warning = generateScale(colors.warning, isDark) + const error = generateScale(colors.error, isDark) + const info = generateScale(colors.info, isDark) + const interactive = generateScale(colors.interactive, isDark) + const amber = generateScale( + shift(colors.warning, isDark ? { h: -16, l: -0.058, c: 1.14 } : { h: -22, l: -0.082, c: 0.94 }), + isDark, + ) + const blue = generateScale(shift(colors.interactive, { h: -12, l: 0.128, c: 1.12 }), isDark) + const diffAdd = generateScale( + colors.diffAdd ?? shift(colors.success, { c: isDark ? 0.7 : 0.55, l: isDark ? -0.18 : 0.14 }), + isDark, + ) + const diffDelete = generateScale( + colors.diffDelete ?? shift(colors.error, { c: isDark ? 0.82 : 0.7, l: isDark ? -0.08 : 0.08 }), + isDark, + ) + const ink = colors.ink ?? colors.neutral + const tint = colors.compact ? hexToOklch(ink) : undefined + const body = tint + ? shift(ink, { + l: isDark ? Math.max(0, 0.88 - tint.l) * 0.4 : -Math.max(0, tint.l - 0.18) * 0.24, + c: isDark ? 1.04 : 1.02, + }) + : undefined + const backgroundOverride = overrides["background-base"] + const backgroundHex = getHex(backgroundOverride) + const overlay = Boolean(backgroundOverride) && !backgroundHex + const content = (seed: HexColor, scale: HexColor[]) => { + const base = hexToOklch(seed) + const value = isDark ? (base.l > 0.84 ? shift(seed, { c: 1.18 }) : scale[10]) : scale[10] + return shift(value, { l: isDark ? 0.034 : -0.024, c: isDark ? 1.3 : 1.18 }) + } + const modified = () => { + if (!colors.compact) return isDark ? "#ffba92" : "#FF8C00" + const warningHue = hexToOklch(colors.warning).h + const deleteHue = hexToOklch(colors.diffDelete ?? colors.error).h + const delta = Math.abs(((((deleteHue - warningHue) % 360) + 540) % 360) - 180) + if (delta < 48) return isDark ? "#ffba92" : "#FF8C00" + return content(colors.warning, warning) + } + const surface = ( + seed: HexColor, + alpha: { base: number; weak: number; weaker: number; strong: number; stronger: number }, + ) => { + const base = alphaTone(seed, alpha.base) + return { + base, + weak: alphaTone(seed, alpha.weak), + weaker: alphaTone(seed, alpha.weaker), + strong: alphaTone(seed, alpha.strong), + stronger: alphaTone(seed, alpha.stronger), + } + } + const background = backgroundHex ?? neutral[0] + const alphaTone = (color: HexColor, alpha: number) => + overlay ? (withAlpha(color, alpha) as ColorValue) : blend(color, background, alpha) + const borderTone = (light: number, dark: number) => + alphaTone(ink, isDark ? Math.min(1, dark + 0.024 + (colors.compact ? 0.08 : 0)) : Math.min(1, light + 0.024)) + const diffHiddenSurface = surface( + isDark ? shift(colors.interactive, { c: 0.55, l: 0 }) : shift(colors.interactive, { c: 0.45, l: 0.08 }), + isDark + ? { base: 0.14, weak: 0.08, weaker: 0.18, strong: 0.26, stronger: 0.42 } + : { base: 0.12, weak: 0.08, weaker: 0.16, strong: 0.24, stronger: 0.36 }, + ) + + const neutralAlpha = generateNeutralAlphaScale(neutral, isDark) + const brandb = primary[8] + const brandh = primary[9] + const interb = interactive[isDark ? 6 : 4] + const interh = interactive[isDark ? 7 : 5] + const interw = interactive[isDark ? 5 : 3] + const succb = success[isDark ? 6 : 4] + const succw = success[isDark ? 5 : 3] + const succs = success[10] + const warnb = warning[isDark ? 6 : 4] + const warnw = warning[isDark ? 5 : 3] + const warns = warning[10] + const critb = error[isDark ? 6 : 4] + const critw = error[isDark ? 5 : 3] + const crits = error[10] + const infob = info[isDark ? 6 : 4] + const infow = info[isDark ? 5 : 3] + const infos = info[10] + const lum = (hex: HexColor) => { + const rgb = hexToRgb(hex) + const lift = (v: number) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)) + return 0.2126 * lift(rgb.r) + 0.7152 * lift(rgb.g) + 0.0722 * lift(rgb.b) + } + const hit = (a: HexColor, b: HexColor) => { + const x = lum(a) + const y = lum(b) + const light = Math.max(x, y) + const dark = Math.min(x, y) + return (light + 0.05) / (dark + 0.05) + } + const on = (fill: HexColor) => { + const light = "#ffffff" as HexColor + const dark = "#000000" as HexColor + return hit(light, fill) > hit(dark, fill) ? light : dark + } + + const tokens: ResolvedTheme = {} + + tokens["background-base"] = neutral[0] + tokens["background-weak"] = neutral[2] + tokens["background-strong"] = neutral[0] + tokens["background-stronger"] = isDark ? neutral[1] : "#fcfcfc" + + tokens["surface-base"] = neutralAlpha[1] + tokens["base"] = neutralAlpha[1] + tokens["surface-base-hover"] = neutralAlpha[2] + tokens["surface-base-active"] = neutralAlpha[2] + tokens["surface-base-interactive-active"] = withAlpha(interactive[2], 0.3) as ColorValue + tokens["base2"] = neutralAlpha[1] + tokens["base3"] = neutralAlpha[1] + tokens["surface-inset-base"] = neutralAlpha[1] + tokens["surface-inset-base-hover"] = neutralAlpha[2] + tokens["surface-inset-strong"] = isDark + ? (withAlpha(neutral[0], 0.5) as ColorValue) + : (withAlpha(neutral[3], 0.09) as ColorValue) + tokens["surface-inset-strong-hover"] = tokens["surface-inset-strong"] + tokens["surface-raised-base"] = neutralAlpha[0] + tokens["surface-float-base"] = isDark ? neutral[1] : neutral[11] + tokens["surface-float-base-hover"] = isDark ? neutral[2] : neutral[10] + tokens["surface-raised-base-hover"] = neutralAlpha[1] + tokens["surface-raised-base-active"] = neutralAlpha[2] + tokens["surface-raised-strong"] = isDark ? neutralAlpha[3] : neutral[0] + tokens["surface-raised-strong-hover"] = isDark ? neutralAlpha[5] : "#ffffff" + tokens["surface-raised-stronger"] = isDark ? neutralAlpha[5] : "#ffffff" + tokens["surface-raised-stronger-hover"] = isDark ? neutralAlpha[6] : "#ffffff" + tokens["surface-weak"] = neutralAlpha[2] + tokens["surface-weaker"] = neutralAlpha[3] + tokens["surface-strong"] = isDark ? neutralAlpha[6] : "#ffffff" + tokens["surface-raised-stronger-non-alpha"] = isDark ? neutral[2] : "#ffffff" + + tokens["surface-brand-base"] = brandb + tokens["surface-brand-hover"] = brandh + + tokens["surface-interactive-base"] = interb + tokens["surface-interactive-hover"] = interh + tokens["surface-interactive-weak"] = interw + tokens["surface-interactive-weak-hover"] = interb + + tokens["surface-success-base"] = succb + tokens["surface-success-weak"] = succw + tokens["surface-success-strong"] = succs + tokens["surface-warning-base"] = warnb + tokens["surface-warning-weak"] = warnw + tokens["surface-warning-strong"] = warns + tokens["surface-critical-base"] = critb + tokens["surface-critical-weak"] = critw + tokens["surface-critical-strong"] = crits + tokens["surface-info-base"] = infob + tokens["surface-info-weak"] = infow + tokens["surface-info-strong"] = infos + + tokens["surface-diff-unchanged-base"] = isDark ? neutral[0] : "#ffffff00" + tokens["surface-diff-skip-base"] = isDark ? neutralAlpha[0] : neutral[1] + tokens["surface-diff-hidden-base"] = diffHiddenSurface.base + tokens["surface-diff-hidden-weak"] = diffHiddenSurface.weak + tokens["surface-diff-hidden-weaker"] = diffHiddenSurface.weaker + tokens["surface-diff-hidden-strong"] = diffHiddenSurface.strong + tokens["surface-diff-hidden-stronger"] = diffHiddenSurface.stronger + tokens["surface-diff-add-base"] = diffAdd[2] + tokens["surface-diff-add-weak"] = diffAdd[isDark ? 3 : 1] + tokens["surface-diff-add-weaker"] = diffAdd[isDark ? 2 : 0] + tokens["surface-diff-add-strong"] = diffAdd[4] + tokens["surface-diff-add-stronger"] = diffAdd[isDark ? 10 : 8] + tokens["surface-diff-delete-base"] = diffDelete[2] + tokens["surface-diff-delete-weak"] = diffDelete[isDark ? 3 : 1] + tokens["surface-diff-delete-weaker"] = diffDelete[isDark ? 2 : 0] + tokens["surface-diff-delete-strong"] = diffDelete[isDark ? 4 : 5] + tokens["surface-diff-delete-stronger"] = diffDelete[isDark ? 10 : 8] + + tokens["input-base"] = isDark ? neutral[1] : neutral[0] + tokens["input-hover"] = isDark ? neutral[2] : neutral[1] + tokens["input-active"] = isDark ? interactive[6] : interactive[0] + tokens["input-selected"] = isDark ? interactive[7] : interactive[3] + tokens["input-focus"] = isDark ? interactive[6] : interactive[0] + tokens["input-disabled"] = neutral[3] + + tokens["text-base"] = colors.compact ? (body as HexColor) : neutral[10] + tokens["text-weak"] = colors.compact ? shift(body as HexColor, { l: isDark ? -0.11 : 0.11, c: 0.9 }) : neutral[8] + tokens["text-weaker"] = colors.compact + ? shift(body as HexColor, { l: isDark ? -0.2 : 0.21, c: isDark ? 0.78 : 0.72 }) + : neutral[7] + tokens["text-strong"] = colors.compact + ? isDark + ? blend("#ffffff", body as HexColor, 0.9) + : shift(body as HexColor, { l: -0.07, c: 1.04 }) + : neutral[11] + tokens["text-invert-base"] = isDark ? neutral[10] : neutral[1] + tokens["text-invert-weak"] = isDark ? neutral[8] : neutral[2] + tokens["text-invert-weaker"] = isDark ? neutral[7] : neutral[3] + tokens["text-invert-strong"] = isDark ? neutral[11] : neutral[0] + tokens["text-interactive-base"] = interactive[isDark ? 10 : 9] + tokens["text-on-brand-base"] = on(brandb) + tokens["text-on-interactive-base"] = on(interb) + tokens["text-on-interactive-weak"] = on(interb) + tokens["text-on-success-base"] = on(succb) + tokens["text-on-critical-base"] = on(critb) + tokens["text-on-critical-weak"] = on(critb) + tokens["text-on-critical-strong"] = on(crits) + tokens["text-on-warning-base"] = on(warnb) + tokens["text-on-info-base"] = on(infob) + tokens["text-diff-add-base"] = diffAdd[10] + tokens["text-diff-delete-base"] = diffDelete[9] + tokens["text-diff-delete-strong"] = diffDelete[11] + tokens["text-diff-add-strong"] = diffAdd[isDark ? 7 : 11] + tokens["text-on-info-weak"] = on(infob) + tokens["text-on-info-strong"] = on(infos) + tokens["text-on-warning-weak"] = on(warnb) + tokens["text-on-warning-strong"] = on(warns) + tokens["text-on-success-weak"] = on(succb) + tokens["text-on-success-strong"] = on(succs) + tokens["text-on-brand-weak"] = on(brandb) + tokens["text-on-brand-weaker"] = on(brandb) + tokens["text-on-brand-strong"] = on(brandh) + + tokens["button-primary-base"] = neutral[11] + tokens["button-secondary-base"] = isDark ? neutral[2] : neutral[0] + tokens["button-secondary-hover"] = isDark ? neutral[3] : neutral[1] + tokens["button-ghost-hover"] = neutralAlpha[1] + tokens["button-ghost-hover2"] = neutralAlpha[2] + + tokens["border-base"] = colors.compact ? borderTone(0.22, 0.16) : neutralAlpha[6] + tokens["border-hover"] = colors.compact ? borderTone(0.28, 0.2) : neutralAlpha[7] + tokens["border-active"] = colors.compact ? borderTone(0.34, 0.24) : neutralAlpha[8] + tokens["border-selected"] = withAlpha(interactive[8], isDark ? 0.9 : 0.99) as ColorValue + tokens["border-disabled"] = colors.compact ? borderTone(0.18, 0.12) : neutralAlpha[7] + tokens["border-focus"] = colors.compact ? borderTone(0.34, 0.24) : neutralAlpha[8] + tokens["border-weak-base"] = colors.compact ? borderTone(0.1, 0.08) : neutralAlpha[isDark ? 5 : 4] + tokens["border-strong-base"] = colors.compact ? borderTone(0.34, 0.24) : neutralAlpha[isDark ? 7 : 6] + tokens["border-strong-hover"] = colors.compact ? borderTone(0.4, 0.28) : neutralAlpha[7] + tokens["border-strong-active"] = colors.compact ? borderTone(0.46, 0.32) : neutralAlpha[isDark ? 7 : 6] + tokens["border-strong-selected"] = withAlpha(interactive[5], 0.6) as ColorValue + tokens["border-strong-disabled"] = colors.compact ? borderTone(0.14, 0.1) : neutralAlpha[5] + tokens["border-strong-focus"] = colors.compact ? borderTone(0.46, 0.32) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weak-hover"] = colors.compact ? borderTone(0.16, 0.12) : neutralAlpha[isDark ? 6 : 5] + tokens["border-weak-active"] = colors.compact ? borderTone(0.22, 0.16) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weak-selected"] = withAlpha(interactive[4], isDark ? 0.6 : 0.5) as ColorValue + tokens["border-weak-disabled"] = colors.compact ? borderTone(0.08, 0.06) : neutralAlpha[5] + tokens["border-weak-focus"] = colors.compact ? borderTone(0.22, 0.16) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weaker-base"] = colors.compact ? borderTone(0.06, 0.04) : neutralAlpha[2] + + tokens["border-interactive-base"] = interactive[6] + tokens["border-interactive-hover"] = interactive[7] + tokens["border-interactive-active"] = interactive[8] + tokens["border-interactive-selected"] = interactive[8] + tokens["border-interactive-disabled"] = neutral[7] + tokens["border-interactive-focus"] = interactive[8] + + tokens["border-success-base"] = success[isDark ? 6 : 6] + tokens["border-success-hover"] = success[isDark ? 7 : 7] + tokens["border-success-selected"] = success[8] + tokens["border-warning-base"] = warning[isDark ? 6 : 6] + tokens["border-warning-hover"] = warning[isDark ? 7 : 7] + tokens["border-warning-selected"] = warning[8] + tokens["border-critical-base"] = error[isDark ? 6 : 6] + tokens["border-critical-hover"] = error[isDark ? 7 : 7] + tokens["border-critical-selected"] = error[8] + tokens["border-info-base"] = info[isDark ? 6 : 6] + tokens["border-info-hover"] = info[isDark ? 7 : 7] + tokens["border-info-selected"] = info[8] + tokens["border-color"] = "#ffffff" + + tokens["icon-base"] = colors.compact && !isDark ? tokens["text-weak"] : neutral[isDark ? 9 : 8] + tokens["icon-hover"] = colors.compact && !isDark ? tokens["text-base"] : neutral[10] + tokens["icon-active"] = colors.compact && !isDark ? tokens["text-strong"] : neutral[11] + tokens["icon-selected"] = colors.compact && !isDark ? tokens["text-strong"] : neutral[11] + tokens["icon-disabled"] = neutral[isDark ? 6 : 7] + tokens["icon-focus"] = colors.compact && !isDark ? tokens["text-strong"] : neutral[11] + tokens["icon-invert-base"] = isDark ? neutral[0] : "#ffffff" + tokens["icon-weak-base"] = neutral[isDark ? 5 : 6] + tokens["icon-weak-hover"] = neutral[isDark ? 11 : 7] + tokens["icon-weak-active"] = neutral[8] + tokens["icon-weak-selected"] = neutral[isDark ? 8 : 9] + tokens["icon-weak-disabled"] = neutral[isDark ? 3 : 5] + tokens["icon-weak-focus"] = neutral[8] + tokens["icon-strong-base"] = neutral[11] + tokens["icon-strong-hover"] = isDark ? "#f6f3f3" : "#151313" + tokens["icon-strong-active"] = isDark ? "#fcfcfc" : "#020202" + tokens["icon-strong-selected"] = isDark ? "#fdfcfc" : "#020202" + tokens["icon-strong-disabled"] = neutral[7] + tokens["icon-strong-focus"] = isDark ? "#fdfcfc" : "#020202" + tokens["icon-brand-base"] = isDark ? "#ffffff" : neutral[11] + tokens["icon-interactive-base"] = interactive[8] + tokens["icon-success-base"] = success[isDark ? 8 : 6] + tokens["icon-success-hover"] = success[9] + tokens["icon-success-active"] = success[10] + tokens["icon-warning-base"] = amber[isDark ? 8 : 6] + tokens["icon-warning-hover"] = amber[9] + tokens["icon-warning-active"] = amber[10] + tokens["icon-critical-base"] = error[isDark ? 8 : 9] + tokens["icon-critical-hover"] = error[9] + tokens["icon-critical-active"] = error[10] + tokens["icon-info-base"] = info[isDark ? 8 : 6] + tokens["icon-info-hover"] = info[isDark ? 9 : 7] + tokens["icon-info-active"] = info[10] + tokens["icon-on-brand-base"] = on(brandb) + tokens["icon-on-brand-hover"] = on(brandh) + tokens["icon-on-brand-selected"] = on(brandh) + tokens["icon-on-interactive-base"] = on(interb) + + tokens["icon-agent-plan-base"] = info[8] + tokens["icon-agent-docs-base"] = amber[8] + tokens["icon-agent-ask-base"] = blue[8] + tokens["icon-agent-build-base"] = interactive[isDark ? 10 : 8] + + tokens["icon-on-success-base"] = on(succb) + tokens["icon-on-success-hover"] = on(succs) + tokens["icon-on-success-selected"] = on(succs) + tokens["icon-on-warning-base"] = on(warnb) + tokens["icon-on-warning-hover"] = on(warns) + tokens["icon-on-warning-selected"] = on(warns) + tokens["icon-on-critical-base"] = on(critb) + tokens["icon-on-critical-hover"] = on(crits) + tokens["icon-on-critical-selected"] = on(crits) + tokens["icon-on-info-base"] = on(infob) + tokens["icon-on-info-hover"] = on(infos) + tokens["icon-on-info-selected"] = on(infos) + + tokens["icon-diff-add-base"] = diffAdd[10] + tokens["icon-diff-add-hover"] = diffAdd[isDark ? 9 : 11] + tokens["icon-diff-add-active"] = diffAdd[isDark ? 10 : 11] + tokens["icon-diff-delete-base"] = diffDelete[9] + tokens["icon-diff-delete-hover"] = diffDelete[isDark ? 10 : 10] + tokens["icon-diff-modified-base"] = modified() + + if (colors.compact) { + tokens["syntax-comment"] = "var(--text-weak)" + tokens["syntax-regexp"] = "var(--text-base)" + tokens["syntax-string"] = content(colors.success, success) + tokens["syntax-keyword"] = content(colors.accent, accent) + tokens["syntax-primitive"] = content(colors.primary, primary) + tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-variable"] = "var(--text-strong)" + tokens["syntax-property"] = content(colors.info, info) + tokens["syntax-type"] = content(colors.warning, warning) + tokens["syntax-constant"] = content(colors.accent, accent) + tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-object"] = "var(--text-strong)" + tokens["syntax-success"] = success[10] + tokens["syntax-warning"] = amber[10] + tokens["syntax-critical"] = error[10] + tokens["syntax-info"] = content(colors.info, info) + tokens["syntax-diff-add"] = diffAdd[10] + tokens["syntax-diff-delete"] = diffDelete[10] + tokens["syntax-diff-unknown"] = "#ff0000" + + tokens["markdown-heading"] = content(colors.primary, primary) + tokens["markdown-text"] = tokens["text-base"] + tokens["markdown-link"] = content(colors.interactive, interactive) + tokens["markdown-link-text"] = content(colors.info, info) + tokens["markdown-code"] = content(colors.success, success) + tokens["markdown-block-quote"] = content(colors.warning, warning) + tokens["markdown-emph"] = content(colors.warning, warning) + tokens["markdown-strong"] = content(colors.accent, accent) + tokens["markdown-horizontal-rule"] = tokens["border-base"] + tokens["markdown-list-item"] = content(colors.interactive, interactive) + tokens["markdown-list-enumeration"] = content(colors.info, info) + tokens["markdown-image"] = content(colors.interactive, interactive) + tokens["markdown-image-text"] = content(colors.info, info) + tokens["markdown-code-block"] = tokens["text-base"] + } + + if (!colors.compact) { + tokens["syntax-comment"] = "var(--text-weak)" + tokens["syntax-regexp"] = "var(--text-base)" + tokens["syntax-string"] = isDark ? "#00ceb9" : "#006656" + tokens["syntax-keyword"] = "var(--text-weak)" + tokens["syntax-primitive"] = isDark ? "#ffba92" : "#fb4804" + tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-variable"] = "var(--text-strong)" + tokens["syntax-property"] = isDark ? "#ff9ae2" : "#ed6dc8" + tokens["syntax-type"] = isDark ? "#ecf58c" : "#596600" + tokens["syntax-constant"] = isDark ? "#93e9f6" : "#007b80" + tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-object"] = "var(--text-strong)" + tokens["syntax-success"] = success[10] + tokens["syntax-warning"] = amber[10] + tokens["syntax-critical"] = error[10] + tokens["syntax-info"] = isDark ? "#93e9f6" : "#0092a8" + tokens["syntax-diff-add"] = diffAdd[10] + tokens["syntax-diff-delete"] = diffDelete[10] + tokens["syntax-diff-unknown"] = "#ff0000" + + tokens["markdown-heading"] = isDark ? "#9d7cd8" : "#d68c27" + tokens["markdown-text"] = isDark ? "#eeeeee" : "#1a1a1a" + tokens["markdown-link"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-link-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code"] = isDark ? "#7fd88f" : "#3d9a57" + tokens["markdown-block-quote"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-emph"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-strong"] = isDark ? "#f5a742" : "#d68c27" + tokens["markdown-horizontal-rule"] = isDark ? "#808080" : "#8a8a8a" + tokens["markdown-list-item"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-list-enumeration"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-image"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-image-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code-block"] = isDark ? "#eeeeee" : "#1a1a1a" + } + + tokens["avatar-background-pink"] = isDark ? "#501b3f" : "#feeef8" + tokens["avatar-background-mint"] = isDark ? "#033a34" : "#e1fbf4" + tokens["avatar-background-orange"] = isDark ? "#5f2a06" : "#fff1e7" + tokens["avatar-background-purple"] = isDark ? "#432155" : "#f9f1fe" + tokens["avatar-background-cyan"] = isDark ? "#0f3058" : "#e7f9fb" + tokens["avatar-background-lime"] = isDark ? "#2b3711" : "#eefadc" + tokens["avatar-text-pink"] = isDark ? "#e34ba9" : "#cd1d8d" + tokens["avatar-text-mint"] = isDark ? "#95f3d9" : "#147d6f" + tokens["avatar-text-orange"] = isDark ? "#ff802b" : "#ed5f00" + tokens["avatar-text-purple"] = isDark ? "#9d5bd2" : "#8445bc" + tokens["avatar-text-cyan"] = isDark ? "#369eff" : "#0894b3" + tokens["avatar-text-lime"] = isDark ? "#c4f042" : "#5d770d" + + for (const [key, value] of Object.entries(overrides)) { + tokens[key] = value + } + + if (colors.compact && "text-weak" in overrides && !("text-weaker" in overrides)) { + const weak = tokens["text-weak"] + if (weak.startsWith("#")) { + tokens["text-weaker"] = shift(weak as HexColor, { l: isDark ? -0.12 : 0.12, c: 0.75 }) + } else { + tokens["text-weaker"] = weak + } + } + + if (colors.compact) { + if (!("markdown-text" in overrides)) { + tokens["markdown-text"] = tokens["text-base"] + } + if (!("markdown-code-block" in overrides)) { + tokens["markdown-code-block"] = tokens["text-base"] + } + } + + if (!("text-stronger" in overrides)) { + tokens["text-stronger"] = tokens["text-strong"] + } + + return tokens +} + +interface ThemeColors { + compact: boolean + neutral: HexColor + ink?: HexColor + primary: HexColor + accent: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + interactive: HexColor + diffAdd?: HexColor + diffDelete?: HexColor +} + +function getColors(variant: ThemeVariant): ThemeColors { + const input = variant as { palette?: unknown; seeds?: unknown } + if (input.palette && input.seeds) { + throw new Error("Theme variant cannot define both `palette` and `seeds`") + } + + if (variant.palette) { + return { + compact: true, + neutral: variant.palette.neutral, + ink: variant.palette.ink, + primary: variant.palette.primary, + accent: variant.palette.accent ?? variant.palette.info, + success: variant.palette.success, + warning: variant.palette.warning, + error: variant.palette.error, + info: variant.palette.info, + interactive: variant.palette.interactive ?? variant.palette.primary, + diffAdd: variant.palette.diffAdd, + diffDelete: variant.palette.diffDelete, + } + } + + if (variant.seeds) { + return { + compact: false, + neutral: variant.seeds.neutral, + ink: undefined, + primary: variant.seeds.primary, + accent: variant.seeds.info, + success: variant.seeds.success, + warning: variant.seeds.warning, + error: variant.seeds.error, + info: variant.seeds.info, + interactive: variant.seeds.interactive, + diffAdd: variant.seeds.diffAdd, + diffDelete: variant.seeds.diffDelete, + } + } + + throw new Error("Theme variant requires `palette` or `seeds`") +} + +function generateNeutralAlphaScale(neutralScale: HexColor[], isDark: boolean): HexColor[] { + const alphas = isDark + ? [0.038, 0.066, 0.1, 0.142, 0.19, 0.252, 0.334, 0.446, 0.58, 0.718, 0.854, 0.985] + : [0.03, 0.06, 0.1, 0.145, 0.2, 0.265, 0.35, 0.47, 0.61, 0.74, 0.86, 0.97] + + return alphas.map((alpha) => blend(neutralScale[11], neutralScale[0], alpha)) +} + +function getHex(value: ColorValue | undefined): HexColor | undefined { + if (!value?.startsWith("#")) return + return value as HexColor +} + +export function resolveTheme(theme: DesktopTheme): { light: ResolvedTheme; dark: ResolvedTheme } { + return { + light: resolveThemeVariant(theme.light, false), + dark: resolveThemeVariant(theme.dark, true), + } +} + +export function themeToCss(tokens: ResolvedTheme): string { + return Object.entries(tokens) + .map(([key, value]) => `--${key}: ${value};`) + .join("\n ") +} diff --git a/packages/ui/src/theme/themes/amoled.json b/packages/ui/src/theme/themes/amoled.json new file mode 100644 index 0000000000000000000000000000000000000000..4734927a22cc771b37f9cb14f6eee3a209ab00ac --- /dev/null +++ b/packages/ui/src/theme/themes/amoled.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "AMOLED", + "id": "amoled", + "light": { + "palette": { + "neutral": "#f0f0f0", + "ink": "#0a0a0a", + "primary": "#6200ff", + "accent": "#ff0080", + "success": "#00e676", + "warning": "#ffab00", + "error": "#ff1744", + "info": "#00b0ff", + "diffAdd": "#00e676", + "diffDelete": "#ff1744" + }, + "overrides": { + "syntax-comment": "#757575", + "syntax-keyword": "#d500f9", + "syntax-string": "#00e676", + "syntax-primitive": "#00b0ff", + "syntax-property": "#ff9100", + "syntax-constant": "#6200ff" + } + }, + "dark": { + "palette": { + "neutral": "#000000", + "ink": "#ffffff", + "primary": "#b388ff", + "accent": "#ff4081", + "success": "#00ff88", + "warning": "#ffea00", + "error": "#ff1744", + "info": "#18ffff", + "diffAdd": "#00ff88", + "diffDelete": "#ff1744" + }, + "overrides": { + "syntax-comment": "#555555", + "syntax-keyword": "#ff00ff", + "syntax-string": "#00ff88", + "syntax-primitive": "#18ffff", + "syntax-property": "#ffea00", + "syntax-constant": "#b388ff" + } + } +} diff --git a/packages/ui/src/theme/themes/aura.json b/packages/ui/src/theme/themes/aura.json new file mode 100644 index 0000000000000000000000000000000000000000..6b70aae9cab3808227bff21881da1550edab2df5 --- /dev/null +++ b/packages/ui/src/theme/themes/aura.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Aura", + "id": "aura", + "light": { + "palette": { + "neutral": "#f5f0ff", + "ink": "#2d2640", + "primary": "#a277ff", + "accent": "#d94f4f", + "success": "#40bf7a", + "warning": "#d9a24a", + "error": "#d94f4f", + "info": "#5bb8d9", + "diffAdd": "#b3e6cc", + "diffDelete": "#f5b3b3" + }, + "overrides": { + "syntax-comment": "#8d88a3", + "syntax-keyword": "#7b5ae0", + "syntax-string": "#2b8a57", + "syntax-primitive": "#2f78b8", + "syntax-property": "#a96a22", + "syntax-type": "#2b8a57", + "syntax-constant": "#d94f4f" + } + }, + "dark": { + "palette": { + "neutral": "#15141b", + "ink": "#edecee", + "primary": "#a277ff", + "accent": "#ff6767", + "success": "#61ffca", + "warning": "#ffca85", + "error": "#ff6767", + "info": "#82e2ff", + "diffAdd": "#61ffca", + "diffDelete": "#ff6767" + }, + "overrides": { + "syntax-comment": "#6d6a7e", + "syntax-keyword": "#a277ff", + "syntax-string": "#61ffca", + "syntax-primitive": "#82e2ff", + "syntax-property": "#ffca85", + "syntax-type": "#61ffca", + "syntax-constant": "#ff6767" + } + } +} diff --git a/packages/ui/src/theme/themes/ayu.json b/packages/ui/src/theme/themes/ayu.json new file mode 100644 index 0000000000000000000000000000000000000000..e6d73f1ee523afe8b0d879225addd2e01600f7f0 --- /dev/null +++ b/packages/ui/src/theme/themes/ayu.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Ayu", + "id": "ayu", + "light": { + "palette": { + "neutral": "#fdfaf4", + "ink": "#4f5964", + "primary": "#4aa8c8", + "accent": "#ef7d71", + "success": "#5fb978", + "warning": "#ea9f41", + "error": "#e6656a", + "info": "#2f9bce", + "diffAdd": "#b1d780", + "diffDelete": "#e6656a" + }, + "overrides": { + "syntax-comment": "#6e7681", + "syntax-keyword": "#c76a1a", + "syntax-string": "#6f8f00", + "syntax-primitive": "#b87500", + "syntax-property": "#2f86b7", + "syntax-type": "#227fc0", + "syntax-constant": "#a37acc" + } + }, + "dark": { + "palette": { + "neutral": "#0f1419", + "ink": "#d6dae0", + "primary": "#3fb7e3", + "accent": "#f2856f", + "success": "#78d05c", + "warning": "#e4a75c", + "error": "#f58572", + "info": "#66c6f1", + "diffAdd": "#59c57c", + "diffDelete": "#f58572" + }, + "overrides": { + "syntax-comment": "#5a6673", + "syntax-keyword": "#ff8f40", + "syntax-string": "#aad94c", + "syntax-primitive": "#ffb454", + "syntax-property": "#39bae6", + "syntax-type": "#59c2ff", + "syntax-constant": "#d2a6ff" + } + } +} diff --git a/packages/ui/src/theme/themes/carbonfox.json b/packages/ui/src/theme/themes/carbonfox.json new file mode 100644 index 0000000000000000000000000000000000000000..50e6475ae3008ea375f3d2fa4c483b0d28875441 --- /dev/null +++ b/packages/ui/src/theme/themes/carbonfox.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Carbonfox", + "id": "carbonfox", + "light": { + "palette": { + "neutral": "#8e8e8e", + "ink": "#161616", + "primary": "#0072c3", + "accent": "#da1e28", + "success": "#198038", + "warning": "#f1c21b", + "error": "#da1e28", + "info": "#0043ce", + "interactive": "#0f62fe", + "diffAdd": "#198038", + "diffDelete": "#da1e28" + }, + "overrides": { + "syntax-comment": "#6f6f6f", + "syntax-keyword": "#8a3ffc", + "syntax-string": "#198038", + "syntax-primitive": "#0f62fe", + "syntax-property": "#0043ce", + "syntax-type": "#8a5f00", + "syntax-constant": "#da1e28" + } + }, + "dark": { + "palette": { + "neutral": "#393939", + "ink": "#f2f4f8", + "primary": "#33b1ff", + "accent": "#ff8389", + "success": "#42be65", + "warning": "#f1c21b", + "error": "#ff8389", + "info": "#78a9ff", + "interactive": "#4589ff", + "diffAdd": "#42be65", + "diffDelete": "#ff8389" + }, + "overrides": { + "syntax-comment": "#6f6f6f", + "syntax-keyword": "#be95ff", + "syntax-string": "#42be65", + "syntax-primitive": "#33b1ff", + "syntax-property": "#78a9ff", + "syntax-type": "#f1c21b", + "syntax-constant": "#ff8389" + } + } +} diff --git a/packages/ui/src/theme/themes/catppuccin-frappe.json b/packages/ui/src/theme/themes/catppuccin-frappe.json new file mode 100644 index 0000000000000000000000000000000000000000..ea02b981de16c0d56bc0c132783d9e4eb9547222 --- /dev/null +++ b/packages/ui/src/theme/themes/catppuccin-frappe.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Catppuccin Frappe", + "id": "catppuccin-frappe", + "light": { + "palette": { + "neutral": "#303446", + "ink": "#c6d0f5", + "primary": "#8da4e2", + "accent": "#f4b8e4", + "success": "#a6d189", + "warning": "#e5c890", + "error": "#e78284", + "info": "#81c8be" + }, + "overrides": { + "text-weak": "#b5bfe2", + "syntax-comment": "#949cb8", + "syntax-keyword": "#ca9ee6", + "syntax-string": "#a6d189", + "syntax-primitive": "#8da4e2", + "syntax-variable": "#e78284", + "syntax-property": "#99d1db", + "syntax-type": "#e5c890", + "syntax-constant": "#ef9f76", + "syntax-operator": "#99d1db", + "syntax-punctuation": "#c6d0f5", + "syntax-object": "#e78284", + "markdown-heading": "#ca9ee6", + "markdown-text": "#c6d0f5", + "markdown-link": "#8da4e2", + "markdown-link-text": "#99d1db", + "markdown-code": "#a6d189", + "markdown-block-quote": "#e5c890", + "markdown-emph": "#e5c890", + "markdown-strong": "#ef9f76", + "markdown-horizontal-rule": "#a5adce", + "markdown-list-item": "#8da4e2", + "markdown-list-enumeration": "#99d1db", + "markdown-image": "#8da4e2", + "markdown-image-text": "#99d1db", + "markdown-code-block": "#c6d0f5" + } + }, + "dark": { + "palette": { + "neutral": "#303446", + "ink": "#c6d0f5", + "primary": "#8da4e2", + "accent": "#f4b8e4", + "success": "#a6d189", + "warning": "#e5c890", + "error": "#e78284", + "info": "#81c8be" + }, + "overrides": { + "text-weak": "#b5bfe2", + "syntax-comment": "#949cb8", + "syntax-keyword": "#ca9ee6", + "syntax-string": "#a6d189", + "syntax-primitive": "#8da4e2", + "syntax-variable": "#e78284", + "syntax-property": "#99d1db", + "syntax-type": "#e5c890", + "syntax-constant": "#ef9f76", + "syntax-operator": "#99d1db", + "syntax-punctuation": "#c6d0f5", + "syntax-object": "#e78284", + "markdown-heading": "#ca9ee6", + "markdown-text": "#c6d0f5", + "markdown-link": "#8da4e2", + "markdown-link-text": "#99d1db", + "markdown-code": "#a6d189", + "markdown-block-quote": "#e5c890", + "markdown-emph": "#e5c890", + "markdown-strong": "#ef9f76", + "markdown-horizontal-rule": "#a5adce", + "markdown-list-item": "#8da4e2", + "markdown-list-enumeration": "#99d1db", + "markdown-image": "#8da4e2", + "markdown-image-text": "#99d1db", + "markdown-code-block": "#c6d0f5" + } + } +} diff --git a/packages/ui/src/theme/themes/catppuccin-macchiato.json b/packages/ui/src/theme/themes/catppuccin-macchiato.json new file mode 100644 index 0000000000000000000000000000000000000000..9315e1542080158a186a1693d86aa633ef97306e --- /dev/null +++ b/packages/ui/src/theme/themes/catppuccin-macchiato.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Catppuccin Macchiato", + "id": "catppuccin-macchiato", + "light": { + "palette": { + "neutral": "#24273a", + "ink": "#cad3f5", + "primary": "#8aadf4", + "accent": "#f5bde6", + "success": "#a6da95", + "warning": "#eed49f", + "error": "#ed8796", + "info": "#8bd5ca" + }, + "overrides": { + "text-weak": "#b8c0e0", + "syntax-comment": "#939ab7", + "syntax-keyword": "#c6a0f6", + "syntax-string": "#a6da95", + "syntax-primitive": "#8aadf4", + "syntax-variable": "#ed8796", + "syntax-property": "#91d7e3", + "syntax-type": "#eed49f", + "syntax-constant": "#f5a97f", + "syntax-operator": "#91d7e3", + "syntax-punctuation": "#cad3f5", + "syntax-object": "#ed8796", + "markdown-heading": "#c6a0f6", + "markdown-text": "#cad3f5", + "markdown-link": "#8aadf4", + "markdown-link-text": "#91d7e3", + "markdown-code": "#a6da95", + "markdown-block-quote": "#eed49f", + "markdown-emph": "#eed49f", + "markdown-strong": "#f5a97f", + "markdown-horizontal-rule": "#a5adcb", + "markdown-list-item": "#8aadf4", + "markdown-list-enumeration": "#91d7e3", + "markdown-image": "#8aadf4", + "markdown-image-text": "#91d7e3", + "markdown-code-block": "#cad3f5" + } + }, + "dark": { + "palette": { + "neutral": "#24273a", + "ink": "#cad3f5", + "primary": "#8aadf4", + "accent": "#f5bde6", + "success": "#a6da95", + "warning": "#eed49f", + "error": "#ed8796", + "info": "#8bd5ca" + }, + "overrides": { + "text-weak": "#b8c0e0", + "syntax-comment": "#939ab7", + "syntax-keyword": "#c6a0f6", + "syntax-string": "#a6da95", + "syntax-primitive": "#8aadf4", + "syntax-variable": "#ed8796", + "syntax-property": "#91d7e3", + "syntax-type": "#eed49f", + "syntax-constant": "#f5a97f", + "syntax-operator": "#91d7e3", + "syntax-punctuation": "#cad3f5", + "syntax-object": "#ed8796", + "markdown-heading": "#c6a0f6", + "markdown-text": "#cad3f5", + "markdown-link": "#8aadf4", + "markdown-link-text": "#91d7e3", + "markdown-code": "#a6da95", + "markdown-block-quote": "#eed49f", + "markdown-emph": "#eed49f", + "markdown-strong": "#f5a97f", + "markdown-horizontal-rule": "#a5adcb", + "markdown-list-item": "#8aadf4", + "markdown-list-enumeration": "#91d7e3", + "markdown-image": "#8aadf4", + "markdown-image-text": "#91d7e3", + "markdown-code-block": "#cad3f5" + } + } +} diff --git a/packages/ui/src/theme/themes/catppuccin.json b/packages/ui/src/theme/themes/catppuccin.json new file mode 100644 index 0000000000000000000000000000000000000000..b67dfaf3e67501f2759206bc486973236dbd4334 --- /dev/null +++ b/packages/ui/src/theme/themes/catppuccin.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Catppuccin", + "id": "catppuccin", + "light": { + "palette": { + "neutral": "#f5e0dc", + "ink": "#4c4f69", + "primary": "#7287fd", + "accent": "#d20f39", + "success": "#40a02b", + "warning": "#df8e1d", + "error": "#d20f39", + "info": "#04a5e5", + "diffAdd": "#a6d189", + "diffDelete": "#e78284" + }, + "overrides": { + "syntax-comment": "#6c7086", + "syntax-keyword": "#8839ef", + "syntax-primitive": "#1e66f5", + "syntax-constant": "#ca6702" + } + }, + "dark": { + "palette": { + "neutral": "#1e1e2e", + "ink": "#cdd6f4", + "primary": "#b4befe", + "accent": "#f38ba8", + "success": "#a6d189", + "warning": "#f4b8e4", + "error": "#f38ba8", + "info": "#89dceb", + "diffAdd": "#94e2d5", + "diffDelete": "#f38ba8" + }, + "overrides": { + "syntax-comment": "#6c7086", + "syntax-keyword": "#cba6f7", + "syntax-primitive": "#89b4fa", + "syntax-constant": "#fab387" + } + } +} diff --git a/packages/ui/src/theme/themes/cobalt2.json b/packages/ui/src/theme/themes/cobalt2.json new file mode 100644 index 0000000000000000000000000000000000000000..56ff3b5041c3db13af3cd70e75bb547284257c5e --- /dev/null +++ b/packages/ui/src/theme/themes/cobalt2.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Cobalt2", + "id": "cobalt2", + "light": { + "palette": { + "neutral": "#ffffff", + "ink": "#193549", + "primary": "#0066cc", + "accent": "#00acc1", + "success": "#4caf50", + "warning": "#ff9800", + "error": "#e91e63", + "info": "#ff5722" + }, + "overrides": { + "text-weak": "#5c6b7d", + "syntax-comment": "#5c6b7d", + "syntax-keyword": "#ff5722", + "syntax-string": "#4caf50", + "syntax-primitive": "#ff9800", + "syntax-variable": "#193549", + "syntax-property": "#00acc1", + "syntax-type": "#00acc1", + "syntax-constant": "#e91e63", + "syntax-operator": "#ff5722", + "syntax-punctuation": "#193549", + "syntax-object": "#193549", + "markdown-heading": "#ff9800", + "markdown-text": "#193549", + "markdown-link": "#0066cc", + "markdown-link-text": "#00acc1", + "markdown-code": "#4caf50", + "markdown-block-quote": "#5c6b7d", + "markdown-emph": "#ff5722", + "markdown-strong": "#e91e63", + "markdown-horizontal-rule": "#d3dae3", + "markdown-list-item": "#0066cc", + "markdown-list-enumeration": "#00acc1", + "markdown-image": "#0066cc", + "markdown-image-text": "#00acc1", + "markdown-code-block": "#193549" + } + }, + "dark": { + "palette": { + "neutral": "#193549", + "ink": "#ffffff", + "primary": "#0088ff", + "accent": "#2affdf", + "success": "#9eff80", + "warning": "#ffc600", + "error": "#ff0088", + "info": "#ff9d00", + "diffAdd": "#b9ff9f", + "diffDelete": "#ff5fb3" + }, + "overrides": { + "text-weak": "#adb7c9", + "syntax-comment": "#0088ff", + "syntax-keyword": "#ff9d00", + "syntax-string": "#9eff80", + "syntax-primitive": "#ffc600", + "syntax-variable": "#ffffff", + "syntax-property": "#2affdf", + "syntax-type": "#2affdf", + "syntax-constant": "#ff628c", + "syntax-operator": "#ff9d00", + "syntax-punctuation": "#ffffff", + "syntax-object": "#ffffff", + "markdown-heading": "#ffc600", + "markdown-text": "#ffffff", + "markdown-link": "#0088ff", + "markdown-link-text": "#2affdf", + "markdown-code": "#9eff80", + "markdown-block-quote": "#adb7c9", + "markdown-emph": "#ff9d00", + "markdown-strong": "#ff628c", + "markdown-horizontal-rule": "#2d5a7b", + "markdown-list-item": "#0088ff", + "markdown-list-enumeration": "#2affdf", + "markdown-image": "#0088ff", + "markdown-image-text": "#2affdf", + "markdown-code-block": "#ffffff" + } + } +} diff --git a/packages/ui/src/theme/themes/cursor.json b/packages/ui/src/theme/themes/cursor.json new file mode 100644 index 0000000000000000000000000000000000000000..cbea5687075980977430a5875cbbb0cefa8012f9 --- /dev/null +++ b/packages/ui/src/theme/themes/cursor.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Cursor", + "id": "cursor", + "light": { + "palette": { + "neutral": "#fcfcfc", + "ink": "#141414", + "primary": "#6f9ba6", + "accent": "#6f9ba6", + "success": "#1f8a65", + "warning": "#db704b", + "error": "#cf2d56", + "info": "#3c7cab", + "interactive": "#206595", + "diffAdd": "#55a583", + "diffDelete": "#e75e78" + }, + "overrides": { + "text-weak": "#141414ad", + "syntax-comment": "#141414ad", + "syntax-keyword": "#b3003f", + "syntax-string": "#9e94d5", + "syntax-primitive": "#db704b", + "syntax-variable": "#141414", + "syntax-property": "#141414ad", + "syntax-type": "#206595", + "syntax-constant": "#b8448b", + "syntax-operator": "#141414", + "syntax-punctuation": "#141414", + "syntax-object": "#141414", + "markdown-heading": "#206595", + "markdown-text": "#141414", + "markdown-link": "#206595", + "markdown-link-text": "#141414ad", + "markdown-code": "#1f8a65", + "markdown-block-quote": "#141414ad", + "markdown-emph": "#141414", + "markdown-strong": "#141414", + "markdown-horizontal-rule": "#141414ad", + "markdown-list-item": "#141414", + "markdown-list-enumeration": "#141414ad", + "markdown-image": "#206595", + "markdown-image-text": "#141414ad", + "markdown-code-block": "#141414" + } + }, + "dark": { + "palette": { + "neutral": "#181818", + "ink": "#e4e4e4", + "primary": "#88c0d0", + "accent": "#88c0d0", + "success": "#3fa266", + "warning": "#f1b467", + "error": "#e34671", + "info": "#81a1c1", + "interactive": "#82D2CE", + "diffAdd": "#70b489", + "diffDelete": "#fc6b83" + }, + "overrides": { + "text-weak": "#e4e4e45e", + "syntax-comment": "#e4e4e45e", + "syntax-keyword": "#82D2CE", + "syntax-string": "#E394DC", + "syntax-primitive": "#EFB080", + "syntax-variable": "#e4e4e4", + "syntax-property": "#81a1c1", + "syntax-type": "#EFB080", + "syntax-constant": "#F8C762", + "syntax-operator": "#e4e4e4", + "syntax-punctuation": "#e4e4e4", + "syntax-object": "#e4e4e4", + "markdown-heading": "#AAA0FA", + "markdown-text": "#e4e4e4", + "markdown-link": "#82D2CE", + "markdown-link-text": "#81a1c1", + "markdown-code": "#E394DC", + "markdown-block-quote": "#e4e4e45e", + "markdown-emph": "#82D2CE", + "markdown-strong": "#F8C762", + "markdown-horizontal-rule": "#e4e4e45e", + "markdown-list-item": "#e4e4e4", + "markdown-list-enumeration": "#88c0d0", + "markdown-image": "#88c0d0", + "markdown-image-text": "#81a1c1", + "markdown-code-block": "#e4e4e4" + } + } +} diff --git a/packages/ui/src/theme/themes/dracula.json b/packages/ui/src/theme/themes/dracula.json new file mode 100644 index 0000000000000000000000000000000000000000..daebb0df5c74568513bb8cc8eff8a3b3d79bcda6 --- /dev/null +++ b/packages/ui/src/theme/themes/dracula.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Dracula", + "id": "dracula", + "light": { + "palette": { + "neutral": "#f8f8f2", + "ink": "#1f1f2f", + "primary": "#7c6bf5", + "accent": "#d16090", + "success": "#2fbf71", + "warning": "#f7a14d", + "error": "#d9536f", + "info": "#1d7fc5", + "diffAdd": "#9fe3b3", + "diffDelete": "#f8a1b8" + }, + "overrides": { + "syntax-comment": "#7d7f97", + "syntax-keyword": "#d16090", + "syntax-string": "#596600", + "syntax-primitive": "#2f8f57", + "syntax-property": "#1d7fc5", + "syntax-constant": "#7c6bf5" + } + }, + "dark": { + "palette": { + "neutral": "#1d1e28", + "ink": "#f8f8f2", + "primary": "#bd93f9", + "accent": "#ff79c6", + "success": "#50fa7b", + "warning": "#ffb86c", + "error": "#ff5555", + "info": "#8be9fd", + "diffAdd": "#2fb27d", + "diffDelete": "#ff6b81" + }, + "overrides": { + "syntax-comment": "#6272a4", + "syntax-keyword": "#ff79c6", + "syntax-string": "#f1fa8c", + "syntax-primitive": "#50fa7b", + "syntax-property": "#8be9fd", + "syntax-constant": "#bd93f9" + } + } +} diff --git a/packages/ui/src/theme/themes/everforest.json b/packages/ui/src/theme/themes/everforest.json new file mode 100644 index 0000000000000000000000000000000000000000..21c04c8ab38ce759bc64ed0d8360ef8e4330c30b --- /dev/null +++ b/packages/ui/src/theme/themes/everforest.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Everforest", + "id": "everforest", + "light": { + "palette": { + "neutral": "#fdf6e3", + "ink": "#5c6a72", + "primary": "#8da101", + "accent": "#df69ba", + "success": "#8da101", + "warning": "#f57d26", + "error": "#f85552", + "info": "#35a77c", + "diffAdd": "#4db380", + "diffDelete": "#f52a65" + }, + "overrides": { + "text-weak": "#a6b0a0", + "syntax-comment": "#a6b0a0", + "syntax-keyword": "#df69ba", + "syntax-string": "#8da101", + "syntax-primitive": "#8da101", + "syntax-variable": "#f85552", + "syntax-property": "#35a77c", + "syntax-type": "#dfa000", + "syntax-constant": "#f57d26", + "syntax-operator": "#35a77c", + "syntax-punctuation": "#5c6a72", + "syntax-object": "#f85552", + "markdown-heading": "#df69ba", + "markdown-text": "#5c6a72", + "markdown-link": "#8da101", + "markdown-link-text": "#35a77c", + "markdown-code": "#8da101", + "markdown-block-quote": "#dfa000", + "markdown-emph": "#dfa000", + "markdown-strong": "#f57d26", + "markdown-horizontal-rule": "#a6b0a0", + "markdown-list-item": "#8da101", + "markdown-list-enumeration": "#35a77c", + "markdown-image": "#8da101", + "markdown-image-text": "#35a77c", + "markdown-code-block": "#5c6a72" + } + }, + "dark": { + "palette": { + "neutral": "#2d353b", + "ink": "#d3c6aa", + "primary": "#a7c080", + "accent": "#d699b6", + "success": "#a7c080", + "warning": "#e69875", + "error": "#e67e80", + "info": "#83c092", + "diffAdd": "#b8db87", + "diffDelete": "#e26a75" + }, + "overrides": { + "text-weak": "#7a8478", + "syntax-comment": "#7a8478", + "syntax-keyword": "#d699b6", + "syntax-string": "#a7c080", + "syntax-primitive": "#a7c080", + "syntax-variable": "#e67e80", + "syntax-property": "#83c092", + "syntax-type": "#dbbc7f", + "syntax-constant": "#e69875", + "syntax-operator": "#83c092", + "syntax-punctuation": "#d3c6aa", + "syntax-object": "#e67e80", + "markdown-heading": "#d699b6", + "markdown-text": "#d3c6aa", + "markdown-link": "#a7c080", + "markdown-link-text": "#83c092", + "markdown-code": "#a7c080", + "markdown-block-quote": "#dbbc7f", + "markdown-emph": "#dbbc7f", + "markdown-strong": "#e69875", + "markdown-horizontal-rule": "#7a8478", + "markdown-list-item": "#a7c080", + "markdown-list-enumeration": "#83c092", + "markdown-image": "#a7c080", + "markdown-image-text": "#83c092", + "markdown-code-block": "#d3c6aa" + } + } +} diff --git a/packages/ui/src/theme/themes/flexoki.json b/packages/ui/src/theme/themes/flexoki.json new file mode 100644 index 0000000000000000000000000000000000000000..147681818301faf7d89662de07ebad9b264158db --- /dev/null +++ b/packages/ui/src/theme/themes/flexoki.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Flexoki", + "id": "flexoki", + "light": { + "palette": { + "neutral": "#FFFCF0", + "ink": "#100F0F", + "primary": "#205EA6", + "accent": "#BC5215", + "success": "#66800B", + "warning": "#BC5215", + "error": "#AF3029", + "info": "#24837B" + }, + "overrides": { + "text-weak": "#6F6E69", + "syntax-comment": "#6F6E69", + "syntax-keyword": "#66800B", + "syntax-string": "#24837B", + "syntax-primitive": "#BC5215", + "syntax-variable": "#205EA6", + "syntax-property": "#24837B", + "syntax-type": "#AD8301", + "syntax-constant": "#5E409D", + "syntax-operator": "#6F6E69", + "syntax-punctuation": "#6F6E69", + "syntax-object": "#205EA6", + "markdown-heading": "#5E409D", + "markdown-text": "#100F0F", + "markdown-link": "#205EA6", + "markdown-link-text": "#24837B", + "markdown-code": "#24837B", + "markdown-block-quote": "#AD8301", + "markdown-emph": "#AD8301", + "markdown-strong": "#BC5215", + "markdown-horizontal-rule": "#6F6E69", + "markdown-list-item": "#BC5215", + "markdown-list-enumeration": "#24837B", + "markdown-image": "#A02F6F", + "markdown-image-text": "#24837B", + "markdown-code-block": "#100F0F" + } + }, + "dark": { + "palette": { + "neutral": "#100F0F", + "ink": "#CECDC3", + "primary": "#DA702C", + "accent": "#8B7EC8", + "success": "#879A39", + "warning": "#DA702C", + "error": "#D14D41", + "info": "#3AA99F", + "interactive": "#4385BE" + }, + "overrides": { + "text-weak": "#6F6E69", + "syntax-comment": "#6F6E69", + "syntax-keyword": "#879A39", + "syntax-string": "#3AA99F", + "syntax-primitive": "#DA702C", + "syntax-variable": "#4385BE", + "syntax-property": "#3AA99F", + "syntax-type": "#D0A215", + "syntax-constant": "#8B7EC8", + "syntax-operator": "#B7B5AC", + "syntax-punctuation": "#B7B5AC", + "syntax-object": "#4385BE", + "markdown-heading": "#8B7EC8", + "markdown-text": "#CECDC3", + "markdown-link": "#4385BE", + "markdown-link-text": "#3AA99F", + "markdown-code": "#3AA99F", + "markdown-block-quote": "#D0A215", + "markdown-emph": "#D0A215", + "markdown-strong": "#DA702C", + "markdown-horizontal-rule": "#6F6E69", + "markdown-list-item": "#DA702C", + "markdown-list-enumeration": "#3AA99F", + "markdown-image": "#CE5D97", + "markdown-image-text": "#3AA99F", + "markdown-code-block": "#CECDC3" + } + } +} diff --git a/packages/ui/src/theme/themes/github.json b/packages/ui/src/theme/themes/github.json new file mode 100644 index 0000000000000000000000000000000000000000..8cdf54f143e95142cc1235ba0b6a3d8e954ee27f --- /dev/null +++ b/packages/ui/src/theme/themes/github.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "GitHub", + "id": "github", + "light": { + "palette": { + "neutral": "#ffffff", + "ink": "#24292f", + "primary": "#0969da", + "accent": "#1b7c83", + "success": "#1a7f37", + "warning": "#9a6700", + "error": "#cf222e", + "info": "#bc4c00" + }, + "overrides": { + "text-weak": "#57606a", + "syntax-comment": "#57606a", + "syntax-keyword": "#cf222e", + "syntax-string": "#0969da", + "syntax-primitive": "#8250df", + "syntax-variable": "#bc4c00", + "syntax-property": "#1b7c83", + "syntax-type": "#bc4c00", + "syntax-constant": "#1b7c83", + "syntax-operator": "#cf222e", + "syntax-punctuation": "#24292f", + "syntax-object": "#bc4c00", + "markdown-heading": "#0969da", + "markdown-text": "#24292f", + "markdown-link": "#0969da", + "markdown-link-text": "#1b7c83", + "markdown-code": "#bf3989", + "markdown-block-quote": "#57606a", + "markdown-emph": "#9a6700", + "markdown-strong": "#bc4c00", + "markdown-horizontal-rule": "#d0d7de", + "markdown-list-item": "#0969da", + "markdown-list-enumeration": "#1b7c83", + "markdown-image": "#0969da", + "markdown-image-text": "#1b7c83", + "markdown-code-block": "#24292f" + } + }, + "dark": { + "palette": { + "neutral": "#0d1117", + "ink": "#c9d1d9", + "primary": "#58a6ff", + "accent": "#39c5cf", + "success": "#3fb950", + "warning": "#e3b341", + "error": "#f85149", + "info": "#d29922" + }, + "overrides": { + "text-weak": "#8b949e", + "syntax-comment": "#8b949e", + "syntax-keyword": "#ff7b72", + "syntax-string": "#39c5cf", + "syntax-primitive": "#bc8cff", + "syntax-variable": "#d29922", + "syntax-property": "#39c5cf", + "syntax-type": "#d29922", + "syntax-constant": "#58a6ff", + "syntax-operator": "#ff7b72", + "syntax-punctuation": "#c9d1d9", + "syntax-object": "#d29922", + "markdown-heading": "#58a6ff", + "markdown-text": "#c9d1d9", + "markdown-link": "#58a6ff", + "markdown-link-text": "#39c5cf", + "markdown-code": "#ff7b72", + "markdown-block-quote": "#8b949e", + "markdown-emph": "#e3b341", + "markdown-strong": "#d29922", + "markdown-horizontal-rule": "#30363d", + "markdown-list-item": "#58a6ff", + "markdown-list-enumeration": "#39c5cf", + "markdown-image": "#58a6ff", + "markdown-image-text": "#39c5cf", + "markdown-code-block": "#c9d1d9" + } + } +} diff --git a/packages/ui/src/theme/themes/gruvbox.json b/packages/ui/src/theme/themes/gruvbox.json new file mode 100644 index 0000000000000000000000000000000000000000..c1af64e9131582e56a8751eb0614af2a3d628821 --- /dev/null +++ b/packages/ui/src/theme/themes/gruvbox.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Gruvbox", + "id": "gruvbox", + "light": { + "palette": { + "neutral": "#fbf1c7", + "ink": "#3c3836", + "primary": "#076678", + "accent": "#9d0006", + "success": "#79740e", + "warning": "#b57614", + "error": "#9d0006", + "info": "#8f3f71", + "diffAdd": "#79740e", + "diffDelete": "#9d0006" + }, + "overrides": { + "syntax-comment": "#928374", + "syntax-keyword": "#9d0006", + "syntax-primitive": "#076678", + "syntax-constant": "#8f3f71" + } + }, + "dark": { + "palette": { + "neutral": "#282828", + "ink": "#ebdbb2", + "primary": "#83a598", + "accent": "#fb4934", + "success": "#b8bb26", + "warning": "#fabd2f", + "error": "#fb4934", + "info": "#d3869b", + "diffAdd": "#b8bb26", + "diffDelete": "#fb4934" + }, + "overrides": { + "syntax-comment": "#928374", + "syntax-keyword": "#fb4934", + "syntax-primitive": "#83a598", + "syntax-constant": "#d3869b" + } + } +} diff --git a/packages/ui/src/theme/themes/kanagawa.json b/packages/ui/src/theme/themes/kanagawa.json new file mode 100644 index 0000000000000000000000000000000000000000..e1b308c15e3843ddfcc2f8f08f657d796c25e1b5 --- /dev/null +++ b/packages/ui/src/theme/themes/kanagawa.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Kanagawa", + "id": "kanagawa", + "light": { + "palette": { + "neutral": "#F2E9DE", + "ink": "#54433A", + "primary": "#2D4F67", + "accent": "#D27E99", + "success": "#98BB6C", + "warning": "#D7A657", + "error": "#E82424", + "info": "#76946A", + "diffAdd": "#89AF5B", + "diffDelete": "#D61F1F" + }, + "overrides": { + "text-weak": "#9E9389", + "syntax-comment": "#9E9389", + "syntax-keyword": "#957FB8", + "syntax-string": "#98BB6C", + "syntax-primitive": "#2D4F67", + "syntax-variable": "#54433A", + "syntax-property": "#76946A", + "syntax-type": "#C38D9D", + "syntax-constant": "#D7A657", + "syntax-operator": "#D27E99", + "syntax-punctuation": "#54433A", + "syntax-object": "#54433A", + "markdown-heading": "#957FB8", + "markdown-text": "#54433A", + "markdown-link": "#2D4F67", + "markdown-link-text": "#76946A", + "markdown-code": "#98BB6C", + "markdown-block-quote": "#9E9389", + "markdown-emph": "#C38D9D", + "markdown-strong": "#D7A657", + "markdown-horizontal-rule": "#9E9389", + "markdown-list-item": "#2D4F67", + "markdown-list-enumeration": "#76946A", + "markdown-image": "#2D4F67", + "markdown-image-text": "#76946A", + "markdown-code-block": "#54433A" + } + }, + "dark": { + "palette": { + "neutral": "#1F1F28", + "ink": "#DCD7BA", + "primary": "#7E9CD8", + "accent": "#D27E99", + "success": "#98BB6C", + "warning": "#D7A657", + "error": "#E82424", + "info": "#76946A", + "diffAdd": "#A9D977", + "diffDelete": "#F24A4A" + }, + "overrides": { + "text-weak": "#727169", + "syntax-comment": "#727169", + "syntax-keyword": "#957FB8", + "syntax-string": "#98BB6C", + "syntax-primitive": "#7E9CD8", + "syntax-variable": "#DCD7BA", + "syntax-property": "#76946A", + "syntax-type": "#C38D9D", + "syntax-constant": "#D7A657", + "syntax-operator": "#D27E99", + "syntax-punctuation": "#DCD7BA", + "syntax-object": "#DCD7BA", + "markdown-heading": "#957FB8", + "markdown-text": "#DCD7BA", + "markdown-link": "#7E9CD8", + "markdown-link-text": "#76946A", + "markdown-code": "#98BB6C", + "markdown-block-quote": "#727169", + "markdown-emph": "#C38D9D", + "markdown-strong": "#D7A657", + "markdown-horizontal-rule": "#727169", + "markdown-list-item": "#7E9CD8", + "markdown-list-enumeration": "#76946A", + "markdown-image": "#7E9CD8", + "markdown-image-text": "#76946A", + "markdown-code-block": "#DCD7BA" + } + } +} diff --git a/packages/ui/src/theme/themes/lucent-orng.json b/packages/ui/src/theme/themes/lucent-orng.json new file mode 100644 index 0000000000000000000000000000000000000000..b963152ec35c28e9837926425b70d153dac31e07 --- /dev/null +++ b/packages/ui/src/theme/themes/lucent-orng.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Lucent Orng", + "id": "lucent-orng", + "light": { + "palette": { + "neutral": "#fff5f0", + "ink": "#1a1a1a", + "primary": "#EC5B2B", + "accent": "#c94d24", + "success": "#0062d1", + "warning": "#EC5B2B", + "error": "#d1383d", + "info": "#318795", + "diffDelete": "#f52a65" + }, + "overrides": { + "text-weak": "#8a8a8a", + "syntax-comment": "#8a8a8a", + "syntax-keyword": "#EC5B2B", + "syntax-string": "#0062d1", + "syntax-primitive": "#c94d24", + "syntax-variable": "#d1383d", + "syntax-property": "#318795", + "syntax-type": "#b0851f", + "syntax-constant": "#EC5B2B", + "syntax-operator": "#318795", + "syntax-punctuation": "#1a1a1a", + "syntax-object": "#d1383d", + "markdown-heading": "#EC5B2B", + "markdown-text": "#1a1a1a", + "markdown-link": "#EC5B2B", + "markdown-link-text": "#318795", + "markdown-code": "#0062d1", + "markdown-block-quote": "#b0851f", + "markdown-emph": "#b0851f", + "markdown-strong": "#EC5B2B", + "markdown-horizontal-rule": "#8a8a8a", + "markdown-list-item": "#EC5B2B", + "markdown-list-enumeration": "#318795", + "markdown-image": "#EC5B2B", + "markdown-image-text": "#318795", + "markdown-code-block": "#1a1a1a" + } + }, + "dark": { + "palette": { + "neutral": "#2a1a15", + "ink": "#eeeeee", + "primary": "#EC5B2B", + "accent": "#FFF7F1", + "success": "#6ba1e6", + "warning": "#EC5B2B", + "error": "#e06c75", + "info": "#56b6c2", + "diffDelete": "#e26a75" + }, + "overrides": { + "text-weak": "#808080", + "syntax-comment": "#808080", + "syntax-keyword": "#EC5B2B", + "syntax-string": "#6ba1e6", + "syntax-primitive": "#EE7948", + "syntax-variable": "#e06c75", + "syntax-property": "#56b6c2", + "syntax-type": "#e5c07b", + "syntax-constant": "#FFF7F1", + "syntax-operator": "#56b6c2", + "syntax-punctuation": "#eeeeee", + "syntax-object": "#e06c75", + "markdown-heading": "#EC5B2B", + "markdown-text": "#eeeeee", + "markdown-link": "#EC5B2B", + "markdown-link-text": "#56b6c2", + "markdown-code": "#6ba1e6", + "markdown-block-quote": "#FFF7F1", + "markdown-emph": "#e5c07b", + "markdown-strong": "#EE7948", + "markdown-horizontal-rule": "#808080", + "markdown-list-item": "#EC5B2B", + "markdown-list-enumeration": "#56b6c2", + "markdown-image": "#EC5B2B", + "markdown-image-text": "#56b6c2", + "markdown-code-block": "#eeeeee" + } + } +} diff --git a/packages/ui/src/theme/themes/material.json b/packages/ui/src/theme/themes/material.json new file mode 100644 index 0000000000000000000000000000000000000000..0753bbf5290ebd288941764156684d05c3dd399a --- /dev/null +++ b/packages/ui/src/theme/themes/material.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Material", + "id": "material", + "light": { + "palette": { + "neutral": "#fafafa", + "ink": "#263238", + "primary": "#6182b8", + "accent": "#39adb5", + "success": "#91b859", + "warning": "#ffb300", + "error": "#e53935", + "info": "#f4511e", + "interactive": "#39adb5" + }, + "overrides": { + "text-weak": "#90a4ae", + "syntax-comment": "#90a4ae", + "syntax-keyword": "#7c4dff", + "syntax-string": "#91b859", + "syntax-primitive": "#6182b8", + "syntax-variable": "#263238", + "syntax-property": "#7c4dff", + "syntax-type": "#ffb300", + "syntax-constant": "#f4511e", + "syntax-operator": "#39adb5", + "syntax-punctuation": "#263238", + "syntax-object": "#263238", + "markdown-heading": "#6182b8", + "markdown-text": "#263238", + "markdown-link": "#39adb5", + "markdown-link-text": "#7c4dff", + "markdown-code": "#91b859", + "markdown-block-quote": "#90a4ae", + "markdown-emph": "#ffb300", + "markdown-strong": "#f4511e", + "markdown-horizontal-rule": "#e0e0e0", + "markdown-list-item": "#6182b8", + "markdown-list-enumeration": "#39adb5", + "markdown-image": "#39adb5", + "markdown-image-text": "#7c4dff", + "markdown-code-block": "#263238" + } + }, + "dark": { + "palette": { + "neutral": "#263238", + "ink": "#eeffff", + "primary": "#82aaff", + "accent": "#89ddff", + "success": "#c3e88d", + "warning": "#ffcb6b", + "error": "#f07178", + "info": "#ffcb6b", + "interactive": "#89ddff" + }, + "overrides": { + "text-weak": "#546e7a", + "syntax-comment": "#546e7a", + "syntax-keyword": "#c792ea", + "syntax-string": "#c3e88d", + "syntax-primitive": "#82aaff", + "syntax-variable": "#eeffff", + "syntax-property": "#c792ea", + "syntax-type": "#ffcb6b", + "syntax-constant": "#ffcb6b", + "syntax-operator": "#89ddff", + "syntax-punctuation": "#eeffff", + "syntax-object": "#eeffff", + "markdown-heading": "#82aaff", + "markdown-text": "#eeffff", + "markdown-link": "#89ddff", + "markdown-link-text": "#c792ea", + "markdown-code": "#c3e88d", + "markdown-block-quote": "#546e7a", + "markdown-emph": "#ffcb6b", + "markdown-strong": "#ffcb6b", + "markdown-horizontal-rule": "#37474f", + "markdown-list-item": "#82aaff", + "markdown-list-enumeration": "#89ddff", + "markdown-image": "#89ddff", + "markdown-image-text": "#c792ea", + "markdown-code-block": "#eeffff" + } + } +} diff --git a/packages/ui/src/theme/themes/matrix.json b/packages/ui/src/theme/themes/matrix.json new file mode 100644 index 0000000000000000000000000000000000000000..7c6d87926f69139a71c0a12fdae0b56dee0d7cc0 --- /dev/null +++ b/packages/ui/src/theme/themes/matrix.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Matrix", + "id": "matrix", + "light": { + "palette": { + "neutral": "#eef3ea", + "ink": "#203022", + "primary": "#1cc24b", + "accent": "#c770ff", + "success": "#1cc24b", + "warning": "#e6ff57", + "error": "#ff4b4b", + "info": "#30b3ff", + "interactive": "#30b3ff", + "diffAdd": "#5dac7e", + "diffDelete": "#d53a3a" + }, + "overrides": { + "text-weak": "#748476", + "syntax-comment": "#748476", + "syntax-keyword": "#c770ff", + "syntax-string": "#1cc24b", + "syntax-primitive": "#30b3ff", + "syntax-variable": "#203022", + "syntax-property": "#24f6d9", + "syntax-type": "#e6ff57", + "syntax-constant": "#ffa83d", + "syntax-operator": "#24f6d9", + "syntax-punctuation": "#203022", + "syntax-object": "#203022", + "markdown-heading": "#24f6d9", + "markdown-text": "#203022", + "markdown-link": "#30b3ff", + "markdown-link-text": "#24f6d9", + "markdown-code": "#1cc24b", + "markdown-block-quote": "#748476", + "markdown-emph": "#ffa83d", + "markdown-strong": "#e6ff57", + "markdown-horizontal-rule": "#748476", + "markdown-list-item": "#30b3ff", + "markdown-list-enumeration": "#24f6d9", + "markdown-image": "#30b3ff", + "markdown-image-text": "#24f6d9", + "markdown-code-block": "#203022" + }, + "v2Overrides": { + "v2-text-text-base": "#353535", + "v2-text-text-muted": "#748476", + "v2-text-text-faint": "#748476", + "v2-background-bg-accent": "var(--v2-green-600)", + "v2-text-text-accent": "var(--v2-green-600)", + "v2-text-text-accent-hover": "var(--v2-green-700)", + "v2-icon-icon-accent": "var(--v2-green-600)", + "v2-icon-icon-accent-hover": "var(--v2-green-700)", + "v2-border-border-focus": "var(--v2-green-500)" + } + }, + "dark": { + "palette": { + "neutral": "#0a0e0a", + "ink": "#62ff94", + "primary": "#2eff6a", + "accent": "#c770ff", + "success": "#62ff94", + "warning": "#e6ff57", + "error": "#ff4b4b", + "info": "#30b3ff", + "interactive": "#30b3ff", + "diffAdd": "#77ffaf", + "diffDelete": "#ff7171" + }, + "overrides": { + "text-weak": "#8ca391", + "syntax-comment": "#8ca391", + "syntax-keyword": "#c770ff", + "syntax-string": "#1cc24b", + "syntax-primitive": "#30b3ff", + "syntax-variable": "#62ff94", + "syntax-property": "#24f6d9", + "syntax-type": "#e6ff57", + "syntax-constant": "#ffa83d", + "syntax-operator": "#24f6d9", + "syntax-punctuation": "#62ff94", + "syntax-object": "#62ff94", + "markdown-heading": "#00efff", + "markdown-text": "#62ff94", + "markdown-link": "#30b3ff", + "markdown-link-text": "#24f6d9", + "markdown-code": "#1cc24b", + "markdown-block-quote": "#8ca391", + "markdown-emph": "#ffa83d", + "markdown-strong": "#e6ff57", + "markdown-horizontal-rule": "#8ca391", + "markdown-list-item": "#30b3ff", + "markdown-list-enumeration": "#24f6d9", + "markdown-image": "#30b3ff", + "markdown-image-text": "#24f6d9", + "markdown-code-block": "#62ff94" + }, + "v2Overrides": { + "v2-text-text-base": "#ececec", + "v2-text-text-muted": "#8ca391", + "v2-text-text-faint": "#8ca391", + "v2-background-bg-accent": "var(--v2-green-600)", + "v2-text-text-accent": "var(--v2-green-400)", + "v2-text-text-accent-hover": "var(--v2-green-300)", + "v2-icon-icon-accent": "var(--v2-green-400)", + "v2-icon-icon-accent-hover": "var(--v2-green-300)", + "v2-border-border-focus": "var(--v2-green-500)" + } + } +} diff --git a/packages/ui/src/theme/themes/mercury.json b/packages/ui/src/theme/themes/mercury.json new file mode 100644 index 0000000000000000000000000000000000000000..0d16eda1edb621a5cdc7af24d32e0f9d1575fffc --- /dev/null +++ b/packages/ui/src/theme/themes/mercury.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Mercury", + "id": "mercury", + "light": { + "palette": { + "neutral": "#ffffff", + "ink": "#363644", + "primary": "#5266eb", + "accent": "#8da4f5", + "success": "#036e43", + "warning": "#a44200", + "error": "#b0175f", + "info": "#007f95", + "interactive": "#465bd1" + }, + "overrides": { + "text-weak": "#70707d", + "syntax-comment": "#70707d", + "syntax-keyword": "#465bd1", + "syntax-string": "#036e43", + "syntax-primitive": "#5266eb", + "syntax-variable": "#007f95", + "syntax-property": "#5266eb", + "syntax-type": "#007f95", + "syntax-constant": "#a44200", + "syntax-operator": "#465bd1", + "syntax-punctuation": "#363644", + "syntax-object": "#007f95", + "markdown-heading": "#1e1e2a", + "markdown-text": "#363644", + "markdown-link": "#465bd1", + "markdown-link-text": "#5266eb", + "markdown-code": "#036e43", + "markdown-block-quote": "#70707d", + "markdown-emph": "#a44200", + "markdown-strong": "#1e1e2a", + "markdown-horizontal-rule": "#7073931a", + "markdown-list-item": "#1e1e2a", + "markdown-list-enumeration": "#5266eb", + "markdown-image": "#465bd1", + "markdown-image-text": "#5266eb", + "markdown-code-block": "#363644" + } + }, + "dark": { + "palette": { + "neutral": "#171721", + "ink": "#dddde5", + "primary": "#8da4f5", + "accent": "#8da4f5", + "success": "#77c599", + "warning": "#fc9b6f", + "error": "#fc92b4", + "info": "#77becf" + }, + "overrides": { + "text-weak": "#9d9da8", + "syntax-comment": "#9d9da8", + "syntax-keyword": "#8da4f5", + "syntax-string": "#77c599", + "syntax-primitive": "#8da4f5", + "syntax-variable": "#77becf", + "syntax-property": "#a7b6f8", + "syntax-type": "#77becf", + "syntax-constant": "#fc9b6f", + "syntax-operator": "#8da4f5", + "syntax-punctuation": "#dddde5", + "syntax-object": "#77becf", + "markdown-heading": "#ffffff", + "markdown-text": "#dddde5", + "markdown-link": "#8da4f5", + "markdown-link-text": "#a7b6f8", + "markdown-code": "#77c599", + "markdown-block-quote": "#9d9da8", + "markdown-emph": "#fc9b6f", + "markdown-strong": "#f4f5f9", + "markdown-horizontal-rule": "#b4b7c81f", + "markdown-list-item": "#ffffff", + "markdown-list-enumeration": "#8da4f5", + "markdown-image": "#8da4f5", + "markdown-image-text": "#a7b6f8", + "markdown-code-block": "#dddde5" + } + } +} diff --git a/packages/ui/src/theme/themes/monokai.json b/packages/ui/src/theme/themes/monokai.json new file mode 100644 index 0000000000000000000000000000000000000000..d680484a2f85bb25b9025033147853ae9e3ec597 --- /dev/null +++ b/packages/ui/src/theme/themes/monokai.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Monokai", + "id": "monokai", + "light": { + "palette": { + "neutral": "#fdf8ec", + "ink": "#292318", + "primary": "#bf7bff", + "accent": "#d9487c", + "success": "#4fb54b", + "warning": "#f1a948", + "error": "#e54b4b", + "info": "#2d9ad7", + "diffAdd": "#bfe7a3", + "diffDelete": "#f6a3ae" + }, + "overrides": { + "syntax-comment": "#8a816f", + "syntax-keyword": "#d9487c", + "syntax-string": "#8a6500", + "syntax-primitive": "#3c8d2f", + "syntax-property": "#1f88c8", + "syntax-constant": "#9b5fe0" + } + }, + "dark": { + "palette": { + "neutral": "#272822", + "ink": "#f8f8f2", + "primary": "#ae81ff", + "accent": "#f92672", + "success": "#a6e22e", + "warning": "#fd971f", + "error": "#f92672", + "info": "#66d9ef", + "diffAdd": "#4d7f2a", + "diffDelete": "#f4477c" + }, + "overrides": { + "syntax-comment": "#75715e", + "syntax-keyword": "#f92672", + "syntax-string": "#e6db74", + "syntax-primitive": "#a6e22e", + "syntax-property": "#66d9ef", + "syntax-constant": "#ae81ff" + } + } +} diff --git a/packages/ui/src/theme/themes/nightowl.json b/packages/ui/src/theme/themes/nightowl.json new file mode 100644 index 0000000000000000000000000000000000000000..863bd5a7604296b167fc1307c3e1c9db45f114ad --- /dev/null +++ b/packages/ui/src/theme/themes/nightowl.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Night Owl", + "id": "nightowl", + "light": { + "palette": { + "neutral": "#f0f0f0", + "ink": "#403f53", + "primary": "#4876d6", + "accent": "#aa0982", + "success": "#2aa298", + "warning": "#c96765", + "error": "#de3d3b", + "info": "#4876d6", + "diffAdd": "#2aa298", + "diffDelete": "#de3d3b" + }, + "overrides": { + "syntax-comment": "#7a8181", + "syntax-keyword": "#994cc3", + "syntax-primitive": "#4876d6", + "syntax-constant": "#c96765" + } + }, + "dark": { + "palette": { + "neutral": "#011627", + "ink": "#d6deeb", + "primary": "#82aaff", + "accent": "#f78c6c", + "success": "#c5e478", + "warning": "#ecc48d", + "error": "#ef5350", + "info": "#82aaff", + "diffAdd": "#c5e478", + "diffDelete": "#ef5350" + }, + "overrides": { + "syntax-comment": "#637777", + "syntax-keyword": "#c792ea", + "syntax-string": "#ecc48d", + "syntax-primitive": "#82aaff", + "syntax-constant": "#f78c6c" + } + } +} diff --git a/packages/ui/src/theme/themes/nord.json b/packages/ui/src/theme/themes/nord.json new file mode 100644 index 0000000000000000000000000000000000000000..136011ae5d0f167e4525b28711f8d66dc0141bc2 --- /dev/null +++ b/packages/ui/src/theme/themes/nord.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Nord", + "id": "nord", + "light": { + "palette": { + "neutral": "#eceff4", + "ink": "#2e3440", + "primary": "#5e81ac", + "accent": "#bf616a", + "success": "#8fbcbb", + "warning": "#d08770", + "error": "#bf616a", + "info": "#81a1c1", + "diffAdd": "#a3be8c", + "diffDelete": "#bf616a" + }, + "overrides": { + "syntax-comment": "#6b7282", + "syntax-keyword": "#5e81ac", + "syntax-string": "#6f8758", + "syntax-primitive": "#5e81ac", + "syntax-constant": "#8d6886" + } + }, + "dark": { + "palette": { + "neutral": "#2e3440", + "ink": "#e5e9f0", + "primary": "#88c0d0", + "accent": "#d57780", + "success": "#a3be8c", + "warning": "#d08770", + "error": "#bf616a", + "info": "#81a1c1", + "diffAdd": "#81a1c1", + "diffDelete": "#bf616a" + }, + "overrides": { + "syntax-comment": "#616e88", + "syntax-keyword": "#81a1c1", + "syntax-primitive": "#88c0d0", + "syntax-constant": "#b48ead" + } + } +} diff --git a/packages/ui/src/theme/themes/oc-2.json b/packages/ui/src/theme/themes/oc-2.json new file mode 100644 index 0000000000000000000000000000000000000000..461262533ab41f776e91e58ece9ed47bc9811154 --- /dev/null +++ b/packages/ui/src/theme/themes/oc-2.json @@ -0,0 +1,476 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "OC-2", + "id": "oc-2", + "light": { + "palette": { + "neutral": "#f7f7f7", + "ink": "#171311", + "primary": "#dcde8d", + "success": "#12c905", + "warning": "#ffdc17", + "error": "#fc533a", + "info": "#a753ae", + "interactive": "#034cff", + "diffAdd": "#9ff29a", + "diffDelete": "#fc533a" + }, + "overrides": { + "text-strong": "#171717", + "text-base": "#6F6F6F", + "text-weak": "#8F8F8F", + "text-weaker": "#C7C7C7", + "text-diff-add-base": "var(--v2-state-fg-success)", + "text-diff-delete-base": "var(--v2-state-fg-danger)", + "border-weak-base": "#DBDBDB", + "border-weaker-base": "#E8E8E8", + "icon-base": "#8F8F8F", + "icon-weak-base": "#C7C7C7", + "surface-raised-base": "#F3F3F3", + "surface-raised-base-hover": "#EDEDED", + "surface-base": "#F8F8F8", + "surface-base-hover": "#0000000A", + "surface-interactive-weak": "#F5FAFF", + "icon-success-base": "#0ABE00", + "surface-success-base": "#E6FFE5", + "syntax-comment": "var(--v2-text-text-muted)", + "syntax-keyword": "var(--v2-pink-800)", + "syntax-string": "var(--v2-green-800)", + "syntax-primitive": "var(--v2-pink-800)", + "syntax-property": "var(--v2-orange-800)", + "syntax-type": "var(--v2-purple-800)", + "syntax-constant": "#007b80", + "syntax-critical": "var(--v2-red-800)", + "syntax-diff-add": "var(--v2-state-fg-success)", + "syntax-diff-delete": "var(--v2-state-fg-danger)", + "syntax-diff-unknown": "#a753ae", + "surface-critical-base": "#FFF2F0" + }, + "v2Overrides": { + "v2-grey-50": "#ffffffff", + "v2-grey-100": "#fafafaff", + "v2-grey-200": "#f2f2f2ff", + "v2-grey-300": "#eeeeeeff", + "v2-grey-400": "#dbdbdbff", + "v2-grey-500": "#aeaeaeff", + "v2-grey-600": "#808080ff", + "v2-grey-700": "#5c5c5cff", + "v2-grey-800": "#3a3a3aff", + "v2-grey-900": "#2e2e2eff", + "v2-grey-1000": "#242424ff", + "v2-grey-1100": "#161616ff", + "v2-grey-1200": "#080808ff", + "v2-red-100": "#fcecebff", + "v2-red-200": "#f6d5d3ff", + "v2-red-300": "#f2bbb7ff", + "v2-red-400": "#f29b96ff", + "v2-red-500": "#f17471ff", + "v2-red-600": "#f1484fff", + "v2-red-700": "#d92e3cff", + "v2-red-800": "#b82d35ff", + "v2-red-900": "#97252bff", + "v2-red-1000": "#7a1f23ff", + "v2-red-1100": "#5f1a1cff", + "v2-red-1200": "#461516ff", + "v2-orange-100": "#fdf2edff", + "v2-orange-200": "#ffe7dcff", + "v2-orange-300": "#ffd8c6ff", + "v2-orange-400": "#ffc1a4ff", + "v2-orange-500": "#ffa478ff", + "v2-orange-600": "#ff8648ff", + "v2-orange-700": "#ee7330ff", + "v2-orange-800": "#d16427ff", + "v2-orange-900": "#b35624ff", + "v2-orange-1000": "#954c27ff", + "v2-orange-1100": "#723d22ff", + "v2-orange-1200": "#5a2c14ff", + "v2-yellow-100": "#fefaecff", + "v2-yellow-200": "#fcefd0ff", + "v2-yellow-300": "#f7e5b5ff", + "v2-yellow-400": "#f3da9bff", + "v2-yellow-500": "#f2cf76ff", + "v2-yellow-600": "#f6c251ff", + "v2-yellow-700": "#e7af36ff", + "v2-yellow-800": "#cb9f34ff", + "v2-yellow-900": "#ac8833ff", + "v2-yellow-1000": "#8e7231ff", + "v2-yellow-1100": "#68552bff", + "v2-yellow-1200": "#4b4025ff", + "v2-green-100": "#e7f9eaff", + "v2-green-200": "#d0f0d5ff", + "v2-green-300": "#b8e9c1ff", + "v2-green-400": "#96e3a6ff", + "v2-green-500": "#6bd586ff", + "v2-green-600": "#49c970ff", + "v2-green-700": "#2eaf5aff", + "v2-green-800": "#198b43ff", + "v2-green-900": "#1d783cff", + "v2-green-1000": "#196130ff", + "v2-green-1100": "#164c26ff", + "v2-green-1200": "#14361dff", + "v2-cyan-100": "#e2f7fbff", + "v2-cyan-200": "#c4edf4ff", + "v2-cyan-300": "#a3e4efff", + "v2-cyan-400": "#65d9ebff", + "v2-cyan-500": "#00c5dfff", + "v2-cyan-600": "#00abcfff", + "v2-cyan-700": "#0096b8ff", + "v2-cyan-800": "#007d9bff", + "v2-cyan-900": "#006c85ff", + "v2-cyan-1000": "#005a6eff", + "v2-cyan-1100": "#004756ff", + "v2-cyan-1200": "#00353fff", + "v2-blue-100": "#ecf1feff", + "v2-blue-200": "#d7e2fcff", + "v2-blue-300": "#c3d4fdff", + "v2-blue-400": "#a2bcffff", + "v2-blue-500": "#7698fdff", + "v2-blue-600": "#3b5cf6ff", + "v2-blue-700": "#3250dfff", + "v2-blue-800": "#2c47c8ff", + "v2-blue-900": "#263fa9ff", + "v2-blue-1000": "#22388fff", + "v2-blue-1100": "#1c2e70ff", + "v2-blue-1200": "#1b2852ff", + "v2-purple-100": "#ebecfeff", + "v2-purple-200": "#d5d5fcff", + "v2-purple-300": "#b9b8f5ff", + "v2-purple-400": "#9e99f7ff", + "v2-purple-500": "#8271f8ff", + "v2-purple-600": "#7152f4ff", + "v2-purple-700": "#623be2ff", + "v2-purple-800": "#5230c2ff", + "v2-purple-900": "#442aa1ff", + "v2-purple-1000": "#361f83ff", + "v2-purple-1100": "#2b1b6aff", + "v2-purple-1200": "#221358ff", + "v2-pink-100": "#fdecf3ff", + "v2-pink-200": "#f7d5e4ff", + "v2-pink-300": "#fabcd8ff", + "v2-pink-400": "#f799c6ff", + "v2-pink-500": "#f26cb2ff", + "v2-pink-600": "#f64aabff", + "v2-pink-700": "#e4429eff", + "v2-pink-800": "#c83d8bff", + "v2-pink-900": "#aa3576ff", + "v2-pink-1000": "#8c2d61ff", + "v2-pink-1100": "#6f284fff", + "v2-pink-1200": "#5c1d3fff", + "v2-background-bg-base": "var(--v2-grey-50)", + "v2-background-bg-deep": "var(--v2-grey-100)", + "v2-background-bg-layer-01": "var(--v2-grey-100)", + "v2-background-bg-layer-02": "var(--v2-grey-200)", + "v2-background-bg-layer-03": "var(--v2-grey-300)", + "v2-background-bg-layer-04": "var(--v2-grey-400)", + "v2-background-bg-inverse": "var(--v2-grey-1100)", + "v2-background-bg-contrast": "var(--v2-grey-1000)", + "v2-background-bg-button-neutral": "var(--v2-grey-50)", + "v2-background-bg-accent": "var(--v2-blue-600)", + "v2-text-text-base": "var(--v2-grey-1100)", + "v2-text-text-muted": "var(--v2-grey-700)", + "v2-text-text-faint": "var(--v2-grey-600)", + "v2-text-text-inverse": "var(--v2-grey-50)", + "v2-text-text-contrast": "var(--v2-grey-50)", + "v2-text-text-accent": "var(--v2-blue-600)", + "v2-text-text-accent-hover": "var(--v2-blue-700)", + "v2-text-text-code-accent": "var(--v2-blue-900)", + "v2-icon-icon-base": "var(--v2-grey-800)", + "v2-icon-icon-muted": "var(--v2-grey-600)", + "v2-icon-icon-inverse": "var(--v2-grey-50)", + "v2-icon-icon-contrast": "var(--v2-grey-100)", + "v2-icon-icon-accent": "var(--v2-blue-600)", + "v2-icon-icon-accent-hover": "var(--v2-blue-700)", + "v2-border-border-muted": "var(--v2-alpha-dark-8)", + "v2-border-border-base": "var(--v2-alpha-dark-10)", + "v2-border-border-strong": "var(--v2-alpha-dark-20)", + "v2-border-border-inverse": "var(--v2-grey-1000)", + "v2-border-border-focus": "var(--v2-blue-500)", + "v2-overlay-simple-overlay-hover": "var(--v2-alpha-dark-4)", + "v2-overlay-simple-overlay-pressed": "var(--v2-alpha-dark-8)", + "v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-light-12)", + "v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-light-24)", + "v2-overlay-simple-overlay-scrim": "var(--v2-alpha-dark-40)", + "v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)", + "v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)", + "v2-overlay-simple-tab-active-scrim": "#fafafa00", + "v2-overlay-simple-tab-hover-scrim": "#eeeeee00", + "v2-overlay-simple-tab-scrim": "#fafafa00", + "v2-state-bg-success": "var(--v2-green-100)", + "v2-state-fg-success": "var(--v2-green-800)", + "v2-state-border-success": "var(--v2-green-300)", + "v2-state-bg-warning": "var(--v2-yellow-100)", + "v2-state-fg-warning": "var(--v2-yellow-800)", + "v2-state-border-warning": "var(--v2-yellow-300)", + "v2-state-bg-danger": "var(--v2-red-100)", + "v2-state-fg-danger": "var(--v2-red-800)", + "v2-state-border-danger": "var(--v2-red-300)", + "v2-state-bg-info": "var(--v2-blue-100)", + "v2-state-fg-info": "var(--v2-blue-800)", + "v2-state-border-info": "var(--v2-blue-300)", + "v2-avatar-bg-orange": "#ee7330ff", + "v2-avatar-border-orange": "#d16427ff", + "v2-avatar-bg-yellow": "#e7af36ff", + "v2-avatar-border-yellow": "#cb9f34ff", + "v2-avatar-bg-cyan": "#0096b8ff", + "v2-avatar-border-cyan": "#007d9bff", + "v2-avatar-bg-green": "#2eaf5aff", + "v2-avatar-border-green": "#198b43ff", + "v2-avatar-bg-red": "#d92e3cff", + "v2-avatar-border-red": "#b82d35ff", + "v2-avatar-bg-pink": "#e4429eff", + "v2-avatar-border-pink": "#c83d8bff", + "v2-avatar-bg-blue": "#3250dfff", + "v2-avatar-border-blue": "#2c47c8ff", + "v2-avatar-bg-purple": "#623be2ff", + "v2-avatar-border-purple": "#5230c2ff", + "v2-avatar-bg-gray": "#5c5c5cff", + "v2-avatar-border-gray": "#3a3a3aff", + "v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-button-neutral": "0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-button-contrast": "0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)", + "v2-elevation-switch-off": "inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)", + "v2-elevation-switch-on": "inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)", + "v2-illustration-illustration-layer-01": "var(--v2-grey-300)", + "v2-illustration-illustration-layer-02": "var(--v2-grey-400)", + "v2-illustration-illustration-layer-03": "var(--v2-grey-500)" + } + }, + "dark": { + "palette": { + "neutral": "#1f1f1f", + "ink": "#f1ece8", + "primary": "#fab283", + "success": "#12c905", + "warning": "#fcd53a", + "error": "#fc533a", + "info": "#edb2f1", + "interactive": "#034cff", + "diffAdd": "#c8ffc4", + "diffDelete": "#fc533a" + }, + "overrides": { + "text-strong": "#EDEDED", + "text-base": "#A0A0A0", + "text-weak": "#707070", + "text-weaker": "#505050", + "text-diff-add-base": "var(--v2-state-fg-success)", + "text-diff-delete-base": "var(--v2-state-fg-danger)", + "border-weak-base": "#282828", + "border-weaker-base": "#232323", + "icon-base": "#7E7E7E", + "icon-weak-base": "#343434", + "surface-raised-base": "#232323", + "surface-raised-base-hover": "#282828", + "surface-base": "#1C1C1C", + "surface-base-hover": "#FFFFFF0D", + "surface-interactive-weak": "#0D172B", + "surface-success-base": "#022B00", + "syntax-comment": "var(--v2-text-text-muted)", + "syntax-keyword": "var(--v2-pink-400)", + "syntax-string": "var(--v2-green-400)", + "syntax-primitive": "var(--v2-pink-400)", + "syntax-property": "var(--v2-orange-400)", + "syntax-type": "var(--v2-purple-400)", + "syntax-constant": "#93e9f6", + "syntax-critical": "var(--v2-red-400)", + "syntax-diff-add": "var(--v2-state-fg-success)", + "syntax-diff-delete": "var(--v2-state-fg-danger)", + "syntax-diff-unknown": "#edb2f1", + "surface-critical-base": "#1F0603" + }, + "v2Overrides": { + "v2-grey-50": "#ffffffff", + "v2-grey-100": "#fafafaff", + "v2-grey-200": "#f2f2f2ff", + "v2-grey-300": "#eeeeeeff", + "v2-grey-400": "#dbdbdbff", + "v2-grey-500": "#aeaeaeff", + "v2-grey-600": "#808080ff", + "v2-grey-700": "#5c5c5cff", + "v2-grey-800": "#3a3a3aff", + "v2-grey-900": "#2e2e2eff", + "v2-grey-1000": "#242424ff", + "v2-grey-1100": "#161616ff", + "v2-grey-1200": "#080808ff", + "v2-red-100": "#fcecebff", + "v2-red-200": "#f6d5d3ff", + "v2-red-300": "#f2bbb7ff", + "v2-red-400": "#f29b96ff", + "v2-red-500": "#f17471ff", + "v2-red-600": "#f1484fff", + "v2-red-700": "#d92e3cff", + "v2-red-800": "#b82d35ff", + "v2-red-900": "#97252bff", + "v2-red-1000": "#7a1f23ff", + "v2-red-1100": "#5f1a1cff", + "v2-red-1200": "#461516ff", + "v2-orange-100": "#fdf2edff", + "v2-orange-200": "#ffe7dcff", + "v2-orange-300": "#ffd8c6ff", + "v2-orange-400": "#ffc1a4ff", + "v2-orange-500": "#ffa478ff", + "v2-orange-600": "#ff8648ff", + "v2-orange-700": "#ee7330ff", + "v2-orange-800": "#d16427ff", + "v2-orange-900": "#b35624ff", + "v2-orange-1000": "#954c27ff", + "v2-orange-1100": "#723d22ff", + "v2-orange-1200": "#5a2c14ff", + "v2-yellow-100": "#fefaecff", + "v2-yellow-200": "#fcefd0ff", + "v2-yellow-300": "#f7e5b5ff", + "v2-yellow-400": "#f3da9bff", + "v2-yellow-500": "#f2cf76ff", + "v2-yellow-600": "#f6c251ff", + "v2-yellow-700": "#e7af36ff", + "v2-yellow-800": "#cb9f34ff", + "v2-yellow-900": "#ac8833ff", + "v2-yellow-1000": "#8e7231ff", + "v2-yellow-1100": "#68552bff", + "v2-yellow-1200": "#4b4025ff", + "v2-green-100": "#e7f9eaff", + "v2-green-200": "#d0f0d5ff", + "v2-green-300": "#b8e9c1ff", + "v2-green-400": "#96e3a6ff", + "v2-green-500": "#6bd586ff", + "v2-green-600": "#49c970ff", + "v2-green-700": "#2eaf5aff", + "v2-green-800": "#198b43ff", + "v2-green-900": "#1d783cff", + "v2-green-1000": "#196130ff", + "v2-green-1100": "#164c26ff", + "v2-green-1200": "#14361dff", + "v2-cyan-100": "#e2f7fbff", + "v2-cyan-200": "#c4edf4ff", + "v2-cyan-300": "#a3e4efff", + "v2-cyan-400": "#65d9ebff", + "v2-cyan-500": "#00c5dfff", + "v2-cyan-600": "#00abcfff", + "v2-cyan-700": "#0096b8ff", + "v2-cyan-800": "#007d9bff", + "v2-cyan-900": "#006c85ff", + "v2-cyan-1000": "#005a6eff", + "v2-cyan-1100": "#004756ff", + "v2-cyan-1200": "#00353fff", + "v2-blue-100": "#ecf1feff", + "v2-blue-200": "#d7e2fcff", + "v2-blue-300": "#c3d4fdff", + "v2-blue-400": "#a2bcffff", + "v2-blue-500": "#7698fdff", + "v2-blue-600": "#3b5cf6ff", + "v2-blue-700": "#3250dfff", + "v2-blue-800": "#2c47c8ff", + "v2-blue-900": "#263fa9ff", + "v2-blue-1000": "#22388fff", + "v2-blue-1100": "#1c2e70ff", + "v2-blue-1200": "#1b2852ff", + "v2-purple-100": "#ebecfeff", + "v2-purple-200": "#d5d5fcff", + "v2-purple-300": "#b9b8f5ff", + "v2-purple-400": "#9e99f7ff", + "v2-purple-500": "#8271f8ff", + "v2-purple-600": "#7152f4ff", + "v2-purple-700": "#623be2ff", + "v2-purple-800": "#5230c2ff", + "v2-purple-900": "#442aa1ff", + "v2-purple-1000": "#361f83ff", + "v2-purple-1100": "#2b1b6aff", + "v2-purple-1200": "#221358ff", + "v2-pink-100": "#fdecf3ff", + "v2-pink-200": "#f7d5e4ff", + "v2-pink-300": "#fabcd8ff", + "v2-pink-400": "#f799c6ff", + "v2-pink-500": "#f26cb2ff", + "v2-pink-600": "#f64aabff", + "v2-pink-700": "#e4429eff", + "v2-pink-800": "#c83d8bff", + "v2-pink-900": "#aa3576ff", + "v2-pink-1000": "#8c2d61ff", + "v2-pink-1100": "#6f284fff", + "v2-pink-1200": "#5c1d3fff", + "v2-background-bg-base": "var(--v2-grey-1100)", + "v2-background-bg-deep": "var(--v2-grey-1200)", + "v2-background-bg-layer-01": "var(--v2-grey-1000)", + "v2-background-bg-layer-02": "var(--v2-grey-900)", + "v2-background-bg-layer-03": "var(--v2-grey-800)", + "v2-background-bg-layer-04": "var(--v2-grey-700)", + "v2-background-bg-inverse": "var(--v2-grey-50)", + "v2-background-bg-contrast": "var(--v2-grey-700)", + "v2-background-bg-button-neutral": "var(--v2-alpha-light-6)", + "v2-background-bg-accent": "var(--v2-blue-600)", + "v2-text-text-base": "var(--v2-grey-100)", + "v2-text-text-muted": "var(--v2-grey-500)", + "v2-text-text-faint": "var(--v2-grey-600)", + "v2-text-text-inverse": "var(--v2-grey-1100)", + "v2-text-text-contrast": "var(--v2-grey-50)", + "v2-text-text-accent": "var(--v2-blue-400)", + "v2-text-text-accent-hover": "var(--v2-blue-300)", + "v2-text-text-code-accent": "var(--v2-blue-400)", + "v2-icon-icon-base": "var(--v2-grey-400)", + "v2-icon-icon-muted": "var(--v2-grey-600)", + "v2-icon-icon-inverse": "var(--v2-grey-1100)", + "v2-icon-icon-contrast": "var(--v2-grey-100)", + "v2-icon-icon-accent": "var(--v2-blue-400)", + "v2-icon-icon-accent-hover": "var(--v2-blue-300)", + "v2-border-border-muted": "var(--v2-alpha-light-8)", + "v2-border-border-base": "var(--v2-alpha-light-10)", + "v2-border-border-strong": "var(--v2-alpha-light-20)", + "v2-border-border-inverse": "var(--v2-grey-100)", + "v2-border-border-focus": "var(--v2-blue-500)", + "v2-overlay-simple-overlay-hover": "var(--v2-alpha-light-6)", + "v2-overlay-simple-overlay-pressed": "var(--v2-alpha-light-10)", + "v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-dark-24)", + "v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-dark-40)", + "v2-overlay-simple-overlay-scrim": "var(--v2-alpha-dark-60)", + "v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)", + "v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)", + "v2-overlay-simple-tab-active-scrim": "#24242400", + "v2-overlay-simple-tab-hover-scrim": "#3a3a3a00", + "v2-overlay-simple-tab-scrim": "#08080800", + "v2-state-bg-success": "var(--v2-green-1200)", + "v2-state-fg-success": "var(--v2-green-500)", + "v2-state-border-success": "var(--v2-green-900)", + "v2-state-bg-warning": "var(--v2-yellow-1200)", + "v2-state-fg-warning": "var(--v2-yellow-500)", + "v2-state-border-warning": "var(--v2-yellow-900)", + "v2-state-bg-danger": "var(--v2-red-1200)", + "v2-state-fg-danger": "var(--v2-red-500)", + "v2-state-border-danger": "var(--v2-red-900)", + "v2-state-bg-info": "var(--v2-blue-1200)", + "v2-state-fg-info": "var(--v2-blue-500)", + "v2-state-border-info": "var(--v2-blue-900)", + "v2-avatar-bg-orange": "#723d22ff", + "v2-avatar-border-orange": "#ff8648ff", + "v2-avatar-bg-yellow": "#68552bff", + "v2-avatar-border-yellow": "#e7af36ff", + "v2-avatar-bg-cyan": "#005a6eff", + "v2-avatar-border-cyan": "#0096b8ff", + "v2-avatar-bg-green": "#196130ff", + "v2-avatar-border-green": "#49c970ff", + "v2-avatar-bg-red": "#7a1f23ff", + "v2-avatar-border-red": "#d92e3cff", + "v2-avatar-bg-pink": "#8c2d61ff", + "v2-avatar-border-pink": "#e4429eff", + "v2-avatar-bg-blue": "#263fa9ff", + "v2-avatar-border-blue": "#7698fdff", + "v2-avatar-bg-purple": "#361f83ff", + "v2-avatar-border-purple": "#7152f4ff", + "v2-avatar-bg-gray": "#5c5c5cff", + "v2-avatar-border-gray": "#aeaeaeff", + "v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-button-neutral": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)", + "v2-elevation-button-contrast": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)", + "v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)", + "v2-elevation-switch-off": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)", + "v2-elevation-switch-on": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)", + "v2-illustration-illustration-layer-01": "var(--v2-grey-900)", + "v2-illustration-illustration-layer-02": "var(--v2-grey-800)", + "v2-illustration-illustration-layer-03": "var(--v2-grey-700)" + } + } +} diff --git a/packages/ui/src/theme/themes/one-dark.json b/packages/ui/src/theme/themes/one-dark.json new file mode 100644 index 0000000000000000000000000000000000000000..0017bd6f512431c1f63e6fde4247cf359cbd9951 --- /dev/null +++ b/packages/ui/src/theme/themes/one-dark.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "One Dark", + "id": "one-dark", + "light": { + "palette": { + "neutral": "#fafafa", + "ink": "#383a42", + "primary": "#4078f2", + "accent": "#0184bc", + "success": "#50a14f", + "warning": "#c18401", + "error": "#e45649", + "info": "#986801", + "diffAdd": "#489447", + "diffDelete": "#d65145" + }, + "overrides": { + "text-weak": "#a0a1a7", + "syntax-comment": "#a0a1a7", + "syntax-keyword": "#a626a4", + "syntax-string": "#50a14f", + "syntax-primitive": "#4078f2", + "syntax-variable": "#e45649", + "syntax-property": "#0184bc", + "syntax-type": "#c18401", + "syntax-constant": "#986801", + "syntax-operator": "#0184bc", + "syntax-punctuation": "#383a42", + "syntax-object": "#e45649", + "markdown-heading": "#a626a4", + "markdown-text": "#383a42", + "markdown-link": "#4078f2", + "markdown-link-text": "#0184bc", + "markdown-code": "#50a14f", + "markdown-block-quote": "#a0a1a7", + "markdown-emph": "#c18401", + "markdown-strong": "#986801", + "markdown-horizontal-rule": "#a0a1a7", + "markdown-list-item": "#4078f2", + "markdown-list-enumeration": "#0184bc", + "markdown-image": "#4078f2", + "markdown-image-text": "#0184bc", + "markdown-code-block": "#383a42" + } + }, + "dark": { + "palette": { + "neutral": "#282c34", + "ink": "#abb2bf", + "primary": "#61afef", + "accent": "#56b6c2", + "success": "#98c379", + "warning": "#e5c07b", + "error": "#e06c75", + "info": "#d19a66", + "diffAdd": "#aad482", + "diffDelete": "#e8828b" + }, + "overrides": { + "text-weak": "#5c6370", + "syntax-comment": "#5c6370", + "syntax-keyword": "#c678dd", + "syntax-string": "#98c379", + "syntax-primitive": "#61afef", + "syntax-variable": "#e06c75", + "syntax-property": "#56b6c2", + "syntax-type": "#e5c07b", + "syntax-constant": "#d19a66", + "syntax-operator": "#56b6c2", + "syntax-punctuation": "#abb2bf", + "syntax-object": "#e06c75", + "markdown-heading": "#c678dd", + "markdown-text": "#abb2bf", + "markdown-link": "#61afef", + "markdown-link-text": "#56b6c2", + "markdown-code": "#98c379", + "markdown-block-quote": "#5c6370", + "markdown-emph": "#e5c07b", + "markdown-strong": "#d19a66", + "markdown-horizontal-rule": "#5c6370", + "markdown-list-item": "#61afef", + "markdown-list-enumeration": "#56b6c2", + "markdown-image": "#61afef", + "markdown-image-text": "#56b6c2", + "markdown-code-block": "#abb2bf" + } + } +} diff --git a/packages/ui/src/theme/themes/onedarkpro.json b/packages/ui/src/theme/themes/onedarkpro.json new file mode 100644 index 0000000000000000000000000000000000000000..44b0f3d27fd506e31bdf17779c77cface8ab1a09 --- /dev/null +++ b/packages/ui/src/theme/themes/onedarkpro.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "One Dark Pro", + "id": "onedarkpro", + "light": { + "palette": { + "neutral": "#f5f6f8", + "ink": "#2b303b", + "primary": "#528bff", + "accent": "#d85462", + "success": "#4fa66d", + "warning": "#d19a66", + "error": "#e06c75", + "info": "#61afef", + "diffAdd": "#c2ebcf", + "diffDelete": "#f7c1c5" + }, + "overrides": { + "syntax-comment": "#6a717d", + "syntax-keyword": "#a626a4", + "syntax-primitive": "#4078f2", + "syntax-constant": "#986801" + } + }, + "dark": { + "palette": { + "neutral": "#1e222a", + "ink": "#abb2bf", + "primary": "#61afef", + "accent": "#e06c75", + "success": "#98c379", + "warning": "#e5c07b", + "error": "#e06c75", + "info": "#56b6c2", + "diffAdd": "#4b815a", + "diffDelete": "#b2555f" + }, + "overrides": { + "syntax-comment": "#5c6370", + "syntax-keyword": "#c678dd", + "syntax-primitive": "#61afef", + "syntax-constant": "#d19a66" + } + } +} diff --git a/packages/ui/src/theme/themes/opencode.json b/packages/ui/src/theme/themes/opencode.json new file mode 100644 index 0000000000000000000000000000000000000000..de7ab19e56e3c4c8c17dcd495009fc5f0b7be145 --- /dev/null +++ b/packages/ui/src/theme/themes/opencode.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "OpenCode", + "id": "opencode", + "light": { + "palette": { + "neutral": "#ffffff", + "ink": "#1a1a1a", + "primary": "#3b7dd8", + "accent": "#d68c27", + "success": "#3d9a57", + "warning": "#d68c27", + "error": "#d1383d", + "info": "#318795", + "diffAdd": "#4db380", + "diffDelete": "#f52a65" + }, + "overrides": { + "text-weak": "#8a8a8a", + "syntax-comment": "#8a8a8a", + "syntax-keyword": "#d68c27", + "syntax-string": "#3d9a57", + "syntax-primitive": "#3b7dd8", + "syntax-variable": "#d1383d", + "syntax-property": "#318795", + "syntax-type": "#b0851f", + "syntax-constant": "#d68c27", + "syntax-operator": "#318795", + "syntax-punctuation": "#1a1a1a", + "syntax-object": "#d1383d", + "markdown-heading": "#d68c27", + "markdown-text": "#1a1a1a", + "markdown-link": "#3b7dd8", + "markdown-link-text": "#318795", + "markdown-code": "#3d9a57", + "markdown-block-quote": "#b0851f", + "markdown-emph": "#b0851f", + "markdown-strong": "#d68c27", + "markdown-horizontal-rule": "#8a8a8a", + "markdown-list-item": "#3b7dd8", + "markdown-list-enumeration": "#318795", + "markdown-image": "#3b7dd8", + "markdown-image-text": "#318795", + "markdown-code-block": "#1a1a1a" + } + }, + "dark": { + "palette": { + "neutral": "#0a0a0a", + "ink": "#eeeeee", + "primary": "#fab283", + "accent": "#9d7cd8", + "success": "#7fd88f", + "warning": "#f5a742", + "error": "#e06c75", + "info": "#56b6c2", + "diffAdd": "#b8db87", + "diffDelete": "#e26a75" + }, + "overrides": { + "text-weak": "#808080", + "syntax-comment": "#808080", + "syntax-keyword": "#9d7cd8", + "syntax-string": "#7fd88f", + "syntax-primitive": "#fab283", + "syntax-variable": "#e06c75", + "syntax-property": "#56b6c2", + "syntax-type": "#e5c07b", + "syntax-constant": "#f5a742", + "syntax-operator": "#56b6c2", + "syntax-punctuation": "#eeeeee", + "syntax-object": "#e06c75", + "markdown-heading": "#9d7cd8", + "markdown-text": "#eeeeee", + "markdown-link": "#fab283", + "markdown-link-text": "#56b6c2", + "markdown-code": "#7fd88f", + "markdown-block-quote": "#e5c07b", + "markdown-emph": "#e5c07b", + "markdown-strong": "#f5a742", + "markdown-horizontal-rule": "#808080", + "markdown-list-item": "#fab283", + "markdown-list-enumeration": "#56b6c2", + "markdown-image": "#fab283", + "markdown-image-text": "#56b6c2", + "markdown-code-block": "#eeeeee" + } + } +} diff --git a/packages/ui/src/theme/themes/orng.json b/packages/ui/src/theme/themes/orng.json new file mode 100644 index 0000000000000000000000000000000000000000..95bf19e9ffc9539a3817d2a3b6eae4af319ebfda --- /dev/null +++ b/packages/ui/src/theme/themes/orng.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Orng", + "id": "orng", + "light": { + "palette": { + "neutral": "#ffffff", + "ink": "#1a1a1a", + "primary": "#EC5B2B", + "accent": "#c94d24", + "success": "#0062d1", + "warning": "#EC5B2B", + "error": "#d1383d", + "info": "#318795", + "diffDelete": "#f52a65" + }, + "overrides": { + "text-weak": "#8a8a8a", + "syntax-comment": "#8a8a8a", + "syntax-keyword": "#EC5B2B", + "syntax-string": "#0062d1", + "syntax-primitive": "#c94d24", + "syntax-variable": "#d1383d", + "syntax-property": "#318795", + "syntax-type": "#b0851f", + "syntax-constant": "#EC5B2B", + "syntax-operator": "#318795", + "syntax-punctuation": "#1a1a1a", + "syntax-object": "#d1383d", + "markdown-heading": "#EC5B2B", + "markdown-text": "#1a1a1a", + "markdown-link": "#EC5B2B", + "markdown-link-text": "#318795", + "markdown-code": "#0062d1", + "markdown-block-quote": "#b0851f", + "markdown-emph": "#b0851f", + "markdown-strong": "#EC5B2B", + "markdown-horizontal-rule": "#8a8a8a", + "markdown-list-item": "#EC5B2B", + "markdown-list-enumeration": "#318795", + "markdown-image": "#EC5B2B", + "markdown-image-text": "#318795", + "markdown-code-block": "#1a1a1a" + } + }, + "dark": { + "palette": { + "neutral": "#0a0a0a", + "ink": "#eeeeee", + "primary": "#EC5B2B", + "accent": "#FFF7F1", + "success": "#6ba1e6", + "warning": "#EC5B2B", + "error": "#e06c75", + "info": "#56b6c2", + "diffDelete": "#e26a75" + }, + "overrides": { + "text-weak": "#808080", + "syntax-comment": "#808080", + "syntax-keyword": "#EC5B2B", + "syntax-string": "#6ba1e6", + "syntax-primitive": "#EE7948", + "syntax-variable": "#e06c75", + "syntax-property": "#56b6c2", + "syntax-type": "#e5c07b", + "syntax-constant": "#FFF7F1", + "syntax-operator": "#56b6c2", + "syntax-punctuation": "#eeeeee", + "syntax-object": "#e06c75", + "markdown-heading": "#EC5B2B", + "markdown-text": "#eeeeee", + "markdown-link": "#EC5B2B", + "markdown-link-text": "#56b6c2", + "markdown-code": "#6ba1e6", + "markdown-block-quote": "#FFF7F1", + "markdown-emph": "#e5c07b", + "markdown-strong": "#EE7948", + "markdown-horizontal-rule": "#808080", + "markdown-list-item": "#EC5B2B", + "markdown-list-enumeration": "#56b6c2", + "markdown-image": "#EC5B2B", + "markdown-image-text": "#56b6c2", + "markdown-code-block": "#eeeeee" + } + } +} diff --git a/packages/ui/src/theme/themes/osaka-jade.json b/packages/ui/src/theme/themes/osaka-jade.json new file mode 100644 index 0000000000000000000000000000000000000000..2a0689c286180d17a545826bf804b5fe9ff7bde6 --- /dev/null +++ b/packages/ui/src/theme/themes/osaka-jade.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Osaka Jade", + "id": "osaka-jade", + "light": { + "palette": { + "neutral": "#F6F5DD", + "ink": "#111c18", + "primary": "#1faa90", + "accent": "#3d7a52", + "success": "#3d7a52", + "warning": "#b5a020", + "error": "#c7392d", + "info": "#1faa90" + }, + "overrides": { + "text-weak": "#53685B", + "syntax-comment": "#53685B", + "syntax-keyword": "#1faa90", + "syntax-string": "#3d7a52", + "syntax-primitive": "#3d7560", + "syntax-variable": "#111c18", + "syntax-property": "#3d7a52", + "syntax-type": "#3d7a52", + "syntax-constant": "#a8527a", + "syntax-operator": "#b5a020", + "syntax-punctuation": "#111c18", + "syntax-object": "#111c18", + "markdown-heading": "#1faa90", + "markdown-text": "#111c18", + "markdown-link": "#1faa90", + "markdown-link-text": "#3d7a52", + "markdown-code": "#3d7a52", + "markdown-block-quote": "#53685B", + "markdown-emph": "#a8527a", + "markdown-strong": "#111c18", + "markdown-horizontal-rule": "#53685B", + "markdown-list-item": "#1faa90", + "markdown-list-enumeration": "#1faa90", + "markdown-image": "#1faa90", + "markdown-image-text": "#3d7a52", + "markdown-code-block": "#111c18" + } + }, + "dark": { + "palette": { + "neutral": "#111c18", + "ink": "#C1C497", + "primary": "#2DD5B7", + "accent": "#549e6a", + "success": "#549e6a", + "warning": "#E5C736", + "error": "#FF5345", + "info": "#2DD5B7", + "interactive": "#8CD3CB", + "diffAdd": "#63b07a", + "diffDelete": "#db9f9c" + }, + "overrides": { + "text-weak": "#53685B", + "syntax-comment": "#53685B", + "syntax-keyword": "#2DD5B7", + "syntax-string": "#63b07a", + "syntax-primitive": "#509475", + "syntax-variable": "#C1C497", + "syntax-property": "#549e6a", + "syntax-type": "#549e6a", + "syntax-constant": "#D2689C", + "syntax-operator": "#459451", + "syntax-punctuation": "#C1C497", + "syntax-object": "#C1C497", + "markdown-heading": "#2DD5B7", + "markdown-text": "#C1C497", + "markdown-link": "#8CD3CB", + "markdown-link-text": "#549e6a", + "markdown-code": "#63b07a", + "markdown-block-quote": "#53685B", + "markdown-emph": "#D2689C", + "markdown-strong": "#C1C497", + "markdown-horizontal-rule": "#53685B", + "markdown-list-item": "#2DD5B7", + "markdown-list-enumeration": "#8CD3CB", + "markdown-image": "#8CD3CB", + "markdown-image-text": "#549e6a", + "markdown-code-block": "#C1C497" + } + } +} diff --git a/packages/ui/src/theme/themes/palenight.json b/packages/ui/src/theme/themes/palenight.json new file mode 100644 index 0000000000000000000000000000000000000000..15463c9c81bda1c06d3aa5a099bfe8273d747ed3 --- /dev/null +++ b/packages/ui/src/theme/themes/palenight.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Palenight", + "id": "palenight", + "light": { + "palette": { + "neutral": "#fafafa", + "ink": "#292d3e", + "primary": "#4976eb", + "accent": "#00acc1", + "success": "#91b859", + "warning": "#ffb300", + "error": "#e53935", + "info": "#f4511e" + }, + "overrides": { + "text-weak": "#8796b0", + "syntax-comment": "#8796b0", + "syntax-keyword": "#a854f2", + "syntax-string": "#91b859", + "syntax-primitive": "#4976eb", + "syntax-variable": "#292d3e", + "syntax-property": "#00acc1", + "syntax-type": "#ffb300", + "syntax-constant": "#f4511e", + "syntax-operator": "#00acc1", + "syntax-punctuation": "#292d3e", + "syntax-object": "#292d3e", + "markdown-heading": "#a854f2", + "markdown-text": "#292d3e", + "markdown-link": "#4976eb", + "markdown-link-text": "#00acc1", + "markdown-code": "#91b859", + "markdown-block-quote": "#8796b0", + "markdown-emph": "#ffb300", + "markdown-strong": "#f4511e", + "markdown-horizontal-rule": "#8796b0", + "markdown-list-item": "#4976eb", + "markdown-list-enumeration": "#00acc1", + "markdown-image": "#4976eb", + "markdown-image-text": "#00acc1", + "markdown-code-block": "#292d3e" + } + }, + "dark": { + "palette": { + "neutral": "#292d3e", + "ink": "#a6accd", + "primary": "#82aaff", + "accent": "#89ddff", + "success": "#c3e88d", + "warning": "#ffcb6b", + "error": "#f07178", + "info": "#f78c6c" + }, + "overrides": { + "text-weak": "#676e95", + "syntax-comment": "#676e95", + "syntax-keyword": "#c792ea", + "syntax-string": "#c3e88d", + "syntax-primitive": "#82aaff", + "syntax-variable": "#a6accd", + "syntax-property": "#89ddff", + "syntax-type": "#ffcb6b", + "syntax-constant": "#f78c6c", + "syntax-operator": "#89ddff", + "syntax-punctuation": "#a6accd", + "syntax-object": "#a6accd", + "markdown-heading": "#c792ea", + "markdown-text": "#a6accd", + "markdown-link": "#82aaff", + "markdown-link-text": "#89ddff", + "markdown-code": "#c3e88d", + "markdown-block-quote": "#676e95", + "markdown-emph": "#ffcb6b", + "markdown-strong": "#f78c6c", + "markdown-horizontal-rule": "#676e95", + "markdown-list-item": "#82aaff", + "markdown-list-enumeration": "#89ddff", + "markdown-image": "#82aaff", + "markdown-image-text": "#89ddff", + "markdown-code-block": "#a6accd" + } + } +} diff --git a/packages/ui/src/theme/themes/rosepine.json b/packages/ui/src/theme/themes/rosepine.json new file mode 100644 index 0000000000000000000000000000000000000000..a71ad18ce000584f70a1e4795a4d49eaeb33b52c --- /dev/null +++ b/packages/ui/src/theme/themes/rosepine.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Rose Pine", + "id": "rosepine", + "light": { + "palette": { + "neutral": "#faf4ed", + "ink": "#575279", + "primary": "#31748f", + "accent": "#d7827e", + "success": "#286983", + "warning": "#ea9d34", + "error": "#b4637a", + "info": "#56949f" + }, + "overrides": { + "text-weak": "#9893a5", + "syntax-comment": "#9893a5", + "syntax-keyword": "#286983", + "syntax-string": "#ea9d34", + "syntax-primitive": "#d7827e", + "syntax-variable": "#575279", + "syntax-property": "#d7827e", + "syntax-type": "#56949f", + "syntax-constant": "#907aa9", + "syntax-operator": "#797593", + "syntax-punctuation": "#797593", + "syntax-object": "#575279", + "markdown-heading": "#907aa9", + "markdown-text": "#575279", + "markdown-link": "#31748f", + "markdown-link-text": "#d7827e", + "markdown-code": "#286983", + "markdown-block-quote": "#9893a5", + "markdown-emph": "#ea9d34", + "markdown-strong": "#b4637a", + "markdown-horizontal-rule": "#dfdad9", + "markdown-list-item": "#31748f", + "markdown-list-enumeration": "#d7827e", + "markdown-image": "#31748f", + "markdown-image-text": "#d7827e", + "markdown-code-block": "#575279" + } + }, + "dark": { + "palette": { + "neutral": "#191724", + "ink": "#e0def4", + "primary": "#9ccfd8", + "accent": "#ebbcba", + "success": "#31748f", + "warning": "#f6c177", + "error": "#eb6f92", + "info": "#9ccfd8" + }, + "overrides": { + "text-weak": "#6e6a86", + "syntax-comment": "#6e6a86", + "syntax-keyword": "#31748f", + "syntax-string": "#f6c177", + "syntax-primitive": "#ebbcba", + "syntax-variable": "#e0def4", + "syntax-property": "#ebbcba", + "syntax-type": "#9ccfd8", + "syntax-constant": "#c4a7e7", + "syntax-operator": "#908caa", + "syntax-punctuation": "#908caa", + "syntax-object": "#e0def4", + "markdown-heading": "#c4a7e7", + "markdown-text": "#e0def4", + "markdown-link": "#9ccfd8", + "markdown-link-text": "#ebbcba", + "markdown-code": "#31748f", + "markdown-block-quote": "#6e6a86", + "markdown-emph": "#f6c177", + "markdown-strong": "#eb6f92", + "markdown-horizontal-rule": "#403d52", + "markdown-list-item": "#9ccfd8", + "markdown-list-enumeration": "#ebbcba", + "markdown-image": "#9ccfd8", + "markdown-image-text": "#ebbcba", + "markdown-code-block": "#e0def4" + } + } +} diff --git a/packages/ui/src/theme/themes/shadesofpurple.json b/packages/ui/src/theme/themes/shadesofpurple.json new file mode 100644 index 0000000000000000000000000000000000000000..99c299191ed6e1f8a59c7034cc6e5c5641db3e58 --- /dev/null +++ b/packages/ui/src/theme/themes/shadesofpurple.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Shades of Purple", + "id": "shadesofpurple", + "light": { + "palette": { + "neutral": "#f7ebff", + "ink": "#3b2c59", + "primary": "#7a5af8", + "accent": "#ff6bd5", + "success": "#3dd598", + "warning": "#f7c948", + "error": "#ff6bd5", + "info": "#62d4ff", + "diffAdd": "#c8f8da", + "diffDelete": "#ffc3ef" + }, + "overrides": { + "syntax-comment": "#8e4be3", + "syntax-keyword": "#c45f00", + "syntax-string": "#2f8b32", + "syntax-primitive": "#a13bd6", + "syntax-property": "#008fb8", + "syntax-type": "#9d7a00", + "syntax-constant": "#e04d7a" + } + }, + "dark": { + "palette": { + "neutral": "#1a102b", + "ink": "#f5f0ff", + "primary": "#c792ff", + "accent": "#ff7ac6", + "success": "#7be0b0", + "warning": "#ffd580", + "error": "#ff7ac6", + "info": "#7dd4ff", + "diffAdd": "#53c39f", + "diffDelete": "#d85aa0" + }, + "overrides": { + "syntax-comment": "#b362ff", + "syntax-keyword": "#ff9d00", + "syntax-string": "#a5ff90", + "syntax-primitive": "#fb94ff", + "syntax-property": "#9effff", + "syntax-type": "#fad000", + "syntax-constant": "#ff628c" + } + } +} diff --git a/packages/ui/src/theme/themes/solarized.json b/packages/ui/src/theme/themes/solarized.json new file mode 100644 index 0000000000000000000000000000000000000000..98fb4d09802f04fe2b8b8d9028a469459828d2ed --- /dev/null +++ b/packages/ui/src/theme/themes/solarized.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Solarized", + "id": "solarized", + "light": { + "palette": { + "neutral": "#fdf6e3", + "ink": "#586e75", + "primary": "#268bd2", + "accent": "#d33682", + "success": "#859900", + "warning": "#b58900", + "error": "#dc322f", + "info": "#2aa198", + "diffAdd": "#c6dc7a", + "diffDelete": "#f2a1a1" + }, + "overrides": { + "syntax-comment": "#657b83", + "syntax-keyword": "#728600", + "syntax-string": "#1f8f88", + "syntax-primitive": "#268bd2", + "syntax-property": "#268bd2", + "syntax-constant": "#d33682" + } + }, + "dark": { + "palette": { + "neutral": "#002b36", + "ink": "#93a1a1", + "primary": "#6c71c4", + "accent": "#d33682", + "success": "#859900", + "warning": "#b58900", + "error": "#dc322f", + "info": "#2aa198", + "diffAdd": "#4c7654", + "diffDelete": "#c34b4b" + }, + "overrides": { + "syntax-comment": "#586e75", + "syntax-keyword": "#859900", + "syntax-string": "#2aa198", + "syntax-primitive": "#268bd2", + "syntax-property": "#268bd2", + "syntax-constant": "#d33682" + } + } +} diff --git a/packages/ui/src/theme/themes/synthwave84.json b/packages/ui/src/theme/themes/synthwave84.json new file mode 100644 index 0000000000000000000000000000000000000000..ce86ce89f2d3e5cc145b7f8c2a476807f0788ca2 --- /dev/null +++ b/packages/ui/src/theme/themes/synthwave84.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Synthwave '84", + "id": "synthwave84", + "light": { + "palette": { + "neutral": "#fafafa", + "ink": "#262335", + "primary": "#00bcd4", + "accent": "#9c27b0", + "success": "#4caf50", + "warning": "#ff9800", + "error": "#f44336", + "info": "#ff5722" + }, + "overrides": { + "text-weak": "#5c5c8a", + "syntax-comment": "#5c5c8a", + "syntax-keyword": "#e91e63", + "syntax-string": "#ff9800", + "syntax-primitive": "#ff5722", + "syntax-variable": "#262335", + "syntax-property": "#9c27b0", + "syntax-type": "#00bcd4", + "syntax-constant": "#9c27b0", + "syntax-operator": "#e91e63", + "syntax-punctuation": "#262335", + "syntax-object": "#262335", + "markdown-heading": "#e91e63", + "markdown-text": "#262335", + "markdown-link": "#00bcd4", + "markdown-link-text": "#9c27b0", + "markdown-code": "#4caf50", + "markdown-block-quote": "#5c5c8a", + "markdown-emph": "#ff9800", + "markdown-strong": "#ff5722", + "markdown-horizontal-rule": "#e0e0e0", + "markdown-list-item": "#00bcd4", + "markdown-list-enumeration": "#9c27b0", + "markdown-image": "#00bcd4", + "markdown-image-text": "#9c27b0", + "markdown-code-block": "#262335" + } + }, + "dark": { + "palette": { + "neutral": "#262335", + "ink": "#ffffff", + "primary": "#36f9f6", + "accent": "#b084eb", + "success": "#72f1b8", + "warning": "#fede5d", + "error": "#fe4450", + "info": "#ff8b39", + "diffAdd": "#97f1d8", + "diffDelete": "#ff5e5b" + }, + "overrides": { + "text-weak": "#848bbd", + "syntax-comment": "#848bbd", + "syntax-keyword": "#ff7edb", + "syntax-string": "#fede5d", + "syntax-primitive": "#ff8b39", + "syntax-variable": "#ffffff", + "syntax-property": "#b084eb", + "syntax-type": "#36f9f6", + "syntax-constant": "#b084eb", + "syntax-operator": "#ff7edb", + "syntax-punctuation": "#ffffff", + "syntax-object": "#ffffff", + "markdown-heading": "#ff7edb", + "markdown-text": "#ffffff", + "markdown-link": "#36f9f6", + "markdown-link-text": "#b084eb", + "markdown-code": "#72f1b8", + "markdown-block-quote": "#848bbd", + "markdown-emph": "#fede5d", + "markdown-strong": "#ff8b39", + "markdown-horizontal-rule": "#495495", + "markdown-list-item": "#36f9f6", + "markdown-list-enumeration": "#b084eb", + "markdown-image": "#36f9f6", + "markdown-image-text": "#b084eb", + "markdown-code-block": "#ffffff" + } + } +} diff --git a/packages/ui/src/theme/themes/tokyonight.json b/packages/ui/src/theme/themes/tokyonight.json new file mode 100644 index 0000000000000000000000000000000000000000..2193b3f69c6a04b9faf0a37f773a8cf55d794ebf --- /dev/null +++ b/packages/ui/src/theme/themes/tokyonight.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Tokyonight", + "id": "tokyonight", + "light": { + "palette": { + "neutral": "#e1e2e7", + "ink": "#273153", + "primary": "#2e7de9", + "accent": "#b15c00", + "success": "#587539", + "warning": "#8c6c3e", + "error": "#c94060", + "info": "#007197", + "diffAdd": "#4f8f7b", + "diffDelete": "#d05f7c" + }, + "overrides": { + "syntax-comment": "#6b6f7a", + "syntax-keyword": "#9854f1", + "syntax-primitive": "#1f6fd4", + "syntax-property": "#007197", + "syntax-constant": "#b15c00" + } + }, + "dark": { + "palette": { + "neutral": "#1a1b26", + "ink": "#c0caf5", + "primary": "#7aa2f7", + "accent": "#ff9e64", + "success": "#9ece6a", + "warning": "#e0af68", + "error": "#f7768e", + "info": "#7dcfff", + "diffAdd": "#41a6b5", + "diffDelete": "#c34043" + }, + "overrides": { + "syntax-comment": "#565f89", + "syntax-keyword": "#bb9af7", + "syntax-primitive": "#7aa2f7", + "syntax-property": "#7dcfff", + "syntax-constant": "#ff9e64" + } + } +} diff --git a/packages/ui/src/theme/themes/vercel.json b/packages/ui/src/theme/themes/vercel.json new file mode 100644 index 0000000000000000000000000000000000000000..dd0028c7061c9f4f51a73871f25ebbacc09f2da2 --- /dev/null +++ b/packages/ui/src/theme/themes/vercel.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Vercel", + "id": "vercel", + "light": { + "palette": { + "neutral": "#FFFFFF", + "ink": "#171717", + "primary": "#0070F3", + "accent": "#8E4EC6", + "success": "#388E3C", + "warning": "#FF9500", + "error": "#DC3545", + "info": "#0070F3", + "diffAdd": "#46A758", + "diffDelete": "#E5484D" + }, + "overrides": { + "text-weak": "#666666", + "syntax-comment": "#888888", + "syntax-keyword": "#E93D82", + "syntax-string": "#46A758", + "syntax-primitive": "#8E4EC6", + "syntax-variable": "#0070F3", + "syntax-property": "#12A594", + "syntax-type": "#12A594", + "syntax-constant": "#FFB224", + "syntax-operator": "#E93D82", + "syntax-punctuation": "#171717", + "syntax-object": "#0070F3", + "markdown-heading": "#8E4EC6", + "markdown-text": "#171717", + "markdown-link": "#0070F3", + "markdown-link-text": "#12A594", + "markdown-code": "#46A758", + "markdown-block-quote": "#666666", + "markdown-emph": "#FFB224", + "markdown-strong": "#E93D82", + "markdown-horizontal-rule": "#999999", + "markdown-list-item": "#171717", + "markdown-list-enumeration": "#0070F3", + "markdown-image": "#12A594", + "markdown-image-text": "#12A594", + "markdown-code-block": "#171717" + } + }, + "dark": { + "palette": { + "neutral": "#000000", + "ink": "#EDEDED", + "primary": "#0070F3", + "accent": "#8E4EC6", + "success": "#46A758", + "warning": "#FFB224", + "error": "#E5484D", + "info": "#52A8FF", + "interactive": "#52A8FF", + "diffAdd": "#63C46D", + "diffDelete": "#FF6166" + }, + "overrides": { + "text-weak": "#878787", + "syntax-comment": "#878787", + "syntax-keyword": "#F75590", + "syntax-string": "#63C46D", + "syntax-primitive": "#BF7AF0", + "syntax-variable": "#52A8FF", + "syntax-property": "#0AC7AC", + "syntax-type": "#0AC7AC", + "syntax-constant": "#F2A700", + "syntax-operator": "#F75590", + "syntax-punctuation": "#EDEDED", + "syntax-object": "#52A8FF", + "markdown-heading": "#BF7AF0", + "markdown-text": "#EDEDED", + "markdown-link": "#52A8FF", + "markdown-link-text": "#0AC7AC", + "markdown-code": "#63C46D", + "markdown-block-quote": "#878787", + "markdown-emph": "#F2A700", + "markdown-strong": "#F75590", + "markdown-horizontal-rule": "#454545", + "markdown-list-item": "#EDEDED", + "markdown-list-enumeration": "#52A8FF", + "markdown-image": "#0AC7AC", + "markdown-image-text": "#50E3C2", + "markdown-code-block": "#EDEDED" + } + } +} diff --git a/packages/ui/src/theme/themes/vesper.json b/packages/ui/src/theme/themes/vesper.json new file mode 100644 index 0000000000000000000000000000000000000000..1283b61e503f51b57fa84bfc18ecea9aa1109c1a --- /dev/null +++ b/packages/ui/src/theme/themes/vesper.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Vesper", + "id": "vesper", + "light": { + "palette": { + "neutral": "#F0F0F0", + "ink": "#101010", + "primary": "#FFC799", + "accent": "#B30000", + "success": "#99FFE4", + "warning": "#FFC799", + "error": "#FF8080", + "info": "#FFC799", + "diffAdd": "#99FFE4", + "diffDelete": "#FF8080" + }, + "overrides": { + "syntax-comment": "#7a7a7a", + "syntax-keyword": "#6e6e6e", + "syntax-string": "#117e69", + "syntax-primitive": "#8d541c", + "syntax-property": "#101010", + "syntax-type": "#8d541c", + "syntax-constant": "#8d541c" + } + }, + "dark": { + "palette": { + "neutral": "#101010", + "ink": "#FFF", + "primary": "#FFC799", + "accent": "#FF8080", + "success": "#99FFE4", + "warning": "#FFC799", + "error": "#FF8080", + "info": "#FFC799", + "diffAdd": "#99FFE4", + "diffDelete": "#FF8080" + }, + "overrides": { + "syntax-comment": "#8b8b8b", + "syntax-keyword": "#a0a0a0", + "syntax-string": "#99ffe4", + "syntax-primitive": "#ffc799", + "syntax-property": "#ffffff", + "syntax-type": "#ffc799", + "syntax-constant": "#ffc799" + } + } +} diff --git a/packages/ui/src/theme/themes/zenburn.json b/packages/ui/src/theme/themes/zenburn.json new file mode 100644 index 0000000000000000000000000000000000000000..fc642059903053893d9e4a86861c2695dd494af8 --- /dev/null +++ b/packages/ui/src/theme/themes/zenburn.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Zenburn", + "id": "zenburn", + "light": { + "palette": { + "neutral": "#ffffef", + "ink": "#3f3f3f", + "primary": "#5f7f8f", + "accent": "#5f8f8f", + "success": "#5f8f5f", + "warning": "#8f8f5f", + "error": "#8f5f5f", + "info": "#8f7f5f" + }, + "overrides": { + "text-weak": "#6f6f6f", + "syntax-comment": "#5f7f5f", + "syntax-keyword": "#8f8f5f", + "syntax-string": "#8f5f5f", + "syntax-primitive": "#5f7f8f", + "syntax-variable": "#3f3f3f", + "syntax-property": "#5f8f8f", + "syntax-type": "#5f8f8f", + "syntax-constant": "#5f8f5f", + "syntax-operator": "#8f8f5f", + "syntax-punctuation": "#3f3f3f", + "syntax-object": "#3f3f3f", + "markdown-heading": "#8f8f5f", + "markdown-text": "#3f3f3f", + "markdown-link": "#5f7f8f", + "markdown-link-text": "#5f8f8f", + "markdown-code": "#5f8f5f", + "markdown-block-quote": "#6f6f6f", + "markdown-emph": "#8f8f5f", + "markdown-strong": "#8f7f5f", + "markdown-horizontal-rule": "#6f6f6f", + "markdown-list-item": "#5f7f8f", + "markdown-list-enumeration": "#5f8f8f", + "markdown-image": "#5f7f8f", + "markdown-image-text": "#5f8f8f", + "markdown-code-block": "#3f3f3f" + } + }, + "dark": { + "palette": { + "neutral": "#3f3f3f", + "ink": "#dcdccc", + "primary": "#8cd0d3", + "accent": "#93e0e3", + "success": "#7f9f7f", + "warning": "#f0dfaf", + "error": "#cc9393", + "info": "#dfaf8f", + "diffAdd": "#8fb28f", + "diffDelete": "#dca3a3" + }, + "overrides": { + "text-weak": "#9f9f9f", + "syntax-comment": "#7f9f7f", + "syntax-keyword": "#f0dfaf", + "syntax-string": "#cc9393", + "syntax-primitive": "#8cd0d3", + "syntax-variable": "#dcdccc", + "syntax-property": "#93e0e3", + "syntax-type": "#93e0e3", + "syntax-constant": "#8fb28f", + "syntax-operator": "#f0dfaf", + "syntax-punctuation": "#dcdccc", + "syntax-object": "#dcdccc", + "markdown-heading": "#f0dfaf", + "markdown-text": "#dcdccc", + "markdown-link": "#8cd0d3", + "markdown-link-text": "#93e0e3", + "markdown-code": "#7f9f7f", + "markdown-block-quote": "#9f9f9f", + "markdown-emph": "#e0cf9f", + "markdown-strong": "#dfaf8f", + "markdown-horizontal-rule": "#9f9f9f", + "markdown-list-item": "#8cd0d3", + "markdown-list-enumeration": "#93e0e3", + "markdown-image": "#8cd0d3", + "markdown-image-text": "#93e0e3", + "markdown-code-block": "#dcdccc" + } + } +} diff --git a/packages/ui/src/theme/types.ts b/packages/ui/src/theme/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..2284659b48426bc293c744916583260d3c4aa3f5 --- /dev/null +++ b/packages/ui/src/theme/types.ts @@ -0,0 +1,75 @@ +export type HexColor = `#${string}` + +export interface OklchColor { + l: number // Lightness 0-1 + c: number // Chroma 0-0.4+ + h: number // Hue 0-360 +} + +export interface ThemeSeedColors { + neutral: HexColor + primary: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + interactive: HexColor + diffAdd: HexColor + diffDelete: HexColor +} + +export interface ThemePaletteColors { + neutral: HexColor + ink: HexColor + primary: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + accent?: HexColor + interactive?: HexColor + diffAdd?: HexColor + diffDelete?: HexColor +} + +type ThemeVariantBase = { + overrides?: Record + v2Overrides?: Record +} + +export type ThemeVariant = + | ({ seeds: ThemeSeedColors; palette?: never } & ThemeVariantBase) + | ({ palette: ThemePaletteColors; seeds?: never } & ThemeVariantBase) + +export interface DesktopTheme { + $schema?: string + name: string + id: string + light: ThemeVariant + dark: ThemeVariant +} + +export type TokenCategory = + | "background" + | "surface" + | "text" + | "border" + | "icon" + | "input" + | "button" + | "syntax" + | "markdown" + | "diff" + | "avatar" + +export type ThemeToken = string + +export type CssVarRef = `var(--${string})` + +export type ColorValue = HexColor | CssVarRef + +export type V2ColorValue = HexColor | CssVarRef | string + +export type ResolvedTheme = Record + +export type ResolvedV2Theme = Record diff --git a/packages/ui/src/theme/v2/avatar.ts b/packages/ui/src/theme/v2/avatar.ts new file mode 100644 index 0000000000000000000000000000000000000000..52c0cc67acf50e246579eb7d299c849efec015ac --- /dev/null +++ b/packages/ui/src/theme/v2/avatar.ts @@ -0,0 +1,48 @@ +import type { V2ColorValue } from "../types" + +/** Fixed project avatar colors (OC-2); theme-independent like v1 `avatar-background-*`. */ +export const V2_AVATAR_FG = "#ffffffff" + +export const V2_AVATAR_LIGHT: Record = { + "v2-avatar-fg": V2_AVATAR_FG, + "v2-avatar-bg-orange": "#ee7330ff", + "v2-avatar-border-orange": "#d16427ff", + "v2-avatar-bg-yellow": "#e7af36ff", + "v2-avatar-border-yellow": "#cb9f34ff", + "v2-avatar-bg-cyan": "#0096b8ff", + "v2-avatar-border-cyan": "#007d9bff", + "v2-avatar-bg-green": "#2eaf5aff", + "v2-avatar-border-green": "#198b43ff", + "v2-avatar-bg-red": "#d92e3cff", + "v2-avatar-border-red": "#b82d35ff", + "v2-avatar-bg-pink": "#e4429eff", + "v2-avatar-border-pink": "#c83d8bff", + "v2-avatar-bg-blue": "#3250dfff", + "v2-avatar-border-blue": "#2c47c8ff", + "v2-avatar-bg-purple": "#623be2ff", + "v2-avatar-border-purple": "#5230c2ff", + "v2-avatar-bg-gray": "#5c5c5cff", + "v2-avatar-border-gray": "#3a3a3aff", +} + +export const V2_AVATAR_DARK: Record = { + "v2-avatar-fg": V2_AVATAR_FG, + "v2-avatar-bg-orange": "#723d22ff", + "v2-avatar-border-orange": "#ff8648ff", + "v2-avatar-bg-yellow": "#68552bff", + "v2-avatar-border-yellow": "#e7af36ff", + "v2-avatar-bg-cyan": "#005a6eff", + "v2-avatar-border-cyan": "#0096b8ff", + "v2-avatar-bg-green": "#196130ff", + "v2-avatar-border-green": "#49c970ff", + "v2-avatar-bg-red": "#7a1f23ff", + "v2-avatar-border-red": "#d92e3cff", + "v2-avatar-bg-pink": "#8c2d61ff", + "v2-avatar-border-pink": "#e4429eff", + "v2-avatar-bg-blue": "#263fa9ff", + "v2-avatar-border-blue": "#7698fdff", + "v2-avatar-bg-purple": "#361f83ff", + "v2-avatar-border-purple": "#7152f4ff", + "v2-avatar-bg-gray": "#5c5c5cff", + "v2-avatar-border-gray": "#aeaeaeff", +} diff --git a/packages/ui/src/theme/v2/default-primitives.ts b/packages/ui/src/theme/v2/default-primitives.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc0beb467dd128405db5a1e7bb3051008f826ff3 --- /dev/null +++ b/packages/ui/src/theme/v2/default-primitives.ts @@ -0,0 +1,114 @@ +import type { V2ColorValue } from "../types" + +/** Default v2 hue ramps from `v2/styles/colors.css` (OC-2). Alpha ramps live in CSS only. */ +export const V2_PRIMITIVES_DEFAULT: Record = { + "v2-grey-50": "#ffffffff", + "v2-grey-100": "#fafafaff", + "v2-grey-200": "#f2f2f2ff", + "v2-grey-300": "#eeeeeeff", + "v2-grey-400": "#dbdbdbff", + "v2-grey-500": "#aeaeaeff", + "v2-grey-600": "#808080ff", + "v2-grey-700": "#5c5c5cff", + "v2-grey-800": "#3a3a3aff", + "v2-grey-900": "#2e2e2eff", + "v2-grey-1000": "#242424ff", + "v2-grey-1100": "#161616ff", + "v2-grey-1200": "#080808ff", + "v2-red-100": "#fcecebff", + "v2-red-200": "#f6d5d3ff", + "v2-red-300": "#f2bbb7ff", + "v2-red-400": "#f29b96ff", + "v2-red-500": "#f17471ff", + "v2-red-600": "#f1484fff", + "v2-red-700": "#d92e3cff", + "v2-red-800": "#b82d35ff", + "v2-red-900": "#97252bff", + "v2-red-1000": "#7a1f23ff", + "v2-red-1100": "#5f1a1cff", + "v2-red-1200": "#461516ff", + "v2-orange-100": "#fdf2edff", + "v2-orange-200": "#ffe7dcff", + "v2-orange-300": "#ffd8c6ff", + "v2-orange-400": "#ffc1a4ff", + "v2-orange-500": "#ffa478ff", + "v2-orange-600": "#ff8648ff", + "v2-orange-700": "#ee7330ff", + "v2-orange-800": "#d16427ff", + "v2-orange-900": "#b35624ff", + "v2-orange-1000": "#954c27ff", + "v2-orange-1100": "#723d22ff", + "v2-orange-1200": "#5a2c14ff", + "v2-yellow-100": "#fefaecff", + "v2-yellow-200": "#fcefd0ff", + "v2-yellow-300": "#f7e5b5ff", + "v2-yellow-400": "#f3da9bff", + "v2-yellow-500": "#f2cf76ff", + "v2-yellow-600": "#f6c251ff", + "v2-yellow-700": "#e7af36ff", + "v2-yellow-800": "#cb9f34ff", + "v2-yellow-900": "#ac8833ff", + "v2-yellow-1000": "#8e7231ff", + "v2-yellow-1100": "#68552bff", + "v2-yellow-1200": "#4b4025ff", + "v2-green-100": "#e7f9eaff", + "v2-green-200": "#d0f0d5ff", + "v2-green-300": "#b8e9c1ff", + "v2-green-400": "#96e3a6ff", + "v2-green-500": "#6bd586ff", + "v2-green-600": "#49c970ff", + "v2-green-700": "#2eaf5aff", + "v2-green-800": "#198b43ff", + "v2-green-900": "#1d783cff", + "v2-green-1000": "#196130ff", + "v2-green-1100": "#164c26ff", + "v2-green-1200": "#14361dff", + "v2-cyan-100": "#e2f7fbff", + "v2-cyan-200": "#c4edf4ff", + "v2-cyan-300": "#a3e4efff", + "v2-cyan-400": "#65d9ebff", + "v2-cyan-500": "#00c5dfff", + "v2-cyan-600": "#00abcfff", + "v2-cyan-700": "#0096b8ff", + "v2-cyan-800": "#007d9bff", + "v2-cyan-900": "#006c85ff", + "v2-cyan-1000": "#005a6eff", + "v2-cyan-1100": "#004756ff", + "v2-cyan-1200": "#00353fff", + "v2-blue-100": "#ecf1feff", + "v2-blue-200": "#d7e2fcff", + "v2-blue-300": "#c3d4fdff", + "v2-blue-400": "#a2bcffff", + "v2-blue-500": "#7698fdff", + "v2-blue-600": "#3b5cf6ff", + "v2-blue-700": "#3250dfff", + "v2-blue-800": "#2c47c8ff", + "v2-blue-900": "#263fa9ff", + "v2-blue-1000": "#22388fff", + "v2-blue-1100": "#1c2e70ff", + "v2-blue-1200": "#1b2852ff", + "v2-purple-100": "#ebecfeff", + "v2-purple-200": "#d5d5fcff", + "v2-purple-300": "#b9b8f5ff", + "v2-purple-400": "#9e99f7ff", + "v2-purple-500": "#8271f8ff", + "v2-purple-600": "#7152f4ff", + "v2-purple-700": "#623be2ff", + "v2-purple-800": "#5230c2ff", + "v2-purple-900": "#442aa1ff", + "v2-purple-1000": "#361f83ff", + "v2-purple-1100": "#2b1b6aff", + "v2-purple-1200": "#221358ff", + "v2-pink-100": "#fdecf3ff", + "v2-pink-200": "#f7d5e4ff", + "v2-pink-300": "#fabcd8ff", + "v2-pink-400": "#f799c6ff", + "v2-pink-500": "#f26cb2ff", + "v2-pink-600": "#f64aabff", + "v2-pink-700": "#e4429eff", + "v2-pink-800": "#c83d8bff", + "v2-pink-900": "#aa3576ff", + "v2-pink-1000": "#8c2d61ff", + "v2-pink-1100": "#6f284fff", + "v2-pink-1200": "#5c1d3fff", +} diff --git a/packages/ui/src/theme/v2/foreground.ts b/packages/ui/src/theme/v2/foreground.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd946570e1be92c8a46cdccd649ed7ab09324403 --- /dev/null +++ b/packages/ui/src/theme/v2/foreground.ts @@ -0,0 +1,60 @@ +import { blend, contrastRatio, hexToOklch, shift } from "../color" +import { mapV2Semantics } from "./mapping" +import type { ColorValue, HexColor, V2ColorValue } from "../types" + +const GREY_STEPS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200] as const + +const greyRef = (step: number): V2ColorValue => `var(--v2-grey-${step})` + +function greyHex(primitives: Record, step: number) { + const hex = primitives[`v2-grey-${step}`] + if (typeof hex === "string" && hex.startsWith("#")) return hex as HexColor +} + +function resolveGreyRef(value: V2ColorValue, primitives: Record) { + const step = value.match(/^var\(--v2-grey-(\d+)\)$/)?.[1] + if (!step) throw new Error(`Expected grey primitive ref, got ${value}`) + const hex = greyHex(primitives, Number(step)) + if (!hex) throw new Error(`Missing grey primitive v2-grey-${step}`) + return hex +} + +function pickGrey(primitives: Record, background: HexColor, minContrast: number, target: number) { + const matches = GREY_STEPS.filter((step) => { + const hex = greyHex(primitives, step) + return hex && contrastRatio(hex, background) >= minContrast + }) + if (matches.length === 0) return target + return matches.reduce((best, step) => (Math.abs(step - target) < Math.abs(best - target) ? step : best)) +} + +export function mapV2Foreground( + ink: HexColor, + isDark: boolean, + primitives: Record, + overrides: Record = {}, +): Record { + const tint = hexToOklch(ink) + const body = shift(ink, { + l: isDark ? Math.max(0, 0.88 - tint.l) * 0.4 : -Math.max(0, tint.l - 0.18) * 0.24, + c: isDark ? 1.04 : 1.02, + }) + + const semantics = mapV2Semantics(isDark) + const bgBase = resolveGreyRef(semantics["v2-background-bg-base"], primitives) + const bgContrast = resolveGreyRef(semantics["v2-background-bg-contrast"], primitives) + const bgInverse = resolveGreyRef(semantics["v2-background-bg-inverse"], primitives) + const inverseTarget = hexToOklch(bgInverse).l > 0.55 ? 1100 : greyHex(primitives, 50) ? 50 : 100 + + return { + "v2-text-text-base": isDark ? blend("#ffffff", body, 0.9) : shift(body, { l: -0.07, c: 1.04 }), + "v2-text-text-muted": overrides["text-weak"] ?? shift(body, { l: isDark ? -0.11 : 0.11, c: 0.9 }), + "v2-text-text-faint": shift(body, { l: isDark ? -0.2 : 0.21, c: isDark ? 0.78 : 0.72 }), + "v2-icon-icon-base": greyRef(pickGrey(primitives, bgBase, 7, isDark ? 400 : 800)), + "v2-icon-icon-muted": greyRef(pickGrey(primitives, bgBase, 3, 600)), + "v2-icon-icon-inverse": greyRef(pickGrey(primitives, bgInverse, 7, inverseTarget)), + "v2-icon-icon-contrast": greyRef(pickGrey(primitives, bgContrast, 7, 100)), + "v2-icon-icon-accent": isDark ? "var(--v2-blue-400)" : "var(--v2-blue-600)", + "v2-icon-icon-accent-hover": isDark ? "var(--v2-blue-300)" : "var(--v2-blue-700)", + } +} diff --git a/packages/ui/src/theme/v2/mapping.ts b/packages/ui/src/theme/v2/mapping.ts new file mode 100644 index 0000000000000000000000000000000000000000..e019fa466ef1a3573630f081783d22ecbe043d8b --- /dev/null +++ b/packages/ui/src/theme/v2/mapping.ts @@ -0,0 +1,170 @@ +import type { V2ColorValue } from "../types" +import { V2_AVATAR_DARK, V2_AVATAR_LIGHT } from "./avatar" + +const ref = (name: string): V2ColorValue => `var(--${name})` + +const lightAgentTokens: Record = { + "v2-agent-plan-solid": ref("v2-pink-800"), + "v2-agent-plan-border": "rgba(200, 61, 139, 0.20)", + "v2-agent-plan-background": "rgba(253, 236, 243, 0.10)", + "v2-agent-build-solid": ref("v2-blue-800"), + "v2-agent-build-border": "rgba(44, 71, 200, 0.20)", + "v2-agent-build-background": "rgba(236, 241, 254, 0.10)", + "v2-agent-explore-solid": ref("v2-yellow-900"), + "v2-agent-explore-border": "rgba(203, 159, 52, 0.20)", + "v2-agent-explore-background": "rgba(254, 250, 236, 0.1)", + "v2-agent-review-solid": ref("v2-green-800"), + "v2-agent-writer-solid": ref("v2-purple-700"), +} + +const darkAgentTokens: Record = { + "v2-agent-plan-solid": ref("v2-pink-400"), + "v2-agent-plan-border": "rgba(247, 153, 198, 0.20)", + "v2-agent-plan-background": "rgba(170, 53, 118, 0.05)", + "v2-agent-build-solid": ref("v2-blue-300"), + "v2-agent-build-border": "rgba(162, 188, 255, 0.20)", + "v2-agent-build-background": "rgba(38, 63, 169, 0.05)", + "v2-agent-explore-solid": ref("v2-yellow-300"), + "v2-agent-explore-border": "rgba(243, 218, 155, 0.20)", + "v2-agent-explore-background": "rgba(172, 136, 51, 0.05)", + "v2-agent-review-solid": ref("v2-green-300"), + "v2-agent-writer-solid": ref("v2-purple-400"), +} + +const light: Record = { + "v2-background-bg-base": ref("v2-grey-100"), + "v2-background-bg-deep": ref("v2-grey-200"), + "v2-background-bg-layer-01": ref("v2-grey-300"), + "v2-background-bg-layer-02": ref("v2-grey-400"), + "v2-background-bg-layer-03": ref("v2-grey-500"), + "v2-background-bg-layer-04": ref("v2-grey-600"), + "v2-background-bg-inverse": ref("v2-grey-1000"), + "v2-background-bg-contrast": ref("v2-grey-900"), + "v2-background-bg-button-neutral": ref("v2-grey-100"), + "v2-background-bg-accent": ref("v2-blue-600"), + "v2-text-text-inverse": ref("v2-grey-100"), + "v2-text-text-contrast": ref("v2-grey-100"), + "v2-text-text-accent": ref("v2-blue-600"), + "v2-text-text-accent-hover": ref("v2-blue-700"), + "v2-text-text-code-accent": ref("v2-blue-900"), + "v2-border-border-muted": ref("v2-alpha-dark-8"), + "v2-border-border-base": ref("v2-alpha-dark-10"), + "v2-border-border-strong": ref("v2-alpha-dark-20"), + "v2-border-border-inverse": ref("v2-grey-1000"), + "v2-border-border-focus": ref("v2-blue-500"), + "v2-overlay-simple-overlay-hover": ref("v2-alpha-dark-4"), + "v2-overlay-simple-overlay-pressed": ref("v2-alpha-dark-8"), + "v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-light-12"), + "v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-light-24"), + "v2-overlay-simple-overlay-scrim": ref("v2-alpha-dark-40"), + "v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"), + "v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"), + "v2-overlay-simple-tab-active-scrim": "#fafafa00", + "v2-overlay-simple-tab-hover-scrim": "#eeeeee00", + "v2-overlay-simple-tab-scrim": "#fafafa00", + "v2-state-bg-success": ref("v2-green-100"), + "v2-state-fg-success": ref("v2-green-800"), + "v2-state-border-success": ref("v2-green-300"), + "v2-state-bg-warning": ref("v2-yellow-100"), + "v2-state-fg-warning": ref("v2-yellow-800"), + "v2-state-border-warning": ref("v2-yellow-300"), + "v2-state-bg-danger": ref("v2-red-100"), + "v2-state-fg-danger": ref("v2-red-800"), + "v2-state-border-danger": ref("v2-red-300"), + "v2-state-bg-info": ref("v2-blue-100"), + "v2-state-fg-info": ref("v2-blue-800"), + "v2-state-border-info": ref("v2-blue-300"), + ...lightAgentTokens, + ...V2_AVATAR_LIGHT, + "v2-elevation-raised": + "0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-floating": + "0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-overlay": + "0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-button-neutral": + "0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-button-contrast": + "0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)", + "v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)", + "v2-elevation-switch-off": + "inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)", + "v2-elevation-switch-on": + "inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)", + "v2-illustration-illustration-layer-01": ref("v2-grey-300"), + "v2-illustration-illustration-layer-02": ref("v2-grey-400"), + "v2-illustration-illustration-layer-03": ref("v2-grey-500"), +} + +const dark: Record = { + "v2-background-bg-base": ref("v2-grey-1000"), + "v2-background-bg-deep": ref("v2-grey-1100"), + "v2-background-bg-layer-01": ref("v2-grey-800"), + "v2-background-bg-layer-02": ref("v2-grey-600"), + "v2-background-bg-layer-03": ref("v2-grey-500"), + "v2-background-bg-layer-04": ref("v2-grey-400"), + "v2-background-bg-inverse": ref("v2-grey-100"), + "v2-background-bg-contrast": ref("v2-grey-700"), + "v2-background-bg-button-neutral": ref("v2-alpha-light-6"), + "v2-background-bg-accent": ref("v2-blue-600"), + "v2-text-text-inverse": ref("v2-grey-1000"), + "v2-text-text-contrast": ref("v2-grey-100"), + "v2-text-text-accent": ref("v2-blue-400"), + "v2-text-text-accent-hover": ref("v2-blue-300"), + "v2-text-text-code-accent": ref("v2-blue-400"), + "v2-border-border-muted": ref("v2-alpha-light-8"), + "v2-border-border-base": ref("v2-alpha-light-10"), + "v2-border-border-strong": ref("v2-alpha-light-20"), + "v2-border-border-inverse": ref("v2-grey-100"), + "v2-border-border-focus": ref("v2-blue-500"), + "v2-overlay-simple-overlay-hover": ref("v2-alpha-light-6"), + "v2-overlay-simple-overlay-pressed": ref("v2-alpha-light-10"), + "v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-dark-24"), + "v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-dark-40"), + "v2-overlay-simple-overlay-scrim": ref("v2-alpha-dark-60"), + "v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"), + "v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"), + "v2-overlay-simple-tab-active-scrim": "#24242400", + "v2-overlay-simple-tab-hover-scrim": "#3a3a3a00", + "v2-overlay-simple-tab-scrim": "#08080800", + "v2-state-bg-success": ref("v2-green-1200"), + "v2-state-fg-success": ref("v2-green-500"), + "v2-state-border-success": ref("v2-green-900"), + "v2-state-bg-warning": ref("v2-yellow-1200"), + "v2-state-fg-warning": ref("v2-yellow-500"), + "v2-state-border-warning": ref("v2-yellow-900"), + "v2-state-bg-danger": ref("v2-red-1200"), + "v2-state-fg-danger": ref("v2-red-500"), + "v2-state-border-danger": ref("v2-red-900"), + "v2-state-bg-info": ref("v2-blue-1200"), + "v2-state-fg-info": ref("v2-blue-500"), + "v2-state-border-info": ref("v2-blue-900"), + ...darkAgentTokens, + ...V2_AVATAR_DARK, + "v2-elevation-raised": + "0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-floating": + "0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-overlay": + "0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)", + "v2-elevation-button-neutral": + "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)", + "v2-elevation-button-contrast": + "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)", + "v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)", + "v2-elevation-switch-off": + "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)", + "v2-elevation-switch-on": + "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)", + "v2-illustration-illustration-layer-01": ref("v2-grey-900"), + "v2-illustration-illustration-layer-02": ref("v2-grey-800"), + "v2-illustration-illustration-layer-03": ref("v2-grey-700"), +} + +export function mapV2Semantics(isDark: boolean): Record { + return isDark ? dark : light +} + +export function mergeV2Tokens(...layers: Record[]): Record { + return Object.assign({}, ...layers) +} diff --git a/packages/ui/src/theme/v2/resolve.ts b/packages/ui/src/theme/v2/resolve.ts new file mode 100644 index 0000000000000000000000000000000000000000..b939a97800f131373ed7b142706c1a3f91cb78ad --- /dev/null +++ b/packages/ui/src/theme/v2/resolve.ts @@ -0,0 +1,153 @@ +// @refresh reload + +import { generateNeutralScale, hexToOklch, oklchToHex, shift } from "../color" +import { mapV2Foreground } from "./foreground" +import { mapV2Semantics, mergeV2Tokens } from "./mapping" +import type { DesktopTheme, HexColor, ResolvedV2Theme, ThemeVariant, V2ColorValue } from "../types" +import { V2_PRIMITIVES_DEFAULT } from "./default-primitives" + +const V2_STEPS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200] as const + +interface PaletteInput { + neutral: HexColor + ink: HexColor + primary: HexColor + accent: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + interactive: HexColor + diffAdd: HexColor + diffDelete: HexColor +} + +function clamp(v: number, min: number, max: number) { + return Math.max(min, Math.min(max, v)) +} + +/** v2 ramps: 100 = lightest, 1200 = darkest — wider spread than v1 `generateScale`. */ +function generateV2HueScale(seed: HexColor, isDark: boolean): HexColor[] { + const base = hexToOklch(seed) + const chromaBoost = isDark ? 1 : 1.05 + const lightSteps = [ + 0.99, + 0.965, + 0.93, + 0.885, + 0.835, + clamp(base.l, 0.48, 0.72), + clamp(base.l - 0.07, 0.4, 0.64), + clamp(base.l - 0.14, 0.32, 0.55), + clamp(base.l - 0.21, 0.24, 0.46), + clamp(base.l - 0.28, 0.17, 0.38), + clamp(base.l - 0.34, 0.12, 0.3), + clamp(base.l - 0.4, 0.08, 0.22), + ] + const chromaMultipliers = [0.28, 0.48, 0.68, 0.86, 1.02, 1.28, 1.34, 1.28, 1.18, 1.08, 0.98, 0.88] + + return lightSteps.map((l, i) => + oklchToHex({ + l, + c: base.c * chromaMultipliers[i]! * chromaBoost, + h: base.h, + }), + ) +} + +/** Grey ramp: 100 = lightest, 1200 = darkest. Derived from palette neutral → ink like v1. */ +function generateV2NeutralScale(neutral: HexColor, ink: HexColor, isDark: boolean): HexColor[] { + const scale = generateNeutralScale(neutral, isDark, ink) + return isDark ? scale.toReversed() : scale +} + +function assignHueRamp(prefix: string, scale: HexColor[]): Record { + const tokens: Record = {} + for (let i = 0; i < V2_STEPS.length; i++) { + tokens[`v2-${prefix}-${V2_STEPS[i]}`] = scale[i]! + } + return tokens +} + +function readPalette(variant: ThemeVariant): PaletteInput { + if ("palette" in variant && variant.palette) { + const palette = variant.palette + return { + neutral: palette.neutral, + ink: palette.ink, + primary: palette.primary, + accent: palette.accent ?? palette.info, + success: palette.success, + warning: palette.warning, + error: palette.error, + info: palette.info, + interactive: palette.interactive ?? palette.primary, + diffAdd: palette.diffAdd ?? shift(palette.success, { c: 0.55, l: 0.14 }), + diffDelete: palette.diffDelete ?? palette.error, + } + } + if ("seeds" in variant && variant.seeds) { + const seeds = variant.seeds + return { + neutral: seeds.neutral, + ink: seeds.neutral, + primary: seeds.primary, + accent: seeds.info, + success: seeds.success, + warning: seeds.warning, + error: seeds.error, + info: seeds.info, + interactive: seeds.interactive, + diffAdd: seeds.diffAdd, + diffDelete: seeds.diffDelete, + } + } + throw new Error("Theme variant requires `palette` or `seeds`") +} + +/** Build v2 primitive ramps (100 = lightest). Alpha ramps are static in `v2/styles/colors.css`. */ +export function generateV2Primitives(variant: ThemeVariant, isDark: boolean): Record { + const colors = readPalette(variant) + const grey = generateV2NeutralScale(colors.neutral, colors.ink, isDark) + const blue = generateV2HueScale(colors.interactive, isDark) + const green = generateV2HueScale(colors.success, isDark) + const yellow = generateV2HueScale(colors.warning, isDark) + const red = generateV2HueScale(colors.error, isDark) + const purple = generateV2HueScale(colors.accent, isDark) + const pink = generateV2HueScale(colors.info, isDark) + const orange = generateV2HueScale(shift(colors.warning, { h: -22, l: -0.082, c: 0.94 }), isDark) + const cyan = generateV2HueScale(shift(colors.info, { h: -12, l: 0.128, c: 1.12 }), isDark) + + return { + ...V2_PRIMITIVES_DEFAULT, + ...assignHueRamp("grey", grey), + ...assignHueRamp("blue", blue), + ...assignHueRamp("green", green), + ...assignHueRamp("yellow", yellow), + ...assignHueRamp("red", red), + ...assignHueRamp("purple", purple), + ...assignHueRamp("pink", pink), + ...assignHueRamp("orange", orange), + ...assignHueRamp("cyan", cyan), + } +} + +export function resolveThemeVariantV2(variant: ThemeVariant, isDark: boolean): ResolvedV2Theme { + const primitives = generateV2Primitives(variant, isDark) + const semantics = mapV2Semantics(isDark) + const foreground = mapV2Foreground(readPalette(variant).ink, isDark, primitives, variant.overrides) + return mergeV2Tokens(primitives, semantics, foreground, variant.v2Overrides ?? {}) +} + +export function resolveThemeV2(theme: DesktopTheme): { light: ResolvedV2Theme; dark: ResolvedV2Theme } { + return { + light: resolveThemeVariantV2(theme.light, false), + dark: resolveThemeVariantV2(theme.dark, true), + } +} + +export function themeV2ToCss(tokens: ResolvedV2Theme): string { + return Object.entries(tokens) + .map(([key, value]) => `--${key}: ${value};`) + .join("\n ") +} diff --git a/packages/ui/src/v2/components/accordion-v2.css b/packages/ui/src/v2/components/accordion-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..281a031c36e1af50eec9efd216605eb8a8f89870 --- /dev/null +++ b/packages/ui/src/v2/components/accordion-v2.css @@ -0,0 +1,139 @@ +[data-component="accordion-v2"] { + --accordion-v2-fg: var(--v2-text-text-base); + --accordion-v2-icon: var(--v2-icon-icon-base); + --accordion-v2-border: var(--v2-border-border-strong); + --accordion-v2-bg: var(--v2-background-bg-base); + + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: stretch; + + width: 100%; + background: var(--accordion-v2-bg); + border-radius: 6px; + box-shadow: 0 0 0 0.5px var(--accordion-v2-border); + overflow: hidden; + + color: var(--accordion-v2-fg); + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: geometricPrecision; +} + +[data-component="accordion-v2-item"] { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + background: var(--accordion-v2-bg); + box-shadow: inset 0 -0.5px 0 var(--accordion-v2-border); +} + +[data-component="accordion-v2-item"]:last-child { + box-shadow: none; +} + +[data-slot="accordion-v2-header"] { + display: flex; + margin: 0; +} + +[data-component="accordion-v2-trigger"] { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + + width: 100%; + min-height: 30px; + padding: 0 12px; + + background: transparent; + border: 0; + outline: none; + cursor: default; + user-select: none; + + font-size: 13px; + font-weight: 440; + line-height: 100%; + letter-spacing: -0.04px; + color: var(--accordion-v2-fg); + text-align: left; +} + +[data-component="accordion-v2-trigger"] [data-slot="accordion-v2-trigger-content"] { + display: flex; + flex: 1 1 auto; + flex-direction: row; + align-items: center; + gap: 8px; + min-width: 0; + height: 30px; +} + +[data-component="accordion-v2-trigger"] [data-slot="accordion-v2-chevron"] { + flex: none; + width: 14px; + height: 14px; + color: var(--accordion-v2-icon); + transition: transform 150ms ease; +} + +[data-component="accordion-v2-trigger"][data-expanded] [data-slot="accordion-v2-chevron"] { + transform: rotate(180deg); +} + +[data-component="accordion-v2-item"][data-expanded] [data-component="accordion-v2-trigger"] { + box-shadow: inset 0 -0.5px 0 var(--accordion-v2-border); +} + +[data-component="accordion-v2-trigger"][data-disabled] { + pointer-events: none; + opacity: 0.5; +} + +[data-component="accordion-v2-content"] { + overflow: hidden; + font-size: 13px; + line-height: 140%; + color: var(--accordion-v2-fg); +} + +[data-component="accordion-v2-content"][data-expanded] { + animation: accordion-v2-down 180ms ease-out; +} + +[data-component="accordion-v2-content"][data-closed] { + animation: accordion-v2-up 180ms ease-out; +} + +[data-component="accordion-v2-content"] [data-slot="accordion-v2-content-inner"] { + display: flex; + flex-direction: row; + align-items: flex-start; + width: 100%; + padding: 12px; +} + +@keyframes accordion-v2-down { + from { + height: 0; + } + to { + height: var(--kb-collapsible-content-height); + } +} + +@keyframes accordion-v2-up { + from { + height: var(--kb-collapsible-content-height); + } + to { + height: 0; + } +} diff --git a/packages/ui/src/v2/components/accordion-v2.stories.tsx b/packages/ui/src/v2/components/accordion-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..70fba2dbda54e2c0a2c22b176deef15d4cf08bcf --- /dev/null +++ b/packages/ui/src/v2/components/accordion-v2.stories.tsx @@ -0,0 +1,177 @@ +// @ts-nocheck +import { AccordionV2 } from "./accordion-v2" + +const docs = `### Overview +Compound accordion built on Kobalte's \`Accordion\` primitive. The trigger automatically renders a chevron that rotates open. + +### API +- \`AccordionV2\` — root; forwards Kobalte props (\`multiple\`, \`collapsible\`, \`value\`, \`defaultValue\`, \`onChange\`, etc.). +- \`AccordionV2.Item\` — one expandable row; requires a unique \`value: string\`. +- \`AccordionV2.Header\` — wraps the trigger; preserves heading semantics. +- \`AccordionV2.Trigger\` — auto-renders a trailing chevron; pass \`hideChevron\` to opt out. +- \`AccordionV2.Content\` — body shown when the item is expanded; height-animated. + +### Behavior +- Single-select by default (\`collapsible\` allows closing the active item). Use \`multiple\` to let several items open at once. +- Open/closed state is reflected on items, triggers, and content via \`data-expanded\` / \`data-closed\`. +- Content height animates using Kobalte's \`--kb-collapsible-content-height\` variable. +` + +export default { + title: "UI V2/Accordion", + id: "components-accordion-v2", + component: AccordionV2, + tags: ["autodocs"], + parameters: { + frameBackground: "#f5f5f5", + docs: { + description: { + component: docs, + }, + }, + }, +} + +const frame = { width: "346px", "font-family": "var(--v2-font-family-sans)", "font-size": "13px" } as const + +export const Basic = { + render: () => ( +
+ + + + Is it accessible? + + + Yes. It follows the WAI-ARIA Accordion pattern and ships with full keyboard support. + + + + + Is it styled? + + Yeah + + + + Is it animated? + + Yes. Height animates via Kobalte's collapsible height variable. + + +
+ ), +} + +export const Multiple = { + render: () => ( +
+ + + + Section A + + Multiple items can be open at once. + + + + Section B + + Open me too. + + + + Section C + + Already open by default. + + +
+ ), +} + +export const Disabled = { + render: () => ( +
+ + + + Enabled item + + Body content. + + + + Disabled item + + You can't open this one. + + + + Another enabled item + + Body content. + + +
+ ), +} + +export const LongContent = { + render: () => ( +
+ + + + What's inside? + + +
+

+ Accordions are useful for compressing dense content into scannable sections. They preserve heading + semantics and announce open/closed state to screen readers. +

+

+ The body can hold arbitrary content — paragraphs, lists, even nested components. +

+
    +
  • Keyboard navigable
  • +
  • Animated
  • +
  • Themeable via CSS variables
  • +
+
+
+
+ + + One more + + Short body. + +
+
+ ), +} + +export const NoChevron = { + render: () => ( +
+ + + + Trigger without chevron + + + Pass hideChevron on the trigger. + + + + + Default trigger + + Chevron renders by default. + + +
+ ), +} diff --git a/packages/ui/src/v2/components/accordion-v2.tsx b/packages/ui/src/v2/components/accordion-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5a996a02a21ab9339c07900c86355e9dc798ceed --- /dev/null +++ b/packages/ui/src/v2/components/accordion-v2.tsx @@ -0,0 +1,86 @@ +import { Accordion as Kobalte } from "@kobalte/core/accordion" +import { Show, splitProps, type Component, type ComponentProps, type ParentProps } from "solid-js" +import "./accordion-v2.css" + +const ChevronDown: Component = () => ( + +) + +export interface AccordionV2Props extends ComponentProps {} +export interface AccordionV2ItemProps extends ComponentProps {} +export interface AccordionV2HeaderProps extends ComponentProps {} +export interface AccordionV2TriggerProps extends ComponentProps { + hideChevron?: boolean +} +export interface AccordionV2ContentProps extends ComponentProps {} + +function AccordionV2Root(props: ParentProps) { + const [s, r] = splitProps(props, ["class", "classList"]) + return +} + +function AccordionV2Item(props: ParentProps) { + const [s, r] = splitProps(props, ["class", "classList"]) + return ( + + ) +} + +function AccordionV2Header(props: ParentProps) { + const [s, r] = splitProps(props, ["class", "classList", "children"]) + return ( + + {s.children} + + ) +} + +function AccordionV2Trigger(props: ParentProps) { + const [s, r] = splitProps(props, ["class", "classList", "children", "hideChevron"]) + return ( + + {s.children} + + + + + ) +} + +function AccordionV2Content(props: ParentProps) { + const [s, r] = splitProps(props, ["class", "classList", "children"]) + return ( + +
{s.children}
+
+ ) +} + +export const AccordionV2 = Object.assign(AccordionV2Root, { + Item: AccordionV2Item, + Header: AccordionV2Header, + Trigger: AccordionV2Trigger, + Content: AccordionV2Content, +}) diff --git a/packages/ui/src/v2/components/avatar-v2.css b/packages/ui/src/v2/components/avatar-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..8d717afbe1ef6ac5138fbb9ae2f461ca328cde74 --- /dev/null +++ b/packages/ui/src/v2/components/avatar-v2.css @@ -0,0 +1,70 @@ +[data-component="avatar-v2"] { + --avatar-bg: var(--v2-background-bg-layer-02); + --avatar-fg: var(--v2-text-text-muted); + --avatar-radius: 9999px; + --avatar-font-size: 11px; + --avatar-tracking: 0.05px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + position: relative; + overflow: hidden; + width: 28px; + height: 28px; + border-radius: var(--avatar-radius); + border: 0.5px solid var(--v2-border-border-base); + font-weight: 530; + font-size: var(--avatar-font-size); + line-height: 1; + letter-spacing: var(--avatar-tracking); + text-transform: uppercase; + background-color: var(--avatar-bg); + color: var(--avatar-fg); + user-select: none; + -webkit-user-select: none; +} + +[data-component="avatar-v2"]::before { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(180deg, var(--v2-alpha-light-16) 0%, var(--v2-alpha-light-0) 100%); + pointer-events: none; +} + +[data-component="avatar-v2"][data-has-image]::before { + display: none; +} + +[data-component="avatar-v2"][data-kind="org"] { + --avatar-radius: 6px; +} + +[data-component="avatar-v2"][data-kind="org"][data-size="normal"], +[data-component="avatar-v2"][data-kind="org"][data-size="small"] { + --avatar-radius: 4px; +} + +[data-component="avatar-v2"][data-size="normal"] { + width: 20px; + height: 20px; +} + +[data-component="avatar-v2"][data-size="small"] { + width: 16px; + height: 16px; +} + +[data-component="avatar-v2"] [data-slot="avatar-image"] { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + display: block; + object-fit: cover; + border-radius: inherit; + user-select: none; + -webkit-user-select: none; + -webkit-user-drag: none; +} diff --git a/packages/ui/src/v2/components/avatar-v2.stories.tsx b/packages/ui/src/v2/components/avatar-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f857aa933eae7d06983aab5aca49b170bebdb15e --- /dev/null +++ b/packages/ui/src/v2/components/avatar-v2.stories.tsx @@ -0,0 +1,85 @@ +// @ts-nocheck +import { Avatar } from "./avatar-v2" + +const docs = `### Overview +Avatar matching OpenCode DS variants from Figma. + +Use in user lists and headers. + +### API +- Required: \`fallback\` string. +- Optional: \`src\`, \`background\`, \`foreground\`, \`size\`, \`kind\`. + +### Variants and states +- Sizes: small (16), normal (20), large (28). +- Kind: user (circle), org (rounded-square). +- Image vs initials content state. + +### Behavior +- Uses grapheme-aware fallback rendering. + +### Accessibility +- TODO: provide alt text when using images; currently image is decorative. + +### Theming/tokens +- Uses \`data-component="avatar"\` with size and image state attributes. + +` + +export default { + title: "UI V2/Avatar", + id: "components-avatar-v2", + component: Avatar, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + argTypes: { + size: { + control: "select", + options: ["small", "normal", "large"], + }, + kind: { + control: "select", + options: ["user", "org"], + }, + }, + args: { + fallback: "WW", + size: "large", + kind: "user", + }, +} + +export const Basic = {} + +export const WithImage = { + args: { + src: "https://placehold.co/80x80/png", + fallback: "WW", + }, +} + +export const Sizes = { + render: () => ( +
+ + + +
+ ), +} + +export const OrgVariant = { + render: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/v2/components/avatar-v2.tsx b/packages/ui/src/v2/components/avatar-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..37b223772f2ca965006fa71fa378b5f6ebf9fb96 --- /dev/null +++ b/packages/ui/src/v2/components/avatar-v2.tsx @@ -0,0 +1,59 @@ +import { type ComponentProps, splitProps, Show } from "solid-js" +import "./avatar-v2.css" + +const segmenter = + typeof Intl !== "undefined" && "Segmenter" in Intl + ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) + : undefined + +function first(value: string) { + if (!value) return "" + if (!segmenter) return Array.from(value)[0] ?? "" + return segmenter.segment(value)[Symbol.iterator]().next().value?.segment ?? Array.from(value)[0] ?? "" +} + +export interface AvatarProps extends ComponentProps<"div"> { + fallback: string + src?: string + background?: string + foreground?: string + size?: "small" | "normal" | "large" + kind?: "user" | "org" +} + +export function Avatar(props: AvatarProps) { + const [split, rest] = splitProps(props, [ + "fallback", + "src", + "background", + "foreground", + "size", + "kind", + "class", + "classList", + "style", + ]) + const src = split.src // did this so i can zero it out to test fallback + return ( +
+ + {(src) => } + +
+ ) +} diff --git a/packages/ui/src/v2/components/badge-v2.css b/packages/ui/src/v2/components/badge-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..5c7a59e0aae56f6917d4ddb4d6b903a56ac6540b --- /dev/null +++ b/packages/ui/src/v2/components/badge-v2.css @@ -0,0 +1,33 @@ +[data-component="tag"] { + box-sizing: border-box; + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 4px; + height: 16px; + padding: 0 4px; + user-select: none; + + border-radius: 2px; + border: 0.5px solid var(--v2-border-border-base); + background: var(--v2-background-bg-layer-02); + + font-style: normal; + font-weight: 530; + font-size: 11px; + line-height: 1; + letter-spacing: 0.05px; + color: var(--v2-text-text-muted); + font-variant-numeric: tabular-nums; +} + +[data-component="tag"][data-high-contrast] { + border-color: var(--v2-border-border-strong); +} + +[data-component="tag"][data-variant="accent"] { + border: 0; + background: var(--v2-background-bg-accent); + color: var(--v2-text-text-contrast); +} diff --git a/packages/ui/src/v2/components/badge-v2.stories.tsx b/packages/ui/src/v2/components/badge-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..19af508bc2df7a46c8af6a2e5f54bd93e96f1cd2 --- /dev/null +++ b/packages/ui/src/v2/components/badge-v2.stories.tsx @@ -0,0 +1,59 @@ +// @ts-nocheck +import { Tag } from "./badge-v2" + +const docs = `### Overview +Small label tag for metadata and status chips. + +Use alongside headings or lists for quick metadata. + +### API +- Accepts standard span props. +- Optional: \`variant\` is \`neutral\` (default) or \`accent\`. +- Optional: \`data-high-contrast\` attribute for stronger border contrast. + +### Variants and states +- Neutral and accent variants. +- Optional high-contrast border style. + +### Behavior +- Inline element with fixed 16px height and tabular numeric text. + +### Accessibility +- Ensure text conveys meaning; avoid color-only distinction. + +### Theming/tokens +- Uses \`data-component="tag"\`. + +` + +export default { + title: "UI V2/Badge", + id: "components-badge-v2", + component: Tag, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + args: { + children: "Label", + }, +} + +export const Basic = {} + +export const HighContrast = { + render: () => ( +
+ Label + Label +
+ ), +} + +export const Accent = { + render: () => New, +} diff --git a/packages/ui/src/v2/components/badge-v2.tsx b/packages/ui/src/v2/components/badge-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8583d46da9fbb569f173e8ea5f1560323e9f1aa3 --- /dev/null +++ b/packages/ui/src/v2/components/badge-v2.tsx @@ -0,0 +1,23 @@ +import { type ComponentProps, splitProps } from "solid-js" +import "./badge-v2.css" + +export interface TagProps extends ComponentProps<"span"> { + variant?: "neutral" | "accent" +} + +export function Tag(props: TagProps) { + const [split, rest] = splitProps(props, ["class", "classList", "children", "variant"]) + return ( + + {split.children} + + ) +} diff --git a/packages/ui/src/v2/components/button-v2.css b/packages/ui/src/v2/components/button-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..20a8855503f2377d2b3d11e70105848fa1313314 --- /dev/null +++ b/packages/ui/src/v2/components/button-v2.css @@ -0,0 +1,262 @@ +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: geometricPrecision; +} + +[data-component="button-v2"] { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 6px; + font-style: normal; + font-weight: 530; + font-size: 13px; + line-height: 20px; + color: var(--v2-text-text-base); + text-shadow: none; + font-variant-numeric: tabular-nums; + letter-spacing: -0.04px; + user-select: none; + cursor: pointer; + outline: none; +} + +[data-component="button-v2"]:focus { + outline: none; +} + +[data-component="button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled):not([data-variant="loading"]) { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 2.5px; +} + +[data-component="button-v2"][data-size="normal"] { + height: 28px; + padding: 0 11px; +} + +[data-component="button-v2"][data-size="small"] { + height: 24px; + padding: 0 9px; + border-radius: 4px; +} + +[data-component="button-v2"][data-size="large"] { + height: 32px; + padding: 0 15px; +} + +[data-component="button-v2"][data-icon][data-size="small"] { + padding-left: 9px; +} + +[data-component="button-v2"][data-icon][data-size="normal"] { + padding-left: 11px; +} + +[data-component="button-v2"][data-icon][data-size="large"] { + padding-left: 15px; +} + +[data-component="button-v2"] [data-slot="icon-svg"] { + color: currentColor; +} + +/* Neutral */ +[data-component="button-v2"][data-variant="neutral"] { + background-color: var(--v2-background-bg-button-neutral); + color: var(--v2-text-text-base); + box-shadow: var(--v2-elevation-button-neutral); +} + +[data-component="button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Danger */ +[data-component="button-v2"][data-variant="danger"] { + background-color: var(--v2-background-bg-button-neutral); + color: var(--v2-state-fg-danger); + box-shadow: var(--v2-elevation-button-neutral); +} + +[data-component="button-v2"][data-variant="danger"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="danger"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="danger"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +[data-component="button-v2"][data-variant="warning"] { + background-color: var(--v2-background-bg-button-neutral); + color: var(--v2-state-fg-warning); + box-shadow: var(--v2-elevation-button-neutral); +} + +[data-component="button-v2"][data-variant="warning"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="warning"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="button-v2"][data-variant="warning"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Outline */ +[data-component="button-v2"][data-variant="outline"] { + background-color: transparent; + color: var(--v2-text-text-base); + box-shadow: inset 0 0 0 1px var(--v2-border-border-muted); +} + +[data-component="button-v2"][data-variant="outline"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="button-v2"][data-variant="outline"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="button-v2"][data-variant="outline"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Contrast */ +[data-component="button-v2"][data-variant="contrast"] { + background-image: + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); + color: var(--v2-text-text-contrast); + text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3); + box-shadow: var(--v2-elevation-button-contrast); +} + +[data-component="button-v2"][data-variant="contrast"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient( + 90deg, + var(--v2-overlay-simple-overlay-contrast-hover) 0%, + var(--v2-overlay-simple-overlay-contrast-hover) 100% + ), + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); +} + +[data-component="button-v2"][data-variant="contrast"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient( + 90deg, + var(--v2-overlay-simple-overlay-contrast-pressed) 0%, + var(--v2-overlay-simple-overlay-contrast-pressed) 100% + ), + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); +} + +[data-component="button-v2"][data-variant="contrast"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.4; + cursor: not-allowed; +} + +/* Loading */ +[data-component="button-v2"][data-variant="loading"] { + background: var(--v2-background-bg-layer-02); + color: var(--v2-text-text-base); + box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted); + cursor: default; + pointer-events: none; +} + +/* Ghost */ +[data-component="button-v2"][data-variant="ghost"] { + background-color: transparent; + color: var(--v2-text-text-base); +} + +[data-component="button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not( + [data-expanded] + ) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Ghost muted */ +[data-component="button-v2"][data-variant="ghost-muted"] { + background-color: transparent; + color: var(--v2-text-text-muted); +} + +[data-component="button-v2"][data-variant="ghost-muted"] [data-slot="icon-svg"] { + color: var(--v2-icon-icon-muted); +} + +[data-component="button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not( + [data-expanded] + ) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); + color: var(--v2-text-text-base); +} + +[data-component="button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) [data-slot="icon-svg"] { + color: var(--v2-icon-icon-base); +} + +[data-component="button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/packages/ui/src/v2/components/button-v2.stories.tsx b/packages/ui/src/v2/components/button-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbb38cc5a220b1e33d6bf45b6b94a9cfeda7c149 --- /dev/null +++ b/packages/ui/src/v2/components/button-v2.stories.tsx @@ -0,0 +1,153 @@ +import { ButtonV2 } from "./button-v2" + +const docs = `### Overview +Button v2 with visual variants and three sizes. + +### API +- \`variant\`: "neutral" | "danger" | "warning" | "contrast" | "ghost" | "ghost-muted" | "loading". +- \`size\`: "small" | "normal" | "large". +- \`icon\`: Optional icon name. +- Inherits Kobalte Button props and native button attributes. + +### States +- default, hover, pressed, focus, disabled. +- State selectors are available via pseudo-classes and \`[data-state]\`. +` + +export default { + title: "UI V2/Button", + id: "components-button-v2", + component: ButtonV2, + tags: ["autodocs"], + parameters: { + frameHeight: "240px", + frameBackground: "#fff", + docs: { + description: { + component: docs, + }, + }, + }, + args: { + children: "Button", + variant: "neutral", + size: "normal", + }, + argTypes: { + icon: { + control: "text", + }, + variant: { + control: "select", + options: ["neutral", "danger", "warning", "contrast", "ghost", "ghost-muted", "loading"], + }, + size: { + control: "select", + options: ["normal", "large"], + }, + }, +} + +export const Playground = {} + +export const Variants = { + render: () => ( +
+ Neutral + Danger + Warning + Contrast + Ghost + + Ghost muted + + Loading +
+ ), +} + +export const Sizes = { + render: () => ( +
+ + Small + + + Normal + + + Large + +
+ ), +} + +export const Icon = { + render: () => ( +
+ + Normal + + + Large + +
+ ), +} + +export const AllStates = { + render: () => { + const variants = ["neutral", "danger", "warning", "contrast", "ghost", "ghost-muted", "loading"] as const + const states = ["default", "hover", "pressed", "focus", "disabled"] as const + const toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1) + return ( +
+ {variants.map((variant) => ( +
+
+ {variant} +
+
+ {states.map((state) => ( + + {toTitleCase(state)} + + ))} +
+
+ ))} +
+ ) + }, +} diff --git a/packages/ui/src/v2/components/button-v2.tsx b/packages/ui/src/v2/components/button-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..14b793ba11a957d28bb22b82e078d36d2e618f87 --- /dev/null +++ b/packages/ui/src/v2/components/button-v2.tsx @@ -0,0 +1,35 @@ +import { Button as Kobalte } from "@kobalte/core/button" +import { type ComponentProps, Show, createMemo, splitProps } from "solid-js" +import { Icon, type IconProps } from "./icon" +import "./button-v2.css" + +export interface ButtonV2Props + extends ComponentProps, + Pick, "class" | "classList" | "children"> { + size?: "small" | "normal" | "large" + variant?: "neutral" | "danger" | "warning" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading" + icon?: IconProps["name"] +} + +export function ButtonV2(props: ButtonV2Props) { + const [split, rest] = splitProps(props, ["variant", "size", "icon", "class", "classList"]) + const resolvedIcon = createMemo(() => split.icon) + return ( + + + + + {props.children} + + ) +} diff --git a/packages/ui/src/v2/components/checkbox-v2.css b/packages/ui/src/v2/components/checkbox-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..47e3de9f5e7fcb483133255ee1d27d208f39cd1f --- /dev/null +++ b/packages/ui/src/v2/components/checkbox-v2.css @@ -0,0 +1,184 @@ +[data-slot="checkbox-v2"] { + display: flex; + flex-direction: column; + gap: 6px; + cursor: default; + + &:where([data-disabled]) { + cursor: not-allowed; + } + + [data-slot="checkbox-v2-error"] { + color: var(--v2-state-fg-danger); + font-size: 12px; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="checkbox-v2-error"]:empty { + display: none; + } +} + +[data-slot="checkbox-v2-row"] { + position: relative; + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 0px; + gap: 8px; + + [data-slot="checkbox-v2-input"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + + [data-slot="checkbox-v2-control-stack"] { + position: relative; + width: 16px; + height: 20px; + flex: none; + } + + [data-slot="checkbox-v2-control"] { + box-sizing: border-box; + position: absolute; + width: 16px; + height: 16px; + flex: none; + flex-shrink: 0; + left: 0; + top: calc(50% - 16px / 2); + border-radius: 4px; + border: none; + box-shadow: inset 0 0 0 0.5px var(--v2-border-border-strong); + + background: + linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-base); + transition: + background 170ms ease-out, + opacity 170ms ease-out, + outline-color 170ms ease-out; + } + + [data-slot="checkbox-v2-indicator"] { + position: absolute; + inset: 0; + width: 16px; + height: 16px; + pointer-events: none; + } + + [data-slot="checkbox-v2-text"] { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + padding: 0px; + gap: 6px; + } + + [data-slot="checkbox-v2-label"] { + display: inline-flex; + user-select: none; + color: inherit; + font-style: normal; + font-weight: 440; + font-variant-numeric: tabular-nums; + font-variation-settings: "slnt" 0; + } + + [data-slot="checkbox-v2-label-text"] { + display: inline-flex; + align-items: center; + user-select: none; + color: var(--v2-text-text-base); + font-size: 13px; + line-height: 20px; + letter-spacing: -0.04px; + } + + [data-slot="checkbox-v2-description"] { + color: var(--v2-text-text-muted); + font-size: 11px; + font-weight: 440; + line-height: 1; + letter-spacing: 0.05px; + font-variant-numeric: tabular-nums; + user-select: none; + } +} + +[data-slot="checkbox-v2"]:where(:not([data-readonly])) + [data-slot="checkbox-v2-input"]:focus-visible + ~ [data-slot="checkbox-v2-control-stack"] + [data-slot="checkbox-v2-control"] { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 1px; +} + +[data-slot="checkbox-v2"]:where(:hover):where(:not([data-disabled], [data-readonly], [data-invalid])):where( + :not([data-checked], [data-indeterminate]) + ) + [data-slot="checkbox-v2-control"] { + background: + linear-gradient(0deg, var(--v2-overlay-simple-overlay-hover), var(--v2-overlay-simple-overlay-hover)), + linear-gradient(180deg, var(--v2-alpha-light-6) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-base); +} + +[data-slot="checkbox-v2"]:where([data-disabled]) [data-slot="checkbox-v2-control"] { + opacity: 0.5; +} + +[data-slot="checkbox-v2"]:where([data-checked], [data-indeterminate]) [data-slot="checkbox-v2-control"] { + background: + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-accent); +} + +[data-slot="checkbox-v2"]:where([data-checked], [data-indeterminate]):where(:hover):where( + :not([data-disabled], [data-readonly]) + ) + [data-slot="checkbox-v2-control"] { + background: + linear-gradient( + 0deg, + var(--v2-overlay-simple-overlay-contrast-hover), + var(--v2-overlay-simple-overlay-contrast-hover) + ), + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-accent); +} + +[data-slot="checkbox-v2"]:where([data-invalid]):where(:not([data-checked], [data-indeterminate])) + [data-slot="checkbox-v2-control"] { + background: var(--v2-state-bg-danger); + box-shadow: inset 0 0 0 0.5px var(--v2-state-border-danger); +} + +[data-slot="checkbox-v2"] .checkbox-v2-icon { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: none; +} + +[data-slot="checkbox-v2"]:where([data-checked]):where(:not([data-indeterminate])) .checkbox-v2-icon--check { + display: block; +} + +[data-slot="checkbox-v2"]:where([data-indeterminate]) .checkbox-v2-icon--minus { + display: block; +} + +[data-slot="checkbox-v2"]:where([data-disabled]) [data-slot="checkbox-v2-label"], +[data-slot="checkbox-v2"]:where([data-disabled]) [data-slot="checkbox-v2-description"] { + opacity: 0.5; +} diff --git a/packages/ui/src/v2/components/checkbox-v2.stories.tsx b/packages/ui/src/v2/components/checkbox-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8ca32efbb185c1c4bbfa9b6842722be496201d6b --- /dev/null +++ b/packages/ui/src/v2/components/checkbox-v2.stories.tsx @@ -0,0 +1,92 @@ +// @ts-nocheck +import { createSignal } from "solid-js" +import { CheckboxV2 } from "./checkbox-v2" + +const docs = `### Overview +Binary and tri-state checkbox using Kobalte Checkbox. + +### API +- Forwards Kobalte Checkbox props (\`checked\`, \`defaultChecked\`, \`onChange\`, \`indeterminate\`, \`name\`, \`required\`, \`validationState\`, \`disabled\`, etc.). +- Adds \`label\`, optional \`description\`, and \`hideLabel\`. + +### Behavior +- Controlled or uncontrolled via \`checked\` / \`defaultChecked\`. +- Indeterminate is driven by the \`indeterminate\` prop (pass a reactive boolean, e.g. \`indeterminate={flag()}\`). + +### Theming/tokens +- Uses \`data-slot="checkbox-v2"\` and slot attributes aligned with radio item layout. +` + +export default { + title: "UI V2/Checkbox", + id: "components-checkbox-v2", + component: CheckboxV2, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( + + ), +} + +export const Controlled = { + render: () => { + const [checked, setChecked] = createSignal(false) + return ( +
+ +
+ Checked: {String(checked())} +
+
+ ) + }, +} + +export const Indeterminate = { + render: () => { + const [indeterminate, setIndeterminate] = createSignal(true) + const [checked, setChecked] = createSignal(false) + return ( + { + setChecked(v) + if (v) setIndeterminate(false) + }} + label="Select all" + description="Starts indeterminate; checking clears mixed state." + /> + ) + }, +} + +export const States = { + render: () => ( +
+ + + + + + + +
+ ), +} diff --git a/packages/ui/src/v2/components/checkbox-v2.tsx b/packages/ui/src/v2/components/checkbox-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b66a99f65621581b6198c512334ec975dbf9d163 --- /dev/null +++ b/packages/ui/src/v2/components/checkbox-v2.tsx @@ -0,0 +1,65 @@ +import { Checkbox as Kobalte } from "@kobalte/core/checkbox" +import { Show, splitProps, type JSX } from "solid-js" +import type { ComponentProps } from "solid-js" +import "./checkbox-v2.css" + +export interface CheckboxV2Props extends ComponentProps { + label: JSX.Element + description?: JSX.Element + hideLabel?: boolean +} + +export function CheckboxV2(props: CheckboxV2Props) { + const [local, others] = splitProps(props, ["class", "classList", "label", "description", "hideLabel"]) + return ( + +
+ +
+ + + + + + +
+ +
+ {local.label} + + {(description) => {description()}} + +
+
+
+ +
+ ) +} diff --git a/packages/ui/src/v2/components/dialog-v2.css b/packages/ui/src/v2/components/dialog-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..132d7fae27aa40aedacadcd73d80f1538d4745cd --- /dev/null +++ b/packages/ui/src/v2/components/dialog-v2.css @@ -0,0 +1,177 @@ +/* [data-component="dialog-trigger"] { } */ + +[data-component="dialog-overlay"] { + position: fixed; + inset: 0; + z-index: 50; + background-color: var(--v2-overlay-simple-overlay-scrim); +} + +[data-component="dialog-v2"] { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + + [data-slot="dialog-container"] { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 480px; + height: 368px; + background: var(--v2-background-bg-layer-01); + box-shadow: var(--v2-elevation-overlay); + border-radius: 6px; + overflow: hidden; + pointer-events: auto; + + [data-slot="dialog-content"] { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + width: 100%; + max-height: 100%; + flex: 1; + overflow: auto; + background: transparent; + + scrollbar-width: none; + -ms-overflow-style: none; + &::-webkit-scrollbar { + display: none; + } + + [data-slot="dialog-header"] { + display: flex; + flex-direction: row; + align-items: flex-start; + align-self: stretch; + width: 100%; + gap: 0; + padding: 16px; + flex-shrink: 0; + + [data-slot="dialog-header-title"] { + margin: 0; + flex: 1; + min-width: 0; + user-select: none; + font-weight: 530; + font-size: 13px; + line-height: 16px; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; + } + + [data-slot="dialog-title-group"] { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + flex: 1; + min-width: 0; + } + + [data-slot="dialog-title"] { + margin: 0; + flex: none; + flex-grow: 0; + user-select: none; + font-weight: 530; + font-size: 15px; + line-height: 20px; + letter-spacing: -0.13px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; + } + + [data-slot="dialog-description"] { + flex: none; + flex-grow: 0; + user-select: none; + font-weight: 440; + font-size: 13px; + line-height: 20px; + letter-spacing: -0.04px; + color: var(--v2-text-text-muted); + font-variation-settings: "slnt" 0; + } + + [data-slot="dialog-close-button"] { + display: flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + margin-top: -2px; + margin-inline-end: -2px; + margin-inline-start: auto; + border-radius: 4px; + flex-shrink: 0; + cursor: pointer; + color: var(--v2-icon-icon-muted); + + &:hover { + background: var(--v2-overlay-simple-overlay-hover); + } + } + + &[data-hide-close] [data-slot="dialog-close-button"] { + display: none; + } + } + + [data-slot="dialog-footer"] { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: flex-start; + padding: 16px; + gap: 8px; + align-self: stretch; + flex-shrink: 0; + } + + [data-slot="dialog-body"] { + width: 100%; + position: relative; + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + + &:focus-visible { + outline: none; + } + } + &:focus-visible { + outline: none; + } + } + } + + &[data-fit] { + [data-slot="dialog-container"] { + height: auto; + + [data-slot="dialog-content"] { + min-height: 0; + } + } + } + + &[data-size="large"] [data-slot="dialog-container"] { + width: 640px; + height: 480px; + } + + &[data-size="x-large"] [data-slot="dialog-container"] { + width: min(calc(100vw - 32px), 980px); + height: min(calc(100vh - 92px), 600px); + } +} diff --git a/packages/ui/src/v2/components/dialog-v2.stories.tsx b/packages/ui/src/v2/components/dialog-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..198da5c663d39dea09904cbed8fda0f3a259521c --- /dev/null +++ b/packages/ui/src/v2/components/dialog-v2.stories.tsx @@ -0,0 +1,206 @@ +import { Dialog as KobalteDialog } from "@kobalte/core/dialog" +import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle, DialogTitleGroup } from "./dialog-v2" +import { ButtonV2 } from "./button-v2" + +const docs = `### Overview +Dialog content wrapper built on Kobalte's dialog primitive with v2 styling. + +Compose with \`DialogHeader\`, \`DialogTitle\`, \`DialogTitleGroup\`, \`DialogBody\`, and \`DialogFooter\`. + +### API +- \`Dialog\`: \`size\` (normal | large | x-large), \`variant\`, \`fit\`. +- \`DialogHeader\`: row container with optional \`closeLabel\` and \`hideClose\`. +- \`DialogTitle\`: accessible single-line header title. +- \`DialogTitleGroup\`: column with \`title\` and required \`description\`. + +### Accessibility +- Focus trapping and aria attributes provided by Kobalte Dialog. + +### Theming/tokens +- Uses \`data-component="dialog-v2"\` and slot attributes. +` + +export default { + title: "UI V2/Dialog", + id: "components-dialog-v2", + component: Dialog, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +function dialogHeader(title: string, description: string) { + return ( + + + + ) +} + +export const Basic = { + render: () => ( + + + Open dialog + + + + + {dialogHeader("Dialog", "Description")} + Dialog body content. + + + + ), +} + +export const Sizes = { + render: () => ( +
+ + + Normal + + + + + {dialogHeader("Normal", "Normal size")} + Normal dialog content. + + + + + + + Large + + + + + {dialogHeader("Large", "Large size")} + Large dialog content. + + + + + + + X-Large + + + + + {dialogHeader("Extra large", "X-large size")} + X-large dialog content. + + + +
+ ), +} + +export const TitleOnly = { + render: () => ( + + + Open dialog + + + + + + Open project + + Dialog body content. + + + + ), +} + +export const HeaderControls = { + render: () => ( + + + Open dialog + + + + + + + + Help + + + Dialog body content. + + + + ), +} + +export const WithFooter = { + render: () => ( + + + Open dialog + + + + + {dialogHeader("Save changes", "Your changes will be lost if you don't save them.")} + + Cancel + Save + + + + + ), +} + +export const WithFooterThreeButtons = { + render: () => ( + + + Open dialog + + + + + {dialogHeader("Unsaved changes", "You have unsaved changes. What would you like to do?")} + + + Remind me later + + Cancel + Save + + + + + ), +} + +export const Fit = { + render: () => ( + + + Open fit dialog + + + + + {dialogHeader("Fit content", "Dialog fits its content.")} + Dialog fits its content. + + + + ), +} diff --git a/packages/ui/src/v2/components/dialog-v2.tsx b/packages/ui/src/v2/components/dialog-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8f083f9296896d42e09f4acaa580163b5a5775de --- /dev/null +++ b/packages/ui/src/v2/components/dialog-v2.tsx @@ -0,0 +1,117 @@ +import { Dialog as Kobalte } from "@kobalte/core/dialog" +import { type ComponentProps, type JSXElement, type ParentProps, Show, children, splitProps } from "solid-js" +import { useI18n } from "../../context/i18n" +import "./dialog-v2.css" + +export interface DialogProps extends ParentProps { + size?: "normal" | "large" | "x-large" + variant?: "default" | "settings" + class?: ComponentProps<"div">["class"] + containerClass?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] + fit?: boolean +} + +export interface DialogHeaderProps extends ParentProps { + closeLabel?: string + hideClose?: boolean +} + +export interface DialogTitleGroupProps { + title?: JSXElement + description: JSXElement +} + +export function DialogFooter(props: ParentProps) { + return
{props.children}
+} + +export function DialogBody(props: ParentProps & { class?: ComponentProps<"div">["class"] }) { + const [local] = splitProps(props, ["class", "children"]) + return ( +
+ {local.children} +
+ ) +} + +export function DialogTitle(props: ParentProps) { + return {props.children} +} + +export function DialogTitleGroup(props: DialogTitleGroupProps) { + const title = children(() => props.title) + const description = children(() => props.description) + + return ( +
+ {(t) => {t()}} + {description()} +
+ ) +} + +export function DialogHeader(props: DialogHeaderProps) { + const i18n = useI18n() + const [local] = splitProps(props, ["closeLabel", "hideClose", "children"]) + const hideClose = () => local.hideClose === true + + return ( +
+ {local.children} + {!hideClose() && ( + + + + )} +
+ ) +} + +export function Dialog(props: DialogProps) { + const [local] = splitProps(props, ["size", "variant", "class", "containerClass", "classList", "fit", "children"]) + + return ( +
+
+ { + const target = e.currentTarget as HTMLElement | null + const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null + if (autofocusEl) { + e.preventDefault() + autofocusEl.focus({ preventScroll: true }) + } + }} + > + {local.children} + +
+
+ ) +} + +export const DialogV2 = Dialog diff --git a/packages/ui/src/v2/components/diff-changes-v2.css b/packages/ui/src/v2/components/diff-changes-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..0f6f260521c93d339bcb7078f5d1aff8b612f676 --- /dev/null +++ b/packages/ui/src/v2/components/diff-changes-v2.css @@ -0,0 +1,26 @@ +[data-component="diff-changes"] { + display: flex; + direction: ltr; + unicode-bidi: isolate; + gap: 8px; + justify-content: flex-end; + align-items: center; + + [data-slot="diff-changes-additions"], + [data-slot="diff-changes-deletions"] { + font-size: 11px; + font-style: normal; + font-weight: 440; + line-height: 1; + letter-spacing: 0.05px; + text-align: right; + } + + [data-slot="diff-changes-additions"] { + color: var(--v2-state-fg-success); + } + + [data-slot="diff-changes-deletions"] { + color: var(--v2-state-fg-danger); + } +} diff --git a/packages/ui/src/v2/components/diff-changes-v2.stories.tsx b/packages/ui/src/v2/components/diff-changes-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cc855412ad0a6d2df6aabac8c976923be5f0f719 --- /dev/null +++ b/packages/ui/src/v2/components/diff-changes-v2.stories.tsx @@ -0,0 +1,60 @@ +import { DiffChanges } from "./diff-changes-v2" + +const docs = `### Overview +Summarize additions/deletions as compact text. + +Pair with \`Diff\`/\`DiffSSR\` to contextualize a change set. + +### API +- Required: \`changes\` as { additions, deletions } or an array of those objects. + +### Variants and states +- Handles zero-change state (renders nothing). + +### Behavior +- Aggregates arrays into total additions/deletions. + +### Accessibility +- Ensure surrounding context conveys meaning of the counts/bars. + +### Theming/tokens +- Uses \`data-component="diff-changes"\` and diff color tokens. + +` + +const changes = { additions: 12, deletions: 5 } + +export default { + title: "UI V2/DiffChanges", + id: "components-diff-changes-v2", + component: DiffChanges, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + args: { + changes, + }, +} + +export const Default = {} + +export const MultipleFiles = { + args: { + changes: [ + { additions: 4, deletions: 1 }, + { additions: 8, deletions: 3 }, + { additions: 2, deletions: 0 }, + ], + }, +} + +export const Zero = { + args: { + changes: { additions: 0, deletions: 0 }, + }, +} diff --git a/packages/ui/src/v2/components/diff-changes-v2.tsx b/packages/ui/src/v2/components/diff-changes-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9413d2e9c246319bea848328377f0e8227f52161 --- /dev/null +++ b/packages/ui/src/v2/components/diff-changes-v2.tsx @@ -0,0 +1,28 @@ +import { createMemo, Show } from "solid-js" +import "./diff-changes-v2.css" + +export function DiffChanges(props: { + class?: string + changes: { additions: number; deletions: number } | { additions: number; deletions: number }[] +}) { + const additions = createMemo(() => + Array.isArray(props.changes) + ? props.changes.reduce((acc, diff) => acc + (diff.additions ?? 0), 0) + : props.changes.additions, + ) + const deletions = createMemo(() => + Array.isArray(props.changes) + ? props.changes.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0) + : props.changes.deletions, + ) + const total = createMemo(() => (additions() ?? 0) + (deletions() ?? 0)) + + return ( + 0}> +
+ {`+${additions()}`} + {`-${deletions()}`} +
+
+ ) +} diff --git a/packages/ui/src/v2/components/divider-v2.css b/packages/ui/src/v2/components/divider-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..94a584b40ed915c7375ce2e8e557ad8608765841 --- /dev/null +++ b/packages/ui/src/v2/components/divider-v2.css @@ -0,0 +1,11 @@ +[data-component="divider-v2"] { + box-sizing: border-box; + width: 100%; + height: 1px; + transform: scaleY(0.5); + border: none; + background: var(--v2-border-border-strong); + flex: none; + align-self: stretch; + flex-grow: 0; +} diff --git a/packages/ui/src/v2/components/divider-v2.stories.tsx b/packages/ui/src/v2/components/divider-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e2807ec717afd2efb1a5ca1b909aaaf89b653576 --- /dev/null +++ b/packages/ui/src/v2/components/divider-v2.stories.tsx @@ -0,0 +1,38 @@ +import { DividerV2 } from "./divider-v2" + +const docs = `### Overview +Horizontal hairline divider for v2 layouts. + +### API +- Inherits native div attributes. +- Stretches to full width of its flex parent. + +### Theming/tokens +- Uses \`data-component="divider-v2"\`. +- Border color: \`--v2-border-border-strong\`. +` + +export default { + title: "UI V2/Divider", + id: "components-divider-v2", + component: DividerV2, + tags: ["autodocs"], + parameters: { + frameWidth: "320px", + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Basic = { + render: () => ( +
+ Above + + Below +
+ ), +} diff --git a/packages/ui/src/v2/components/divider-v2.tsx b/packages/ui/src/v2/components/divider-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2c9f4dbea0c0a06d9dea8a695b8a705f0bd6f26e --- /dev/null +++ b/packages/ui/src/v2/components/divider-v2.tsx @@ -0,0 +1,20 @@ +import { type ComponentProps, splitProps } from "solid-js" +import "./divider-v2.css" + +export interface DividerV2Props extends ComponentProps<"div"> {} + +export function DividerV2(props: DividerV2Props) { + const [local, rest] = splitProps(props, ["class", "classList"]) + return ( +
+ ) +} diff --git a/packages/ui/src/v2/components/field-v2.css b/packages/ui/src/v2/components/field-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..ae64c89b31e27b93f2371add5007524bba7c68d0 --- /dev/null +++ b/packages/ui/src/v2/components/field-v2.css @@ -0,0 +1,94 @@ +[data-component="field-v2"] { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + width: 100%; + min-width: 0; +} + +[data-component="field-v2"] [data-slot="field-v2-label"] { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; + gap: 4px; + min-height: 16px; + margin: 0; + padding: 0; + border: 0; + font-style: normal; + font-weight: 530; + font-size: 13px; + line-height: 1; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; + cursor: default; + user-select: none; +} + +[data-component="field-v2"] [data-slot="field-v2-label-text"] { + display: inline-flex; + align-items: center; +} + +[data-component="field-v2"] [data-slot="field-v2-label"] [data-component="tooltip-v2-trigger"] { + display: inline-flex; + flex: none; + width: 16px; + height: 16px; +} + +[data-component="field-v2"] [data-slot="field-v2-label-info"] { + display: inline-flex; + align-items: center; + justify-content: center; + flex: none; + width: 16px; + height: 16px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--v2-icon-icon-muted); + cursor: pointer; + outline: none; +} + +[data-component="field-v2"] [data-slot="field-v2-label-info"]:is(:hover, [data-state="hover"]) { + color: var(--v2-text-text-base); +} + +[data-component="field-v2"] [data-slot="field-v2-label-info"]:focus { + outline: none; +} + +[data-component="field-v2"] [data-slot="field-v2-label-info"]:focus-visible { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 1px; + border-radius: 2px; +} + +[data-component="field-v2"] [data-slot="field-v2-prefix"], +[data-component="field-v2"] [data-slot="field-v2-suffix"] { + align-self: stretch; + width: 100%; + min-height: 11px; + font-style: normal; + font-weight: 440; + font-size: 11px; + line-height: 1; + letter-spacing: 0.05px; + color: var(--v2-text-text-muted); + font-variation-settings: "slnt" 0; + user-select: none; +} + +[data-component="field-v2"] [data-slot="field-v2-control"] { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + min-width: 0; +} diff --git a/packages/ui/src/v2/components/field-v2.stories.tsx b/packages/ui/src/v2/components/field-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2bc5fd252aea1b20c54ee0a007a991ab639b5816 --- /dev/null +++ b/packages/ui/src/v2/components/field-v2.stories.tsx @@ -0,0 +1,135 @@ +// @ts-nocheck +import { createSignal } from "solid-js" +import { Field } from "./field-v2" +import { InlineInputV2 } from "./inline-input-v2" +import { TextInputV2 } from "./text-input-v2" +import { TextareaV2 } from "./textarea-v2" + +const docs = `### Overview +Composable field layout for TextInput, Textarea, and InlineInput v2. + +### Usage +\`\`\`tsx + + Label + Prefix + + + + Suffix + +\`\`\` + +Omit \`Field.Control\` and place the input directly inside \`Field\` — a11y props are merged automatically. + +### API +- \`Field\`: \`invalid\` propagates to the control. +- \`Field.Label\`: \`tooltip\` shows the info icon with tooltip text. +- \`Field.Prefix\` / \`Field.Suffix\`: helper copy above / below the control. +- \`Field.Control\`: optional wrapper (marker only). +` + +export default { + title: "UI V2/Field", + id: "components-field-v2", + subcomponents: { + Label: Field.Label, + Prefix: Field.Prefix, + Suffix: Field.Suffix, + Control: Field.Control, + }, + tags: ["autodocs"], + parameters: { + frameHeight: "500px", + frameBackground: "#fff", + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const TextInputExample = { + render: () => ( +
+ + Label + Prefix + + + + Suffix + +
+ ), +} + +export const TextInputDirectChild = { + render: () => ( +
+ + Label + Prefix + + Suffix + +
+ ), +} + +export const TextareaExample = { + render: () => ( +
+ + Label + Prefix + + Suffix + +
+ ), +} + +export const InlineInputExample = { + render: () => ( +
+ + Label + Prefix + + Suffix + +
+ ), +} + +export const Invalid = { + render: () => ( +
+ + Label + Prefix + + Suffix + +
+ ), +} + +export const Controlled = { + render: () => { + const [value, setValue] = createSignal("") + return ( +
+ + Amount + + setValue(e.currentTarget.value)} numeric /> + + {value() ? `Entered: ${value()}` : "Suffix"} + +
+ ) + }, +} diff --git a/packages/ui/src/v2/components/field-v2.tsx b/packages/ui/src/v2/components/field-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..45b5a70b2efb0ae179d41b70e1dd39f4b0ba53cb --- /dev/null +++ b/packages/ui/src/v2/components/field-v2.tsx @@ -0,0 +1,265 @@ +import { + createContext, + createEffect, + createSignal, + createUniqueId, + onCleanup, + onMount, + splitProps, + useContext, + Show, + type ComponentProps, + type ParentProps, +} from "solid-js" +import { TooltipV2 } from "./tooltip-v2" +import "./field-v2.css" + +type FieldContextValue = { + controlId: string + labelId: string + prefixId: string + suffixId: string + invalid: () => boolean + registerPrefix: () => void + unregisterPrefix: () => void + registerSuffix: () => void + unregisterSuffix: () => void + getDescribedBy: () => string | undefined +} + +const FieldContext = createContext() + +function useField() { + const ctx = useContext(FieldContext) + if (!ctx) { + throw new Error("Field subcomponents must be used within ") + } + return ctx +} + +const CONTROL_SELECTOR = [ + "[data-slot='text-input-v2-input']", + "[data-slot='textarea-v2-textarea']", + "[data-slot='inline-input-v2-input']", +].join(", ") + +export interface FieldV2Props extends ComponentProps<"div"> { + invalid?: boolean +} + +function FieldV2Root(props: ParentProps) { + const [local, rest] = splitProps(props, ["invalid", "class", "classList", "children"]) + + const controlId = `field-control-${createUniqueId()}` + const labelId = `field-label-${createUniqueId()}` + const prefixId = `field-prefix-${createUniqueId()}` + const suffixId = `field-suffix-${createUniqueId()}` + + const [prefixCount, setPrefixCount] = createSignal(0) + const [suffixCount, setSuffixCount] = createSignal(0) + + let rootRef: HTMLDivElement | undefined + + const ctx: FieldContextValue = { + controlId, + labelId, + prefixId, + suffixId, + invalid: () => !!local.invalid, + registerPrefix: () => setPrefixCount((n) => n + 1), + unregisterPrefix: () => setPrefixCount((n) => Math.max(0, n - 1)), + registerSuffix: () => setSuffixCount((n) => n + 1), + unregisterSuffix: () => setSuffixCount((n) => Math.max(0, n - 1)), + getDescribedBy: () => { + const ids: string[] = [] + if (prefixCount() > 0) ids.push(prefixId) + if (suffixCount() > 0) ids.push(suffixId) + return ids.length > 0 ? ids.join(" ") : undefined + }, + } + + const syncControlA11y = () => { + const root = rootRef + if (!root) return + + const control = root.querySelector(CONTROL_SELECTOR) as HTMLInputElement | HTMLTextAreaElement | null + if (!control) return + + const shell = control.closest( + "[data-component='text-input-v2'], [data-component='textarea-v2'], [data-component='inline-input-v2']", + ) as HTMLElement | null + + control.id = controlId + control.setAttribute("aria-labelledby", labelId) + + const describedBy = ctx.getDescribedBy() + if (describedBy) { + control.setAttribute("aria-describedby", describedBy) + } else { + control.removeAttribute("aria-describedby") + } + + if (ctx.invalid()) { + control.setAttribute("aria-invalid", "true") + shell?.setAttribute("data-invalid", "") + } else { + control.removeAttribute("aria-invalid") + shell?.removeAttribute("data-invalid") + } + } + + onMount(() => { + syncControlA11y() + }) + + createEffect(() => { + prefixCount() + suffixCount() + local.invalid + syncControlA11y() + }) + + return ( + +
+ {local.children} +
+
+ ) +} + +function FieldLabelInfoIcon() { + return ( + + ) +} + +export interface FieldLabelProps extends ComponentProps<"label"> { + /** When set, shows the info icon with a tooltip containing this text. */ + tooltip?: string +} + +function FieldLabel(props: ParentProps) { + const [local, rest] = splitProps(props, ["class", "classList", "children", "tooltip"]) + const field = useField() + + return ( + + ) +} + +function FieldPrefix(props: ParentProps>) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + const field = useField() + + onMount(() => { + field.registerPrefix() + onCleanup(() => field.unregisterPrefix()) + }) + + return ( +
+ {local.children} +
+ ) +} + +function FieldSuffix(props: ParentProps>) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + const field = useField() + + onMount(() => { + field.registerSuffix() + onCleanup(() => field.unregisterSuffix()) + }) + + return ( +
+ {local.children} +
+ ) +} + +/** Optional layout wrapper around the control. */ +function FieldControl(props: ParentProps>) { + const [local, rest] = splitProps(props, ["class", "classList", "children"]) + + return ( +
+ {local.children} +
+ ) +} + +export const FieldV2 = Object.assign(FieldV2Root, { + Label: FieldLabel, + Prefix: FieldPrefix, + Suffix: FieldSuffix, + Control: FieldControl, +}) + +export const Field = FieldV2 diff --git a/packages/ui/src/v2/components/file-tree-v2.css b/packages/ui/src/v2/components/file-tree-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..eb9ff7a7ade638019e8b54bd508214c33b32af2f --- /dev/null +++ b/packages/ui/src/v2/components/file-tree-v2.css @@ -0,0 +1,141 @@ +[data-component="file-tree-v2"] { + display: flex; + flex-direction: column; + gap: 2px; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"] { + box-sizing: border-box; + width: 100%; + min-width: 0; + height: 28px; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + padding-inline-end: 8px; + overflow: visible; + border: none; + border-radius: 6px; + background-color: transparent; + color: var(--v2-text-text-muted); + text-align: start; + cursor: pointer; + scroll-margin-block: 8px; + transition: + background-color 120ms ease, + color 120ms ease; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-ignored] { + color: var(--v2-text-text-faint); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"]:hover { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-selected] { + color: var(--v2-text-text-base); + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-selected]:hover { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-guide"] { + position: absolute; + top: 0; + margin-top: -2px; + height: calc(100% + 2px); + width: 1px; + pointer-events: none; + background-color: var(--v2-border-border-muted); + opacity: 0; +} + +[data-component="file-tree-v2"]:hover [data-slot="file-tree-v2-guide"] { + opacity: 0.5; +} + +[data-component="file-tree-v2"] .filetree-icon--mono { + color: var(--v2-icon-icon-muted); +} + +[data-component="file-tree-v2"] .filetree-iconpair .filetree-icon--color { + opacity: 0; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-selected] .filetree-iconpair .filetree-icon--color { + opacity: 1; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-selected] .filetree-iconpair .filetree-icon--mono { + opacity: 0; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-chevron"] { + color: var(--v2-text-text-muted); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-chevron"] svg { + transition: transform 120ms ease; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-chevron"]:not([data-expanded]) svg { + transform: rotate(-90deg); +} + +[data-component="file-tree-v2"]:dir(rtl) [data-slot="file-tree-v2-chevron"]:not([data-expanded]) svg { + transform: rotate(90deg); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-row"][data-selected] [data-slot="file-tree-v2-chevron"] { + color: var(--v2-text-text-base); +} + +[data-component="file-tree-v2"] .filetree-iconpair { + position: relative; + display: inline-flex; + width: 16px; + height: 16px; +} + +[data-component="file-tree-v2"] .filetree-iconpair [data-component="file-icon"] { + position: absolute; + inset: 0; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"] { + box-sizing: border-box; + flex: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + font-size: 11px; + font-weight: 530; + line-height: 1; + letter-spacing: -0.04px; + text-align: center; + text-transform: uppercase; + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: + "tnum" on, + "lnum" on; +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="modified"] { + color: var(--v2-state-fg-info); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="added"] { + color: var(--v2-state-fg-success); +} + +[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="deleted"] { + color: var(--v2-state-fg-danger); +} diff --git a/packages/ui/src/v2/components/icon-button-v2.css b/packages/ui/src/v2/components/icon-button-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..a90a101a081de2aa9e154cce23c58316acefd7e1 --- /dev/null +++ b/packages/ui/src/v2/components/icon-button-v2.css @@ -0,0 +1,159 @@ +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: geometricPrecision; +} + +[data-component="icon-button-v2"] { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + color: var(--v2-text-text-base); + user-select: none; + outline: none; + padding: 0; + cursor: default; +} + +[data-component="icon-button-v2"]:focus { + outline: none; +} + +[data-component="icon-button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled) { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 2.5px; +} + +[data-component="icon-button-v2"][data-size="small"] { + width: 20px; + height: 20px; + border-radius: 4px; +} + +[data-component="icon-button-v2"][data-size="normal"] { + width: 24px; + height: 24px; +} + +[data-component="icon-button-v2"][data-size="large"] { + width: 28px; + height: 28px; +} + +[data-component="icon-button-v2"] [data-slot="icon-svg"] { + color: currentColor; +} + +/* Neutral */ +[data-component="icon-button-v2"][data-variant="neutral"] { + background-color: var(--v2-background-bg-button-neutral); + color: var(--v2-text-text-base); + box-shadow: var(--v2-elevation-button-neutral); +} + +[data-component="icon-button-v2"][data-variant="neutral"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="icon-button-v2"][data-variant="neutral"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%), + linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%); +} + +[data-component="icon-button-v2"][data-variant="neutral"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Contrast */ +[data-component="icon-button-v2"][data-variant="contrast"] { + background-image: + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); + color: var(--v2-text-text-contrast); + text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3); + box-shadow: var(--v2-elevation-button-contrast); +} + +[data-component="icon-button-v2"][data-variant="contrast"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-image: + linear-gradient( + 90deg, + var(--v2-overlay-simple-overlay-contrast-hover) 0%, + var(--v2-overlay-simple-overlay-contrast-hover) 100% + ), + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); +} + +[data-component="icon-button-v2"][data-variant="contrast"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-image: + linear-gradient( + 90deg, + var(--v2-overlay-simple-overlay-contrast-pressed) 0%, + var(--v2-overlay-simple-overlay-contrast-pressed) 100% + ), + linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), + linear-gradient(90deg, var(--v2-background-bg-contrast) 0%, var(--v2-background-bg-contrast) 100%); +} + +[data-component="icon-button-v2"][data-variant="contrast"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.4; + cursor: not-allowed; +} + +/* Ghost */ +[data-component="icon-button-v2"][data-variant="ghost"] { + background-color: transparent; + color: var(--v2-text-text-base); +} + +[data-component="icon-button-v2"][data-variant="ghost"]:is(:hover, [data-state="hover"]):not(:disabled):not( + [data-expanded] + ) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="icon-button-v2"][data-variant="ghost"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="icon-button-v2"][data-variant="ghost"]:where([data-expanded]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="icon-button-v2"][data-variant="ghost"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} + +/* Ghost */ +[data-component="icon-button-v2"][data-variant="ghost-muted"] { + background-color: transparent; + color: var(--v2-icon-icon-muted); +} + +[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:hover, [data-state="hover"]):not(:disabled):not( + [data-expanded] + ) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); + color: var(--v2-icon-icon-base); +} + +[data-component="icon-button-v2"][data-variant="ghost-muted"]:where([data-expanded]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); + color: var(--v2-icon-icon-base); +} + +[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/packages/ui/src/v2/components/icon-button-v2.stories.tsx b/packages/ui/src/v2/components/icon-button-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e32100103f96403627f1bc1fbd04c2ed3d763995 --- /dev/null +++ b/packages/ui/src/v2/components/icon-button-v2.stories.tsx @@ -0,0 +1,105 @@ +import { IconButtonV2 } from "./icon-button-v2" + +const docs = `### Overview +Square icon-only button v2 with three visual variants and three sizes. + +### API +- \`icon\`: Icon name from the icon component. +- \`variant\`: "neutral" | "contrast" | "ghost". +- \`size\`: "small" | "normal" | "large". +- \`iconSize\`: Optional explicit icon size override. +- Inherits Kobalte Button props and native button attributes. + +### States +- default, hover, pressed, focus, disabled. +- State selectors are available via pseudo-classes and \`[data-state]\`. +` + +export default { + title: "UI V2/IconButton", + id: "components-icon-button-v2", + component: IconButtonV2, + tags: ["autodocs"], + parameters: { + frameHeight: "300px", + frameBackground: "#fff", + docs: { + description: { + component: docs, + }, + }, + }, + args: { + icon: "plus", + variant: "neutral", + size: "normal", + }, + argTypes: { + icon: { + control: "text", + }, + variant: { + control: "select", + options: ["neutral", "contrast", "ghost"], + }, + size: { + control: "select", + options: ["small", "normal", "large"], + }, + iconSize: { + control: "select", + options: ["small", "normal", "large"], + }, + }, +} + +export const Playground = {} + +export const Variants = { + render: () => ( +
+ + + +
+ ), +} + +export const Sizes = { + render: () => ( +
+ + + +
+ ), +} + +export const AllStates = { + render: () => { + const variants = ["neutral", "contrast", "ghost"] as const + const states = ["default", "hover", "pressed", "focus", "disabled"] as const + + return ( +
+ {variants.map((variant) => ( +
+
+ {variant} +
+
+ {states.map((state) => ( + + ))} +
+
+ ))} +
+ ) + }, +} diff --git a/packages/ui/src/v2/components/icon-button-v2.tsx b/packages/ui/src/v2/components/icon-button-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1f7ce3efabf2df3c1281db470cf38d11fa9d162e --- /dev/null +++ b/packages/ui/src/v2/components/icon-button-v2.tsx @@ -0,0 +1,37 @@ +import { Button as Kobalte } from "@kobalte/core/button" +import { type ComponentProps, splitProps } from "solid-js" +import { JSX } from "solid-js" +import "./icon-button-v2.css" + +export interface IconButtonV2Props + extends ComponentProps, + Pick, "class" | "classList"> { + // temporary + icon?: JSX.Element + // icon: IconProps["name"] + size?: "small" | "normal" | "large" + // iconSize?: IconProps["size"] + variant?: "neutral" | "contrast" | "ghost" | "ghost-muted" + state?: "rest" | "hover" | "pressed" +} + +export function IconButtonV2(props: ComponentProps<"button"> & IconButtonV2Props) { + const [split, rest] = splitProps(props, ["variant", "size", "iconSize", "class", "classList", "state"]) + return ( + + {props.icon} + {/**/} + + ) +} diff --git a/packages/ui/src/v2/components/icon.tsx b/packages/ui/src/v2/components/icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b22d3140fe589b32b0182ba21ec8d96e8a28f0a9 --- /dev/null +++ b/packages/ui/src/v2/components/icon.tsx @@ -0,0 +1,209 @@ +import { onMount, type ComponentProps, splitProps } from "solid-js" + +const icons = { + edit: { + viewBox: "0 0 16 16", + body: ``, + }, + "folder-add-left": { + viewBox: "0 0 16 16", + body: ``, + }, + folder: { + viewBox: "0 0 16 16", + body: ``, + }, + branch: { + viewBox: "0 0 16 16", + body: ``, + }, + "grid-plus": { + viewBox: "0 0 16 16", + body: ``, + }, + help: { + viewBox: "0 0 16 16", + body: ``, + }, + "sidebar-right": { + viewBox: "0 0 20 20", + body: ``, + }, + status: { + viewBox: "0 0 20 20", + body: ``, + }, + "status-active": { + viewBox: "0 0 20 20", + body: ``, + }, + "magnifying-glass": { + viewBox: "0 0 16 16", + body: ``, + }, + menu: { + viewBox: "0 0 16 16", + body: ``, + }, + plus: { + viewBox: "0 0 16 16", + body: ``, + }, + "settings-gear": { + viewBox: "0 0 16 16", + body: ``, + }, + "chevron-down": { + viewBox: "0 0 16 16", + body: ``, + }, + collapse: { + viewBox: "0 0 16 16", + body: ``, + }, + check: { + viewBox: "0 0 16 16", + body: ``, + }, + monitor: { + viewBox: "0 0 16 16", + body: ``, + }, + "workspace-new": { + viewBox: "0 0 16 16", + body: ``, + }, + "workspace-isolated": { + viewBox: "0 0 16 16", + body: ``, + }, + workspace: { + viewBox: "0 0 16 16", + body: ``, + }, + close: { + viewBox: "0 0 20 20", + body: ``, + }, + "xmark-small": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-xmark": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-chevron-down": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-dots": { + viewBox: "0 0 16 16", + body: ``, + }, + expand: { + viewBox: "0 0 16 16", + body: ``, + }, + filetree: { + viewBox: "0 0 16 16", + body: ``, + }, + split: { + viewBox: "0 0 16 16", + body: ``, + }, + unified: { + viewBox: "0 0 16 16", + body: ``, + }, + review: { + viewBox: "0 0 20 20", + body: ``, + }, + "outline-sliders": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-copy": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-square-arrow": { + viewBox: "0 0 16 16", + body: ``, + }, + "outline-share": { + viewBox: "0 0 16 16", + body: ``, + }, + reset: { + viewBox: "0 0 20 20", + body: ``, + }, + "outline-reset": { + viewBox: "0 0 20 20", + body: ``, + }, + archive: { + viewBox: "0 0 16 16", + body: ``, + }, +} + +const spriteID = "opencode-v2-icon-sprite" +const symbol = (name: keyof typeof icons) => `opencode-v2-icon-${name}` +let spriteInserted = false + +function ensureSprite() { + if (spriteInserted) return + if (typeof document === "undefined") return + if (document.getElementById(spriteID)) { + spriteInserted = true + return + } + + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") + svg.id = spriteID + svg.setAttribute("aria-hidden", "true") + svg.setAttribute("width", "0") + svg.setAttribute("height", "0") + svg.style.position = "absolute" + svg.style.overflow = "hidden" + svg.innerHTML = Object.entries(icons) + .map( + ([name, icon]) => + `${icon.body}`, + ) + .join("") + document.body.insertBefore(svg, document.body.firstChild) + spriteInserted = true +} + +export interface IconProps extends ComponentProps<"svg"> { + name: keyof typeof icons | (string & {}) + size?: "small" | "normal" | "large" +} + +export function Icon(props: IconProps) { + const [split, rest] = splitProps(props, ["name", "size"]) + const iconName = () => (icons[split.name as keyof typeof icons] ? (split.name as keyof typeof icons) : "plus") + const icon = () => icons[iconName()] + const pixelSize = split.size === "small" ? 14 : split.size === "large" ? 20 : 16 + onMount(ensureSprite) + + return ( + + + + ) +} diff --git a/packages/ui/src/v2/components/inline-input-v2.css b/packages/ui/src/v2/components/inline-input-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..c2f13e605da08549479d5ae4fbd97aa3f3873598 --- /dev/null +++ b/packages/ui/src/v2/components/inline-input-v2.css @@ -0,0 +1,225 @@ +[data-component="inline-input-v2"] { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: stretch; + padding: 0; + width: 280px; + height: 28px; + border: 0; + border-radius: 6px; + outline: 1px solid transparent; + outline-offset: 0; + background: + linear-gradient(180deg, var(--v2-alpha-light-2) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-base); + box-shadow: var(--v2-elevation-button-neutral); + flex: none; + align-self: stretch; + overflow: hidden; + transition: + background 85ms ease-out, + outline-color 85ms ease-out, + box-shadow 85ms ease-out; +} + +[data-component="inline-input-v2"][data-appearance="large"] { + height: 32px; +} + +[data-component="inline-input-v2"]:where(:hover):not([data-disabled], [data-invalid]):not(:focus-within) { + background: + linear-gradient(0deg, var(--v2-overlay-simple-overlay-hover), var(--v2-overlay-simple-overlay-hover)), + linear-gradient(180deg, var(--v2-alpha-light-2) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-base); +} + +[data-component="inline-input-v2"]:where(:hover):not([data-disabled], [data-invalid]):not(:focus-within) + [data-slot="inline-input-v2-prefix"] { + background: transparent; +} + +[data-component="inline-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) { + outline-color: var(--v2-border-border-focus); + box-shadow: none; +} + +[data-component="inline-input-v2"]:where([data-invalid]):not([data-disabled]) { + outline-color: var(--v2-state-fg-danger); + box-shadow: none; +} + +[data-component="inline-input-v2"]:where([data-disabled]) { + opacity: 0.5; + cursor: not-allowed; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-prefix"] { + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + flex: none; + align-self: stretch; + width: fit-content; + min-width: 0; + max-width: 100%; + padding: 4px 8px; + gap: 4px; + background: var(--v2-background-bg-layer-01); + border-radius: 4px 0 0 4px; + cursor: default; + transition: background 85ms ease-out; +} + +[data-component="inline-input-v2"]:where([data-disabled]) [data-slot="inline-input-v2-prefix"] { + cursor: not-allowed; +} + +[data-component="inline-input-v2"][data-label-width] [data-slot="inline-input-v2-prefix"] { + width: var(--inline-input-v2-label-width); + min-width: var(--inline-input-v2-label-width); + max-width: var(--inline-input-v2-label-width); +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-prefix-text"] { + display: block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-style: normal; + font-weight: 440; + font-size: 13px; + line-height: 1; + letter-spacing: -0.04px; + color: var(--v2-text-text-muted); + font-variation-settings: "slnt" 0; + user-select: none; +} + +[data-component="inline-input-v2"][data-numeric] [data-slot="inline-input-v2-prefix-text"] { + font-variant-numeric: tabular-nums; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-divider"] { + flex: none; + align-self: stretch; + width: 0.5px; + min-width: 0.5px; + background: var(--v2-border-border-base); +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-field"] { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; + flex: 1 1 auto; + min-width: 0; + padding: 4px 8px; + gap: 8px; +} + +[data-component="inline-input-v2"][data-appearance="large"] [data-slot="inline-input-v2-field"] { + padding-top: 6px; + padding-bottom: 6px; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-value"] { + display: flex; + flex-direction: row; + align-items: center; + flex: 1 1 auto; + min-width: 0; + min-height: 0; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-input"] { + display: block; + width: 100%; + min-width: 0; + height: 100%; + padding: 0; + margin: 0; + border: 0; + background: transparent; + outline: none; + font-style: normal; + font-weight: 440; + font-size: 13px; + line-height: 1; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-input"]::placeholder { + color: var(--v2-text-text-faint); + user-select: none; +} + +[data-component="inline-input-v2"][data-numeric] [data-slot="inline-input-v2-input"] { + font-variant-numeric: tabular-nums; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-icon-button"] { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex: none; + width: 20px; + height: 20px; + padding: 2px; + gap: 3px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--v2-icon-icon-muted); + cursor: pointer; + outline: none; +} + +[data-component="inline-input-v2"] + [data-slot="inline-input-v2-icon-button"]:is(:hover, [data-state="hover"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-component="inline-input-v2"] + [data-slot="inline-input-v2-icon-button"]:is(:active, [data-state="pressed"]):not(:disabled) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-icon-button"]:focus { + outline: none; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-icon-button"]:focus-visible { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 1px; +} + +[data-component="inline-input-v2"]:where([data-disabled]) [data-slot="inline-input-v2-icon-button"] { + cursor: not-allowed; + pointer-events: none; +} + +[data-component="inline-input-v2"] [data-slot="inline-input-v2-icon-button"] [data-slot="icon-svg"] { + display: block; + flex: none; + color: currentColor; +} + +[data-component="inline-input-v2"][data-invalid]:not([data-disabled]) [data-slot="inline-input-v2-prefix-text"] { + color: var(--v2-text-text-muted); +} + +[data-component="inline-input-v2"][data-invalid]:not([data-disabled]) [data-slot="inline-input-v2-input"] { + color: var(--v2-state-fg-danger); + caret-color: var(--v2-state-fg-danger); +} + +[data-component="inline-input-v2"][data-invalid]:not([data-disabled]) [data-slot="inline-input-v2-input"]::placeholder { + color: var(--v2-state-fg-danger); + opacity: 1; +} diff --git a/packages/ui/src/v2/components/inline-input-v2.stories.tsx b/packages/ui/src/v2/components/inline-input-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c1871dc5c978cdadb66ff2d97f4c075b9ad8c8a0 --- /dev/null +++ b/packages/ui/src/v2/components/inline-input-v2.stories.tsx @@ -0,0 +1,141 @@ +// @ts-nocheck +import { createSignal } from "solid-js" +import { Field as FieldV2 } from "./field-v2" +import { InlineInputV2 } from "./inline-input-v2" + +const docs = `### Overview +Single-line field with an inline prefix label, vertical divider, and the same states as TextInput v2. + +### API +- \`prefix\`: Inline label in the leading segment (required). +- \`labelWidth\`: Fixed prefix width (px number or CSS length). Omit for fit-content. +- Forwards native \`input\` props (\`value\`, \`defaultValue\`, \`placeholder\`, \`disabled\`, etc.). +- \`showCopyButton\`, \`copyLabel\`, \`onCopyClick\`: Optional trailing copy control. +- \`invalid\`: Error outline and danger text color. +- \`appearance\`: \`"base"\` (28px) or \`"large"\` (32px). +- \`numeric\`: Tabular numerals on prefix and value. + +### States +- **Hover**, **Focus**, **Invalid**, **Disabled** — same as TextInput v2 on the outer shell. + +### Field +Compose with \`Field\` for label, helper prefix/suffix, and tooltip — see the **Field** story. +` + +export default { + title: "UI V2/InlineInput", + id: "components-inline-input-v2", + component: InlineInputV2, + tags: ["autodocs"], + parameters: { + frameHeight: "400px", + frameBackground: "#fff", + docs: { + description: { + component: docs, + }, + }, + }, + args: { + prefix: "Label", + placeholder: "Text", + showCopyButton: true, + disabled: false, + invalid: false, + appearance: "base", + }, + argTypes: { + prefix: { + control: "text", + }, + labelWidth: { + control: "number", + }, + appearance: { + control: "select", + options: ["base", "large"], + }, + showCopyButton: { + control: "boolean", + }, + disabled: { + control: "boolean", + }, + invalid: { + control: "boolean", + }, + placeholder: { + control: "text", + }, + }, +} + +export const Playground = {} + +export const Controlled = { + render: () => { + const [value, setValue] = createSignal("42") + return ( +
+ setValue(e.currentTarget.value)} + placeholder="0.00" + numeric + /> +
+ Value: {value()} +
+
+ ) + }, +} + +export const Appearances = { + render: () => ( +
+ + + + +
+ ), +} + +export const Field = { + parameters: { frameHeight: "500px" }, + render: () => ( +
+ + Label + Prefix + + Suffix + + + Label + Prefix + + Suffix + +
+ ), +} + +export const States = { + render: () => ( +
+ + + + +
+ ), +} diff --git a/packages/ui/src/v2/components/inline-input-v2.tsx b/packages/ui/src/v2/components/inline-input-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4f8a0cc0744a432bc44dc20c80efe7442340484c --- /dev/null +++ b/packages/ui/src/v2/components/inline-input-v2.tsx @@ -0,0 +1,107 @@ +import { type ComponentProps, type JSX, Show, splitProps } from "solid-js" +import { Icon } from "./icon" +import { useI18n } from "../../context/i18n" +import "./inline-input-v2.css" + +export interface InlineInputV2Props extends Omit, "type" | "prefix"> { + /** Inline label shown before the field (prefix segment). */ + prefix: JSX.Element + /** Fixed width for the prefix segment (px number or CSS length). Omit for fit-content. */ + labelWidth?: number | string + /** Show the trailing copy action. */ + showCopyButton?: boolean + /** Accessible label for the copy button. */ + copyLabel?: string + onCopyClick?: (event: MouseEvent) => void + /** Apply tabular numerals to the prefix and field value. */ + numeric?: boolean + /** Error styling for the field and value text. */ + invalid?: boolean + /** `base` is 28px tall; `large` is 32px tall. */ + appearance?: "base" | "large" + type?: ComponentProps<"input">["type"] +} + +export function InlineInputV2(props: InlineInputV2Props) { + const i18n = useI18n() + const [local, inputProps] = splitProps(props, [ + "class", + "classList", + "prefix", + "labelWidth", + "showCopyButton", + "copyLabel", + "onCopyClick", + "numeric", + "invalid", + "appearance", + "disabled", + "style", + ]) + + let input: HTMLInputElement | undefined + + return ( +
+
{ + if (local.disabled || event.button !== 0) return + // Keep focus on the input without using a native
+ + ) +} diff --git a/packages/ui/src/v2/components/keybind-v2.css b/packages/ui/src/v2/components/keybind-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..ccd6dcee3efbd76f728ddf760c49cec173a57ba7 --- /dev/null +++ b/packages/ui/src/v2/components/keybind-v2.css @@ -0,0 +1,76 @@ +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: geometricPrecision; +} + +[data-component="keybind-v2"] { + box-sizing: border-box; + font-variant-numeric: tabular-nums; + display: inline-flex; + flex-direction: row; + align-items: center; + padding: 0; + gap: 2px; + flex-shrink: 0; +} + +[data-component="keybind-v2"] *, +[data-component="keybind-v2"] *::before, +[data-component="keybind-v2"] *::after { + box-sizing: border-box; +} + +[data-component="keybind-v2"] [data-slot="keybind-v2-key"] { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 0; + gap: 4px; + min-width: 14px; + height: 14px; + border-radius: 2px; + flex: none; + flex-grow: 0; +} + +[data-component="keybind-v2"][data-variant="neutral"] [data-slot="keybind-v2-key"] { + background: var(--v2-background-bg-layer-03); +} + +[data-component="keybind-v2"][data-variant="ghost"] [data-slot="keybind-v2-key"] { + background: transparent; +} + +[data-component="keybind-v2"] [data-slot="keybind-v2-label"] { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + min-width: 14px; + height: 11px; + padding: 0; + flex: none; + font-style: normal; + font-weight: 530; + font-size: 11px; + line-height: 1; + text-align: center; + letter-spacing: 0.05px; + text-transform: uppercase; + font-variant-numeric: tabular-nums; + font-feature-settings: + "tnum" on, + "lnum" on; + font-variation-settings: "slnt" 0; + user-select: none; +} + +[data-component="keybind-v2"][data-variant="neutral"] [data-slot="keybind-v2-label"] { + color: var(--v2-text-text-muted); +} + +[data-component="keybind-v2"][data-variant="ghost"] [data-slot="keybind-v2-label"] { + color: var(--v2-text-text-faint); +} diff --git a/packages/ui/src/v2/components/keybind-v2.stories.tsx b/packages/ui/src/v2/components/keybind-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a50f9401d9b3c4735cf2785b86fc316e00a48069 --- /dev/null +++ b/packages/ui/src/v2/components/keybind-v2.stories.tsx @@ -0,0 +1,82 @@ +import { KeybindV2 } from "./keybind-v2" + +const docs = `### Overview +Inline keybind indicator that renders one or more keyboard keys in a compact row. + +### API +- \`keys\`: Array of key labels to display (e.g. \`["⌘", "K"]\`). +- \`variant\`: "neutral" (gray background) | "ghost" (no background). +- Inherits native div attributes. + +### Variants +- **Neutral** — each key sits on a \`#D4D4D4\` pill with darker text. +- **Ghost** — keys render without a background, lighter text color. +` + +export default { + title: "UI V2/Keybind", + id: "components-keybind-v2", + component: KeybindV2, + tags: ["autodocs"], + parameters: { + frameHeight: "200px", + frameBackground: "#fff", + docs: { + description: { + component: docs, + }, + }, + }, + args: { + keys: ["⌘"], + variant: "neutral", + }, + argTypes: { + keys: { + control: "object", + }, + variant: { + control: "select", + options: ["neutral", "ghost"], + }, + }, +} + +export const Playground = {} + +export const Variants = { + render: () => ( +
+ + +
+ ), +} + +export const MultipleKeys = { + render: () => ( +
+ + +
+ ), +} + +export const AllExamples = { + render: () => ( +
+
+ Neutral + + + +
+
+ Ghost + + + +
+
+ ), +} diff --git a/packages/ui/src/v2/components/keybind-v2.tsx b/packages/ui/src/v2/components/keybind-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4286465613adfff33b0e15a58c6d1c49f56cd1b6 --- /dev/null +++ b/packages/ui/src/v2/components/keybind-v2.tsx @@ -0,0 +1,30 @@ +import { type ComponentProps, For, splitProps } from "solid-js" +import "./keybind-v2.css" + +export interface KeybindV2Props extends ComponentProps<"div"> { + keys: string[] + variant?: "neutral" | "ghost" +} + +export function KeybindV2(props: KeybindV2Props) { + const [local, rest] = splitProps(props, ["keys", "variant", "class", "classList"]) + return ( +
+ + {(key) => ( +
+ {key} +
+ )} +
+
+ ) +} diff --git a/packages/ui/src/v2/components/line-comment-v2.css b/packages/ui/src/v2/components/line-comment-v2.css new file mode 100644 index 0000000000000000000000000000000000000000..cd2406eb4c87abc9f3adccae6c66d656b22b3281 --- /dev/null +++ b/packages/ui/src/v2/components/line-comment-v2.css @@ -0,0 +1,258 @@ +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: geometricPrecision; +} + +[data-component="line-comment-v2"] { + box-sizing: border-box; + font-family: var(--v2-font-family-sans); + font-variant-numeric: tabular-nums; + min-width: 0; + width: 100%; + max-width: 400px; +} + +[data-component="line-comment-v2"] *, +[data-component="line-comment-v2"] *::before, +[data-component="line-comment-v2"] *::after { + box-sizing: border-box; +} + +[data-component="line-comment-v2"] [data-slot="line-comment-v2-shell"] { + background: var(--v2-background-bg-layer-01); + border-radius: 6px; + box-shadow: var(--v2-elevation-raised); +} + +/* --- Display (read) --- */ +[data-component="line-comment-v2"][data-variant="display"] [data-slot="line-comment-v2-shell"] { + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 12px; + gap: 8px; +} + +[data-component="line-comment-v2"][data-variant="display"] [data-slot="line-comment-v2-column"] { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + flex: 1 1 auto; + min-width: 0; +} + +[data-component="line-comment-v2"][data-variant="display"] [data-slot="line-comment-v2-text"] { + margin: 0; + width: 100%; + font-size: 13px; + font-style: normal; + font-weight: 440; + line-height: 1; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; +} + +[data-component="line-comment-v2"][data-variant="display"] [data-slot="line-comment-v2-meta"] { + font-size: 11px; + font-style: normal; + font-weight: 530; + line-height: 1; + letter-spacing: 0.05px; + color: var(--v2-text-text-faint); + font-variation-settings: "slnt" 0; +} + +[data-component="line-comment-v2"][data-variant="display"] [data-slot="line-comment-v2-tools"] { + display: flex; + flex-direction: row; + align-items: flex-start; + flex-shrink: 0; +} + +[data-slot="line-comment-v2-overflow"] { + display: inline-flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + padding: 2px; + gap: 3px; + margin: 0; + border: none; + border-radius: 4px; + background: transparent; + color: var(--v2-icon-icon-muted); + cursor: pointer; + outline: none; +} + +[data-slot="line-comment-v2-overflow"]:focus { + outline: none; +} + +[data-slot="line-comment-v2-overflow"]:focus-visible { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 2px; +} + +[data-slot="line-comment-v2-overflow"]:is(:hover, [data-state="hover"]) { + background-color: var(--v2-overlay-simple-overlay-hover); +} + +[data-slot="line-comment-v2-overflow"]:is(:active, [data-state="pressed"]) { + background-color: var(--v2-overlay-simple-overlay-pressed); +} + +[data-slot="line-comment-v2-overflow"] svg { + display: block; + flex-shrink: 0; +} + +/* --- Editor --- */ +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-shell"] { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 12px; + gap: 12px; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-field"] { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + width: 100%; + min-width: 0; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-label"] { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + font-size: 13px; + font-style: normal; + font-weight: 530; + line-height: 1; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + user-select: none; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-textarea"] { + display: block; + width: 100%; + min-width: 0; + min-height: 80px; + padding: 8px; + margin: 0; + resize: vertical; + border: 1px solid var(--v2-border-border-base); + border-radius: 6px; + background: + linear-gradient(180deg, var(--v2-alpha-light-2) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-base); + font-size: 13px; + font-style: normal; + font-weight: 440; + line-height: 1.35; + letter-spacing: -0.04px; + color: var(--v2-text-text-base); + font-variation-settings: "slnt" 0; + outline: none; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-textarea"]::placeholder { + color: var(--v2-text-text-faint); + user-select: none; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-textarea"]:focus { + border-color: var(--v2-border-border-focus); +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-footer"] { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + width: 100%; + min-width: 0; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-footer-meta"] { + flex: 1 1 auto; + min-width: 0; + font-size: 11px; + font-style: normal; + font-weight: 530; + line-height: 1; + letter-spacing: 0.05px; + color: var(--v2-text-text-faint); + font-variation-settings: "slnt" 0; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-footer-actions"] { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-list"] { + display: flex; + flex-direction: column; + gap: 4px; + max-height: 180px; + overflow: auto; + margin-top: 8px; + padding: 4px; + border: 0.5px solid var(--v2-border-border-muted); + border-radius: 6px; + background: var(--v2-background-bg-layer-02); +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"] { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + min-width: 0; + padding: 6px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--v2-text-text-base); + text-align: start; + cursor: pointer; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"][data-active] { + background: var(--v2-background-bg-layer-03); +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-path"] { + display: flex; + align-items: center; + min-width: 0; + font-size: 12px; + line-height: 16px; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-dir"] { + min-width: 0; + color: var(--v2-text-text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-file"] { + color: var(--v2-text-text-base); + white-space: nowrap; +} diff --git a/packages/ui/src/v2/components/line-comment-v2.stories.tsx b/packages/ui/src/v2/components/line-comment-v2.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6284408d00e775bae979cf5b2b8b8cd8ccefce04 --- /dev/null +++ b/packages/ui/src/v2/components/line-comment-v2.stories.tsx @@ -0,0 +1,88 @@ +// @ts-nocheck +import { createSignal } from "solid-js" +import { LineCommentEditorV2, LineCommentV2, LineCommentV2OverflowIcon } from "./line-comment-v2" + +const docs = `### Overview +Line comment **display** and **editor** cards aligned with OpenCode line-comment specs (raised \`#FAFAFA\` surface, footer line context, \`ButtonV2\` neutral + contrast actions). + +### Display +- \`LineCommentV2\`: column stack (body + meta) beside optional \`actions\` (overflow). +- Use \`LineCommentV2OverflowIcon\` inside a \`data-slot="line-comment-v2-overflow"\` button for the Figma dots control. + +### Editor +- \`LineCommentEditorV2\`: optional \`heading\` above the textarea (default “Comment”), footer (selection meta + Cancel / Comment). +- \`Enter\` submits (Shift+Enter newline); \`Escape\` cancels. Controlled via \`value\` / \`onInput\`. +` + +export default { + title: "UI V2/LineComment", + id: "components-line-comment-v2", + component: LineCommentV2, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, +} + +export const Display = { + render: () => ( +
+ + + + } + /> +
+ ), +} + +export const DisplayWithoutActions = { + render: () => ( +
+ +
+ ), +} + +export const Editor = { + render: () => { + const [value, setValue] = createSignal("") + return ( +
+ setValue("")} + onSubmit={() => setValue("")} + selection="Comment on line 40" + /> +
+ ) + }, +} + +export const EditorFilled = { + render: () => { + const [value, setValue] = createSignal("Use a sentinel or early return when the list is empty.") + return ( +
+ setValue("")} + onSubmit={() => {}} + selection="Comment on line 40" + autofocus={false} + /> +
+ ) + }, +} diff --git a/packages/ui/src/v2/components/line-comment-v2.tsx b/packages/ui/src/v2/components/line-comment-v2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..056b3ddb33700a1a1e8b88254a7f9eda98ed3ee0 --- /dev/null +++ b/packages/ui/src/v2/components/line-comment-v2.tsx @@ -0,0 +1,306 @@ +import { For, Show, createSignal, onMount, splitProps, type ComponentProps, type JSX } from "solid-js" +import { FileIcon } from "../../components/file-icon" +import { useI18n } from "../../context/i18n" +import { useFilteredList } from "../../hooks" +import { ButtonV2 } from "./button-v2" +import "./line-comment-v2.css" + +/** Horizontal “more” glyph for the display-card overflow control (Figma outline-dots). */ +export function LineCommentV2OverflowIcon(props: ComponentProps<"svg">) { + return ( + + + + + + ) +} + +export interface LineCommentV2Props extends ComponentProps<"div"> { + /** Main comment body (text or rich content). */ + comment: JSX.Element + /** Line / selection context (e.g. “Comment on line 40”). */ + selection: JSX.Element + /** Typically an overflow menu trigger; use `LineCommentV2OverflowIcon` inside `line-comment-v2-overflow`. */ + actions?: JSX.Element +} + +export function LineCommentV2(props: LineCommentV2Props) { + const [local, rest] = splitProps(props, ["comment", "selection", "actions", "class", "classList"]) + return ( +
+
+
+
{local.comment}
+
{local.selection}
+
+ {(actions) =>
{actions()}
}
+
+
+ ) +} + +export type LineCommentEditorV2Mention = { + items: (query: string) => string[] | Promise +} + +export interface LineCommentEditorV2Props extends Omit, "children" | "onInput" | "onSubmit"> { + /** Visible field label above the textarea (default: “Comment”). */ + heading?: JSX.Element | string + value: string + onInput: (value: string) => void + onCancel: () => void + onSubmit: (value: string) => void + selection: JSX.Element + placeholder?: string + rows?: number + cancelLabel?: string + submitLabel?: string + autofocus?: boolean + mention?: LineCommentEditorV2Mention +} + +function pathFilename(path: string) { + const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + return index === -1 ? path : path.slice(index + 1) +} + +function pathDirectory(path: string) { + const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + return index === -1 ? "" : path.slice(0, index + 1) +} + +export function LineCommentEditorV2(props: LineCommentEditorV2Props) { + const i18n = useI18n() + let textareaRef: HTMLTextAreaElement | undefined + const [mentionOpen, setMentionOpen] = createSignal(false) + + const [local, rest] = splitProps(props, [ + "heading", + "value", + "onInput", + "onCancel", + "onSubmit", + "selection", + "placeholder", + "rows", + "cancelLabel", + "submitLabel", + "autofocus", + "mention", + "class", + "classList", + ]) + + const heading = () => local.heading ?? i18n.t("ui.lineComment.submit") + const canSubmit = () => local.value.trim().length > 0 + + const closeMention = () => { + setMentionOpen(false) + mention.clear() + } + + const currentMention = () => { + const textarea = textareaRef + if (!textarea) return + if (!local.mention) return + if (textarea.selectionStart !== textarea.selectionEnd) return + + const end = textarea.selectionStart + const match = textarea.value.slice(0, end).match(/@(\S*)$/) + if (!match) return + + return { + query: match[1] ?? "", + start: end - match[0].length, + end, + } + } + + function selectMention(item: { path: string } | undefined) { + if (!item) return + + const textarea = textareaRef + const query = currentMention() + if (!textarea || !query) return + + const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}` + const cursor = query.start + item.path.length + 2 + + local.onInput(value) + closeMention() + + requestAnimationFrame(() => { + textarea.focus() + textarea.setSelectionRange(cursor, cursor) + }) + } + + const mention = useFilteredList<{ path: string }>({ + items: async (query) => { + if (!local.mention) return [] + if (!query.trim()) return [] + const paths = await local.mention.items(query) + return paths.map((path) => ({ path })) + }, + key: (item) => item.path, + filterKeys: ["path"], + skipFilter: () => true, + onSelect: selectMention, + }) + + const syncMention = () => { + const item = currentMention() + if (!item) { + closeMention() + return + } + + setMentionOpen(true) + mention.onInput(item.query) + } + + const selectActiveMention = () => { + const items = mention.flat() + if (items.length === 0) return + const active = mention.active() + selectMention(items.find((item) => item.path === active) ?? items[0]) + } + + const submit = () => { + const v = local.value.trim() + if (!v) return + local.onSubmit(v) + } + + onMount(() => { + if (local.autofocus === false) return + requestAnimationFrame(() => textareaRef?.focus()) + }) + + return ( +
+
+
+
{heading()}
+