fix(ui): align Home controls and theme the Code editor
This commit is contained in:
parent
f61a3572f3
commit
2328b4cb8f
|
|
@ -214,6 +214,7 @@ Keep responsibilities distinct: engine tests cover state contracts, Playwright b
|
|||
- Colocate `ComponentName.stories.ts` with `ComponentName.vue`. Multipart composition stories may use a descriptive family name. Preserve explicit Storybook titles and exported story names during file moves; keep default playgrounds static and give interaction flows named stories. Use deterministic fixtures and colocated Vue demos for substantial markup.
|
||||
- `src/components/ui/**` is store-free app design-system code; feature controls stay in their domain.
|
||||
- SDK property primitives remain controlled/editor-agnostic. Compose property rows from `PanelGrid`, `PanelFieldGroup`, `PanelItemRow`, and `PropertyItemRow`; use `BindableValue`, `FillRoot`, and `FillSwatch` rather than rebuilding binding/picker infrastructure.
|
||||
- Do not add automated tests or snapshot baselines for simple CSS-only UI changes, including spacing, sizing, colors, and responsive breakpoints. Verify these visually instead. Keep automated coverage focused on behavior and contracts; the separate canvas-renderer visual coverage requirement still applies.
|
||||
- Prefer accessible role/name, label, then text in tests. Use scoped `data-slot` anatomy or semantic attributes (`data-property`, `data-command`, `data-node-id`) when needed; reserve `data-test-id` for integration boundaries and never add test-hook props.
|
||||
- Use Reka UI primitives and typed Tailwind Variants themes under `src/theme/**`; merge per-instance `ui` slot overrides, expose `class` for single-root components, and do not add one-off class props. Use `UI` casing in type names.
|
||||
- Bind visual state through semantic `data-*` attributes; Steiger rejects template-time `use*UI()`, visual-state utility branches, and raw SVG app icons.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
- Add local AI usage and technical diagnostics, including token telemetry, provider/model summaries, recent failures, configurable retention, export, and clear controls. (#588)
|
||||
- Import, render, edit, resize, select, and export Figma text-on-path layers while preserving their curved glyph layout.
|
||||
- Show temporary Figma-style distance measurements between selected and Option/Alt-hovered layers. (#491)
|
||||
- Edit Design JSX and HTML/CSS previews in CodeMirror, with Tailwind viewing, completion, diagnostics, bounded execution, and session-level undo. (#130)
|
||||
- Edit Design JSX and HTML/CSS previews in CodeMirror, with theme-aware highlighting, Tailwind viewing, completion, diagnostics, bounded execution, and session-level undo. (#130)
|
||||
- Set provider-specific reasoning effort on supported AI model profiles. (#454)
|
||||
- Show unavailable or substituted document fonts with affected-layer selection and retry actions, and expose font fidelity through the Figma API and MCP tooling. (#503)
|
||||
- Add reusable remote MCP connections for ACP agents, with Streamable HTTP endpoints and credential-backed bearer tokens.
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
### Changed
|
||||
|
||||
- Use compact desktop Home search actions with consistent responsive layout and control sizing.
|
||||
- Keep applied and available Effect styles concise, and collapse equal independent corner fields when all four use the same variable.
|
||||
- Keep pixel-grid rounding invisible while showing alignment guides only for real geometry, objects, and canvas/layout guides.
|
||||
- Copy selections with embedded images into Figma while preserving typed geometry, text sizing, images, components, variables, modes, and shared styles for lossless in-app paste.
|
||||
|
|
|
|||
1
bun.lock
1
bun.lock
|
|
@ -23,6 +23,7 @@
|
|||
"@codemirror/search": "^6.7.1",
|
||||
"@codemirror/state": "^6.7.1",
|
||||
"@codemirror/view": "^6.43.9",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@nanostores/i18n": "^1.3.3",
|
||||
"@nanostores/vue": "^1.1.0",
|
||||
"@open-pencil/cli": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
"@codemirror/search": "^6.7.1",
|
||||
"@codemirror/state": "^6.7.1",
|
||||
"@codemirror/view": "^6.43.9",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@nanostores/i18n": "^1.3.3",
|
||||
"@nanostores/vue": "^1.1.0",
|
||||
"@open-pencil/cli": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -3,14 +3,7 @@ import { closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirro
|
|||
import { defaultKeymap, history, historyKeymap, redo, undo } from '@codemirror/commands'
|
||||
import { html } from '@codemirror/lang-html'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import {
|
||||
bracketMatching,
|
||||
defaultHighlightStyle,
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
indentOnInput,
|
||||
syntaxHighlighting
|
||||
} from '@codemirror/language'
|
||||
import { bracketMatching, foldGutter, foldKeymap, indentOnInput } from '@codemirror/language'
|
||||
import { lintKeymap } from '@codemirror/lint'
|
||||
import { searchKeymap } from '@codemirror/search'
|
||||
import { Compartment, EditorState, Transaction, type Extension } from '@codemirror/state'
|
||||
|
|
@ -25,8 +18,10 @@ import {
|
|||
} from '@codemirror/view'
|
||||
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import { resolvedAppTheme } from '@/app/shell/theme'
|
||||
import { designJSXExtensions } from '@/components/code-editor/extensions'
|
||||
import type { CodeEditorLanguage } from '@/components/code-editor/types'
|
||||
import { codeEditorTheme } from '@/theme/code/editor'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
|
@ -48,6 +43,7 @@ const host = useTemplateRef('host')
|
|||
const languageCompartment = new Compartment()
|
||||
const editableCompartment = new Compartment()
|
||||
const labelCompartment = new Compartment()
|
||||
const themeCompartment = new Compartment()
|
||||
let editor: EditorView | undefined
|
||||
let externalUpdate = false
|
||||
|
||||
|
|
@ -78,7 +74,7 @@ onMounted(() => {
|
|||
drawSelection(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
themeCompartment.of(codeEditorTheme(resolvedAppTheme.value === 'dark')),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
highlightActiveLine(),
|
||||
|
|
@ -97,21 +93,7 @@ onMounted(() => {
|
|||
editableCompartment.of(editableExtensions(readOnly)),
|
||||
labelCompartment.of(EditorView.contentAttributes.of({ 'aria-label': label })),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme({
|
||||
'&': { height: '100%', backgroundColor: 'transparent', color: 'var(--color-surface)' },
|
||||
'.cm-scroller': { overflow: 'auto', fontFamily: 'var(--font-mono)' },
|
||||
'.cm-content': { padding: '12px 0', caretColor: 'var(--color-accent)' },
|
||||
'.cm-line': { padding: '0 12px' },
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'color-mix(in srgb, var(--color-muted) 45%, transparent)',
|
||||
border: 'none'
|
||||
},
|
||||
'&.cm-focused': { outline: 'none' },
|
||||
'.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
|
||||
backgroundColor: 'color-mix(in srgb, var(--color-accent) 22%, transparent)'
|
||||
}
|
||||
}),
|
||||
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (!update.docChanged || externalUpdate) return
|
||||
emit('update:modelValue', update.state.doc.toString())
|
||||
|
|
@ -155,6 +137,10 @@ watch(
|
|||
})
|
||||
)
|
||||
|
||||
watch(resolvedAppTheme, (theme) =>
|
||||
editor?.dispatch({ effects: themeCompartment.reconfigure(codeEditorTheme(theme === 'dark')) })
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => editor?.destroy())
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -39,27 +39,32 @@ watch(
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4 flex flex-col gap-3 sm:mb-6 sm:flex-row sm:items-center">
|
||||
<div class="mb-4 flex flex-col gap-3 md:mb-6 md:flex-row md:items-center">
|
||||
<AppInput
|
||||
ref="searchInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
name="file-search"
|
||||
autocomplete="off"
|
||||
class="sm:flex-1"
|
||||
class="md:flex-1"
|
||||
:density="isMobile ? 'comfortable' : 'compact'"
|
||||
:placeholder="isMobile ? files.searchFiles : files.searchRecentAndStorageFiles"
|
||||
:aria-label="files.searchFiles"
|
||||
>
|
||||
<template #leading><icon-lucide-search class="size-4" /></template>
|
||||
</AppInput>
|
||||
<div class="grid grid-cols-2 gap-2 sm:contents">
|
||||
<AppButton size="lg" variant="outline" data-test-id="home-open-file" @click="openFileDialog">
|
||||
<div class="grid grid-cols-2 gap-2 md:contents">
|
||||
<AppButton
|
||||
:size="isMobile ? 'lg' : 'md'"
|
||||
variant="outline"
|
||||
data-test-id="home-open-file"
|
||||
@click="openFileDialog"
|
||||
>
|
||||
<template #leading><icon-lucide-folder-open class="size-3.5" /></template>
|
||||
{{ menu.open }}
|
||||
</AppButton>
|
||||
<AppButton
|
||||
size="lg"
|
||||
:size="isMobile ? 'lg' : 'md'"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
data-test-id="home-new-document"
|
||||
|
|
|
|||
99
src/theme/code/editor.ts
Normal file
99
src/theme/code/editor.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { tags } from '@lezer/highlight'
|
||||
|
||||
export function codeEditorTheme(dark: boolean) {
|
||||
const palette = dark
|
||||
? {
|
||||
keyword: '#c4a7e7',
|
||||
tag: '#82aaff',
|
||||
string: '#a3d9a5',
|
||||
number: '#e5bc8b',
|
||||
comment: '#969eaa'
|
||||
}
|
||||
: {
|
||||
keyword: '#7947a7',
|
||||
tag: '#145ca4',
|
||||
string: '#276b3b',
|
||||
number: '#965212',
|
||||
comment: '#69727f'
|
||||
}
|
||||
|
||||
return [
|
||||
EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
height: '100%',
|
||||
backgroundColor: 'var(--color-panel)',
|
||||
color: 'var(--color-surface)',
|
||||
fontSize: '12px'
|
||||
},
|
||||
'&.cm-focused': { outline: 'none' },
|
||||
'.cm-scroller': { overflow: 'auto', fontFamily: 'var(--font-mono)', lineHeight: '1.65' },
|
||||
'.cm-content': { padding: '10px 0', caretColor: 'var(--color-accent)' },
|
||||
'.cm-line': { padding: '0 8px' },
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'var(--color-panel)',
|
||||
color: 'var(--color-muted)',
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-lineNumbers .cm-gutterElement': { padding: '0 4px 0 8px', minWidth: '24px' },
|
||||
'.cm-foldGutter .cm-gutterElement': { padding: '0 2px' },
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'color-mix(in srgb, var(--color-hover) 45%, transparent)'
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'var(--color-hover)',
|
||||
color: 'var(--color-surface)'
|
||||
},
|
||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--color-accent)' },
|
||||
'.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
|
||||
backgroundColor: 'color-mix(in srgb, var(--color-accent) 22%, transparent)'
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: 'var(--color-hover)',
|
||||
outline: '1px solid var(--color-border)'
|
||||
},
|
||||
'.cm-tooltip, .cm-panels': {
|
||||
backgroundColor: 'var(--color-panel)',
|
||||
color: 'var(--color-surface)',
|
||||
border: '1px solid var(--color-border)'
|
||||
},
|
||||
'.cm-tooltip': { borderRadius: '6px', overflow: 'hidden' },
|
||||
'.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
||||
backgroundColor: 'var(--color-hover)',
|
||||
color: 'var(--color-surface)'
|
||||
},
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'color-mix(in srgb, var(--color-accent) 20%, transparent)',
|
||||
outline: '1px solid var(--color-accent)'
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: 'color-mix(in srgb, var(--color-accent) 35%, transparent)'
|
||||
},
|
||||
'.cm-textfield, .cm-button': {
|
||||
background: 'var(--color-input)',
|
||||
color: 'var(--color-surface)',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: '4px',
|
||||
font: 'inherit'
|
||||
}
|
||||
},
|
||||
{ dark }
|
||||
),
|
||||
syntaxHighlighting(
|
||||
HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: palette.keyword },
|
||||
{ tag: [tags.tagName, tags.typeName, tags.className], color: palette.tag },
|
||||
{ tag: [tags.string, tags.attributeValue], color: palette.string },
|
||||
{ tag: [tags.number, tags.bool, tags.null], color: palette.number },
|
||||
{ tag: tags.comment, color: palette.comment, fontStyle: 'italic' },
|
||||
{
|
||||
tag: [tags.attributeName, tags.propertyName, tags.variableName],
|
||||
color: 'var(--color-surface)'
|
||||
},
|
||||
{ tag: [tags.punctuation, tags.operator], color: 'var(--color-muted)' }
|
||||
])
|
||||
)
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue