diff --git a/.changeset/many-foxes-invent.md b/.changeset/many-foxes-invent.md new file mode 100644 index 00000000..9bec0dc4 --- /dev/null +++ b/.changeset/many-foxes-invent.md @@ -0,0 +1,5 @@ +--- +'@compai/css-gui': patch +--- + +Add selection state to HTML editor diff --git a/apps/docs/components/playground/Layout.tsx b/apps/docs/components/playground/Layout.tsx index 3eaf96b0..4f5b86eb 100644 --- a/apps/docs/components/playground/Layout.tsx +++ b/apps/docs/components/playground/Layout.tsx @@ -9,8 +9,7 @@ export const Layout = (props: Props) => {
diff --git a/apps/docs/pages/html-editor.tsx b/apps/docs/pages/html-editor.tsx index 4d731749..1c71d8d3 100644 --- a/apps/docs/pages/html-editor.tsx +++ b/apps/docs/pages/html-editor.tsx @@ -1,4 +1,9 @@ -import { HtmlEditor, HtmlRenderer, htmlToEditorSchema } from '@compai/css-gui' +import { + HtmlEditor, + HtmlRenderer, + HtmlEditorProvider, + htmlToEditorSchema, +} from '@compai/css-gui' import { useState } from 'react' // TODO: Handle style attrs @@ -13,10 +18,15 @@ const initialValue = htmlToEditorSchema(` export default function HtmlEditorExample() { const [html, setHtml] = useState(initialValue) + return (
- - + + +
+ +
+
) } diff --git a/packages/gui/src/components/html/Provider.tsx b/packages/gui/src/components/html/Provider.tsx new file mode 100644 index 00000000..b1e35243 --- /dev/null +++ b/packages/gui/src/components/html/Provider.tsx @@ -0,0 +1,53 @@ +import { createContext, ReactNode, useContext, useState } from 'react' +import { htmlToEditorSchema } from '../../lib' +import { HtmlNode, ElementPath } from './types' + +const DEFAULT_HTML_EDITOR_VALUE = { + selected: [], + setSelected: () => {}, + value: htmlToEditorSchema(` +
+

Hello, world!

+

Weeee!

+ + I'm a link! +
+ `), +} + +export type HtmlEditor = { + value: HtmlNode + selected: ElementPath | null + setSelected: (newSelection: ElementPath | null) => void +} + +export function useHtmlEditor() { + const context = useContext(HtmlEditorContext) + return context +} + +const HtmlEditorContext = createContext(DEFAULT_HTML_EDITOR_VALUE) + +type HtmlEditorProviderProps = { + value: HtmlNode + children: ReactNode +} +export function HtmlEditorProvider({ + children, + value, +}: HtmlEditorProviderProps) { + const [selected, setSelected] = useState([]) + + const fullContext = { + value, + selected, + setSelected: (newSelection: ElementPath | null) => + setSelected(newSelection), + } + + return ( + + {children} + + ) +} diff --git a/packages/gui/src/components/html/Renderer.tsx b/packages/gui/src/components/html/Renderer.tsx index 918c943e..6b484326 100644 --- a/packages/gui/src/components/html/Renderer.tsx +++ b/packages/gui/src/components/html/Renderer.tsx @@ -1,34 +1,67 @@ import { toCSSObject } from '../../lib' -import { ElementData } from './types' +import { ElementData, ElementPath } from './types' import { HTMLFontTags } from './FontTags' +import { useHtmlEditor } from './Provider' -interface Props { +interface HtmlRendererProps { value: ElementData + path?: ElementPath } - -export function HtmlRenderer({ value }: Props) { +export function HtmlRenderer({ value }: HtmlRendererProps) { return ( <> - + ) } -function ElementRenderer({ value }: Props) { - const { tagName, attributes = {}, style = {}, children = [] } = value +interface ElementRendererProps { + value: ElementData + path: ElementPath +} +function ElementRenderer({ value, path }: ElementRendererProps) { + const { selected, setSelected } = useHtmlEditor() + const { attributes = {}, style = {}, children = [] } = value const Tag: any = value.tagName || 'div' + const sx = toCSSObject(style) + + if (selected && isSamePath(path, selected)) { + sx.outline = 'thin solid tomato' + } + return ( <> - + { + e.stopPropagation() + setSelected(path) + }} + > {children.map((child, i) => { if (typeof child === 'string') { return child } - return + return })} ) } + +const cleanAttributes = (attributes: Record) => { + const newAttributes = { ...attributes } + + if (newAttributes.href) { + newAttributes.href = '#' + } + + return newAttributes +} + +const isSamePath = (path1: ElementPath, path2: ElementPath) => { + return path1.join('-') === path2.join('-') +} diff --git a/packages/gui/src/components/html/editor.tsx b/packages/gui/src/components/html/editor.tsx index bc784814..a94e8e38 100644 --- a/packages/gui/src/components/html/editor.tsx +++ b/packages/gui/src/components/html/editor.tsx @@ -1,5 +1,5 @@ import { Editor } from '../Editor' -import { HtmlNode, HTMLTag } from './types' +import { HtmlNode, HTMLTag, ElementPath } from './types' import * as Collapsible from '@radix-ui/react-collapsible' import { Fragment, useState } from 'react' import { isNil } from 'lodash-es' @@ -9,6 +9,7 @@ import { Label, Combobox } from '../primitives' import { SelectInput } from '../inputs/SelectInput' import { AttributeEditor } from './AttributeEditor' import { DEFAULT_STYLES } from './default-styles' +import { useHtmlEditor } from './Provider' const HTML_TAGS = [ HTMLTag.P, @@ -26,20 +27,16 @@ const HTML_TAGS = [ HTMLTag.Div, ] -interface EditorProps { - value: HtmlNode +interface HtmlEditorProps { onChange(value: HtmlNode): void } -type ElementPath = number[] - /** * An HTML tree-based editor that lets you add HTML nodes and mess around with their styles */ -export function HtmlEditor({ value, onChange }: EditorProps) { - const [selected, setSelected] = useState( - value ? [0] : null - ) +export function HtmlEditor({ onChange }: HtmlEditorProps) { + const { value, selected, setSelected } = useHtmlEditor() + return (