Browse Source

feat: links panel

Peng Xiao 2 years ago
parent
commit
4e63a82d27

+ 5 - 1
src/main/frontend/extensions/tldraw.cljs

@@ -31,7 +31,11 @@
 
 
 (rum/defc breadcrumb
 (rum/defc breadcrumb
   [props]
   [props]
-  (block/breadcrumb {:preview? true} (state/get-current-repo) (uuid (gobj/get props "blockId")) {:end-separator? true}))
+  (block/breadcrumb {:preview? true}
+                    (state/get-current-repo)
+                    (uuid (gobj/get props "blockId"))
+                    {:end-separator? true
+                     :level-limit (gobj/get props "levelLimit" 3)}))
 
 
 (rum/defc page-name-link
 (rum/defc page-name-link
   [props]
   [props]

+ 11 - 71
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -20,6 +20,7 @@ import { Button } from '../Button'
 import { TablerIcon } from '../icons'
 import { TablerIcon } from '../icons'
 import { ColorInput } from '../inputs/ColorInput'
 import { ColorInput } from '../inputs/ColorInput'
 import { SelectInput, type SelectOption } from '../inputs/SelectInput'
 import { SelectInput, type SelectOption } from '../inputs/SelectInput'
+import { ShapeLinksInput } from '../inputs/ShapeLinksInput'
 import { TextInput } from '../inputs/TextInput'
 import { TextInput } from '../inputs/TextInput'
 import {
 import {
   ToggleGroupInput,
   ToggleGroupInput,
@@ -364,7 +365,6 @@ const SwatchAction = observer(() => {
       popoverSide="top"
       popoverSide="top"
       color={color}
       color={color}
       opacity={shapes[0].props.opacity}
       opacity={shapes[0].props.opacity}
-      collisionRef={document.getElementById('main-content-container')}
       setOpacity={handleSetOpacity}
       setOpacity={handleSetOpacity}
       setColor={handleSetColor}
       setColor={handleSetColor}
     />
     />
@@ -504,80 +504,20 @@ const LinksAction = observer(() => {
   const app = useApp<Shape>()
   const app = useApp<Shape>()
   const shape = app.selectedShapesArray[0]
   const shape = app.selectedShapesArray[0]
 
 
-  const [value, setValue] = React.useState('')
-
-  const [show, setShow] = React.useState(false)
-
-  const { handlers } = React.useContext(LogseqContext)
-
-  const handleChange = () => {
-    const refs = shape.props.refs ?? []
-    if (refs.includes(value)) return
-    shape.update({ refs: [...refs, value] })
+  const handleChange = (refs: string[]) => {
+    shape.update({ refs: refs })
     app.persist()
     app.persist()
   }
   }
 
 
-  const hasLinks = shape.props.refs && shape.props.refs.length > 0
-
   return (
   return (
-    <span className="flex gap-3 relative">
-      <ToggleInput
-        className="px-2 tl-button"
-        pressed={show}
-        onPressedChange={s => setShow(s)}
-        title="Open References & Links"
-      >
-        <TablerIcon name="link" />
-        {hasLinks && <div className="tl-shape-links-count">{shape.props.refs?.length}</div>}
-      </ToggleInput>
-
-      {show && (
-        <div className="tl-shape-links-panel">
-          <TextInput
-            title="Website Url"
-            className="tl-iframe-src"
-            value={value}
-            onChange={e => {
-              setValue(e.target.value)
-            }}
-            onKeyDown={e => {
-              if (e.key === 'Enter') {
-                handleChange()
-              }
-              e.stopPropagation()
-            }}
-          />
-          <div className="text-xs font-bold inline-flex gap-1 items-center">
-            <TablerIcon name="link" />
-            Your Links
-          </div>
-          {shape.props.refs?.map((ref, i) => {
-            return (
-              <div className="tl-shape-links-panel-item">
-                <div>{ref}</div>
-                <div className="flex-1" />
-                <Button
-                  title="Open Page in Right Sidebar"
-                  type="button"
-                  onClick={() => handlers?.sidebarAddBlock(ref, validUUID(ref) ? 'block' : 'page')}
-                >
-                  <TablerIcon name="layout-sidebar-right" />
-                </Button>
-                <button
-                  className="hover:opacity-60"
-                  onClick={() => {
-                    shape.update({ refs: shape.props.refs?.filter((_, j) => j !== i) })
-                    app.persist()
-                  }}
-                >
-                  <TablerIcon name="x" />
-                </button>
-              </div>
-            )
-          })}
-        </div>
-      )}
-    </span>
+    <ShapeLinksInput
+      onRefsChange={handleChange}
+      refs={shape.props.refs ?? []}
+      shapeType={shape.props.type}
+      side="right"
+      pageId={shape.props.type === 'logseq-portal' ? shape.props.pageId : undefined}
+      portalType={shape.props.type === 'logseq-portal' ? shape.props.blockType : undefined}
+    />
   )
   )
 })
 })
 
 

+ 78 - 0
tldraw/apps/tldraw-logseq/src/components/PopoverButton/PopoverButton.tsx

@@ -0,0 +1,78 @@
+import * as Popover from '@radix-ui/react-popover'
+import type { Side } from '@radix-ui/react-popper'
+import { BoundsUtils } from '@tldraw/core'
+import { useApp } from '@tldraw/react'
+import { observer } from 'mobx-react-lite'
+import * as React from 'react'
+
+interface PopoverButton extends React.HTMLAttributes<HTMLButtonElement> {
+  side: Side // default side
+  label: React.ReactNode
+  children: React.ReactNode
+  border?: boolean
+  arrow?: boolean
+}
+
+const sideAndOpposite = {
+  top: 'bottom',
+  bottom: 'top',
+  left: 'right',
+  right: 'left',
+} as const
+
+export const PopoverButton = observer(
+  ({ side, label, arrow, children, border, ...rest }: PopoverButton) => {
+    const contentRef = React.useRef<HTMLDivElement>(null)
+
+    const [isOpen, setIsOpen] = React.useState(false)
+
+    const {
+      viewport: {
+        bounds,
+        camera: { point, zoom },
+      },
+    } = useApp()
+
+    const [tick, setTick] = React.useState<number>(0)
+
+    // Change side if popover is out of bounds
+    React.useEffect(() => {
+      if (!contentRef.current || !isOpen) return
+
+      const boundingRect = contentRef.current.getBoundingClientRect()
+      const outOfView = !BoundsUtils.boundsContain(bounds, {
+        minX: boundingRect.x,
+        minY: boundingRect.y,
+        maxX: boundingRect.right,
+        maxY: boundingRect.bottom,
+        width: boundingRect.width,
+        height: boundingRect.height,
+      })
+
+      if (outOfView) {
+        setTick(tick => tick + 1)
+      }
+    }, [point[0], point[1], zoom, isOpen])
+
+    return (
+      <Popover.Root onOpenChange={o => setIsOpen(o)}>
+        <Popover.Trigger {...rest} data-border={border} className="tl-popover-trigger-button">
+          {label}
+        </Popover.Trigger>
+
+        <Popover.Content
+          // it seems like the Popover.Content component doesn't update collission when camera changes
+          key={'popover-content-' + tick}
+          ref={contentRef}
+          className="tl-popover-content"
+          side={side}
+          sideOffset={15}
+          collisionBoundary={document.querySelector('.logseq-tldraw')}
+        >
+          {children}
+          {arrow && <Popover.Arrow className="tl-popover-arrow" />}
+        </Popover.Content>
+      </Popover.Root>
+    )
+  }
+)

+ 1 - 0
tldraw/apps/tldraw-logseq/src/components/PopoverButton/index.ts

@@ -0,0 +1 @@
+export * from './PopoverButton'

+ 0 - 1
tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx

@@ -33,7 +33,6 @@ export const PrimaryTools = observer(function PrimaryTools() {
           title="Color Picker"
           title="Color Picker"
           popoverSide="left"
           popoverSide="left"
           color={app.settings.color}
           color={app.settings.color}
-          collisionRef={document.getElementById('main-content-container')}
           setColor={handleSetColor}
           setColor={handleSetColor}
         />
         />
       </div>
       </div>

+ 9 - 23
tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx

@@ -1,14 +1,12 @@
-import * as React from 'react'
-import * as Popover from '@radix-ui/react-popover'
 import type { Side } from '@radix-ui/react-popper'
 import type { Side } from '@radix-ui/react-popper'
 import * as Slider from '@radix-ui/react-slider'
 import * as Slider from '@radix-ui/react-slider'
-import { TablerIcon } from '../icons'
 import { Color } from '@tldraw/core'
 import { Color } from '@tldraw/core'
+import { TablerIcon } from '../icons'
+import { PopoverButton } from '../PopoverButton'
 
 
-interface ColorInputProps extends React.InputHTMLAttributes<HTMLButtonElement> {
+interface ColorInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   color?: string
   color?: string
   opacity?: number
   opacity?: number
-  collisionRef: HTMLElement | null
   popoverSide: Side
   popoverSide: Side
   setColor: (value: string) => void
   setColor: (value: string) => void
   setOpacity?: (value: number) => void
   setOpacity?: (value: number) => void
@@ -17,15 +15,12 @@ interface ColorInputProps extends React.InputHTMLAttributes<HTMLButtonElement> {
 export function ColorInput({
 export function ColorInput({
   color,
   color,
   opacity,
   opacity,
-  collisionRef,
   popoverSide,
   popoverSide,
   setColor,
   setColor,
   setOpacity,
   setOpacity,
   ...rest
   ...rest
 }: ColorInputProps) {
 }: ColorInputProps) {
-  const ref = React.useRef<HTMLDivElement>(null)
-
-  function renderColor(color: string) {
+  function renderColor(color?: string) {
     return color ? (
     return color ? (
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
         <div className={`w-full h-full bg-${color}-500`}></div>
         <div className={`w-full h-full bg-${color}-500`}></div>
@@ -38,15 +33,8 @@ export function ColorInput({
   }
   }
 
 
   return (
   return (
-    <Popover.Root>
-      <Popover.Trigger className="tl-color-drip">{renderColor(color)}</Popover.Trigger>
-
-      <Popover.Content
-        className="tl-popover-content p-1"
-        side={popoverSide}
-        sideOffset={15}
-        collisionBoundary={collisionRef}
-      >
+    <PopoverButton {...rest} border arrow side={popoverSide} label={renderColor(color)}>
+      <div className="p-1">
         <div className={'tl-color-palette'}>
         <div className={'tl-color-palette'}>
           {Object.values(Color).map(value => (
           {Object.values(Color).map(value => (
             <button
             <button
@@ -62,7 +50,7 @@ export function ColorInput({
         {setOpacity && (
         {setOpacity && (
           <div className="mx-1 my-2">
           <div className="mx-1 my-2">
             <Slider.Root
             <Slider.Root
-              defaultValue={[opacity]}
+              defaultValue={[opacity ?? 0]}
               onValueCommit={value => setOpacity(value[0])}
               onValueCommit={value => setOpacity(value[0])}
               max={1}
               max={1}
               step={0.1}
               step={0.1}
@@ -76,9 +64,7 @@ export function ColorInput({
             </Slider.Root>
             </Slider.Root>
           </div>
           </div>
         )}
         )}
-
-        <Popover.Arrow className="tl-popover-arrow" />
-      </Popover.Content>
-    </Popover.Root>
+      </div>
+    </PopoverButton>
   )
   )
 }
 }

+ 133 - 0
tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx

@@ -0,0 +1,133 @@
+import type { Side } from '@radix-ui/react-popper'
+import { validUUID } from '@tldraw/core'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
+import { Button } from '../Button'
+import { TablerIcon } from '../icons'
+import { PopoverButton } from '../PopoverButton'
+import { TextInput } from './TextInput'
+
+interface ShapeLinksInputProps extends React.HTMLAttributes<HTMLButtonElement> {
+  shapeType: string
+  side: Side
+  refs: string[]
+  pageId?: string // the portal referenced block id or page name
+  portalType?: 'B' | 'P'
+  onRefsChange: (value: string[]) => void
+}
+
+function ShapeLinkItem({
+  id,
+  type,
+  onRemove,
+}: {
+  id: string
+  type: 'B' | 'P'
+  onRemove?: () => void
+}) {
+  const {
+    handlers,
+    renderers: { Breadcrumb, PageNameLink },
+  } = React.useContext(LogseqContext)
+
+  return (
+    <div className="tl-shape-links-panel-item color-level">
+      <TablerIcon name={type === 'P' ? 'page' : 'block'} />
+      {type === 'P' ? <PageNameLink pageName={id} /> : <Breadcrumb levelLimit={2} blockId={id} />}
+      <div className="flex-1" />
+      <Button title="Open Page" type="button" onClick={() => handlers?.redirectToPage(id)}>
+        <TablerIcon name="external-link" />
+      </Button>
+      <Button
+        title="Open Page in Right Sidebar"
+        type="button"
+        onClick={() => handlers?.sidebarAddBlock(id, type === 'B' ? 'block' : 'page')}
+      >
+        <TablerIcon name="layout-sidebar-right" />
+      </Button>
+      {onRemove && (
+        <Button title="Remove link" type="button" onClick={onRemove}>
+          <TablerIcon name="x" />
+        </Button>
+      )}
+    </div>
+  )
+}
+
+export function ShapeLinksInput({
+  pageId,
+  portalType,
+  shapeType,
+  refs,
+  side,
+  onRefsChange,
+  ...rest
+}: ShapeLinksInputProps) {
+  const noOfLinks = refs.length + (pageId ? 1 : 0)
+  const [value, setValue] = React.useState('')
+
+  return (
+    <PopoverButton
+      {...rest}
+      side={side}
+      label={
+        <div className="flex gap-1 relative items-center justify-center px-1">
+          <TablerIcon name="link" />
+          {noOfLinks > 0 && <div className="tl-shape-links-count">{noOfLinks}</div>}
+        </div>
+      }
+    >
+      <div className="color-level">
+        {pageId && portalType && (
+          <div className="tl-shape-links-reference-panel">
+            <div className="text-base font-bold inline-flex gap-1 items-center">
+              <TablerIcon name="external-link" />
+              Your Reference
+            </div>
+            <div className="h-2" />
+            <ShapeLinkItem type={portalType} id={pageId} />
+          </div>
+        )}
+        <div className="tl-shape-links-panel color-level">
+          <div className="text-base font-bold inline-flex gap-1 items-center">
+            <TablerIcon name="link" />
+            Your Links
+          </div>
+          <div className="h-2" />
+          <div className="whitespace-pre-wrap">
+            This <strong>{shapeType}</strong> can be linked to any other block, page or whiteboard
+            element you have stored in Logseq.
+          </div>
+          <TextInput
+            value={value}
+            onChange={e => {
+              setValue(e.target.value)
+            }}
+            onKeyDown={e => {
+              if (e.key === 'Enter') {
+                if (value && !refs.includes(value)) {
+                  onRefsChange([...refs, value])
+                }
+              }
+              e.stopPropagation()
+            }}
+          />
+          <div className="flex flex-col items-stretch gap-2">
+            {refs.map((ref, i) => {
+              return (
+                <ShapeLinkItem
+                  key={ref}
+                  id={ref}
+                  type={validUUID(ref) ? 'B' : 'P'}
+                  onRemove={() => {
+                    onRefsChange(refs.filter((_, j) => i !== j))
+                  }}
+                />
+              )
+            })}
+          </div>
+        </div>
+      </div>
+    </PopoverButton>
+  )
+}

+ 1 - 0
tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts

@@ -16,6 +16,7 @@ export interface LogseqContextValue {
     }>
     }>
     Breadcrumb: React.FC<{
     Breadcrumb: React.FC<{
       blockId: string
       blockId: string
+      levelLimit?: number
     }>
     }>
     PageNameLink: React.FC<{
     PageNameLink: React.FC<{
       pageName: string
       pageName: string

+ 48 - 24
tldraw/apps/tldraw-logseq/src/styles.css

@@ -125,7 +125,7 @@ html[data-theme='light'] {
   margin-left: auto;
   margin-left: auto;
   padding-left: 20px;
   padding-left: 20px;
 
 
-  .keyboard-shortcut>code {
+  .keyboard-shortcut > code {
     padding: 4px !important;
     padding: 4px !important;
     text-rendering: initial;
     text-rendering: initial;
     font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
     font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
@@ -349,7 +349,7 @@ button.tl-select-input-trigger {
   bottom: -3px;
   bottom: -3px;
 }
 }
 
 
-.floating-panel[data-tool-locked='true']>.tl-button[data-selected='true']::after {
+.floating-panel[data-tool-locked='true'] > .tl-button[data-selected='true']::after {
   @apply block absolute;
   @apply block absolute;
 
 
   content: '';
   content: '';
@@ -602,7 +602,7 @@ button.tl-select-input-trigger {
     background-color: rgba(0, 0, 0, 0.5) !important;
     background-color: rgba(0, 0, 0, 0.5) !important;
   }
   }
 
 
-  >i.ti {
+  > i.ti {
     transform: translateY(-0.5px);
     transform: translateY(-0.5px);
   }
   }
 }
 }
@@ -721,7 +721,7 @@ button.tl-select-input-trigger {
 
 
   .page-ref {
   .page-ref {
     color: var(--ls-title-text-color);
     color: var(--ls-title-text-color);
-    background: var(--ls-tertiary-background-color));
+    background: var(--ls-tertiary-background-color);
   }
   }
 
 
   .breadcrumb {
   .breadcrumb {
@@ -750,7 +750,7 @@ button.tl-select-input-trigger {
 .tl-image-shape-container {
 .tl-image-shape-container {
   @apply h-full w-full overflow-hidden flex items-center justify-center pointer-events-auto;
   @apply h-full w-full overflow-hidden flex items-center justify-center pointer-events-auto;
 
 
-  &[data-asset-loaded="false"] {
+  &[data-asset-loaded='false'] {
     background-color: var(--ls-secondary-background-color);
     background-color: var(--ls-secondary-background-color);
   }
   }
 }
 }
@@ -770,7 +770,7 @@ button.tl-select-input-trigger {
   width: fit-content;
   width: fit-content;
 }
 }
 
 
-.tl-html-anchor>iframe {
+.tl-html-anchor > iframe {
   @apply h-full w-full !important;
   @apply h-full w-full !important;
   margin: 0;
   margin: 0;
 }
 }
@@ -778,7 +778,7 @@ button.tl-select-input-trigger {
 .tl-video-container {
 .tl-video-container {
   @apply h-full w-full m-0 relative;
   @apply h-full w-full m-0 relative;
 
 
-  >video {
+  > video {
     @apply h-full w-full m-0 relative;
     @apply h-full w-full m-0 relative;
   }
   }
 }
 }
@@ -905,7 +905,7 @@ html[data-theme='dark'] {
 }
 }
 
 
 .tl-popover-content {
 .tl-popover-content {
-  @apply rounded-sm drop-shadow-md;
+  @apply rounded-lg drop-shadow-md;
 
 
   background-color: var(--ls-secondary-background-color);
   background-color: var(--ls-secondary-background-color);
   z-index: 100000;
   z-index: 100000;
@@ -951,8 +951,7 @@ html[data-theme='dark'] {
 
 
   user-select: none;
   user-select: none;
   touch-action: none;
   touch-action: none;
-  background: url("../img/checker.png");
-
+  background: url('../img/checker.png');
 }
 }
 
 
 .tl-slider-track {
 .tl-slider-track {
@@ -961,11 +960,11 @@ html[data-theme='dark'] {
   background: linear-gradient(90deg, transparent, var(--ls-tertiary-background-color));
   background: linear-gradient(90deg, transparent, var(--ls-tertiary-background-color));
   border: 1px solid var(--ls-secondary-border-color);
   border: 1px solid var(--ls-secondary-border-color);
 
 
-  &[data-orientation="horizontal"] {
+  &[data-orientation='horizontal'] {
     height: 10px;
     height: 10px;
   }
   }
 
 
-  &[data-orientation="vertical"] {
+  &[data-orientation='vertical'] {
     width: 10px;
     width: 10px;
   }
   }
 }
 }
@@ -999,8 +998,8 @@ html[data-theme='dark'] {
   border-top-right-radius: 6px;
   border-top-right-radius: 6px;
   border-bottom-right-radius: 6px;
   border-bottom-right-radius: 6px;
 
 
-
-  &:hover, &.open {
+  &:hover,
+  &.open {
     @apply p-1.5;
     @apply p-1.5;
   }
   }
 }
 }
@@ -1011,28 +1010,53 @@ html[data-theme='dark'] {
   height: 32px;
   height: 32px;
   transform: translate(4px, -6px);
   transform: translate(4px, -6px);
 
 
-  &:hover, &.open {
+  &:hover,
+  &.open {
     height: 36px;
     height: 36px;
     width: 36px;
     width: 36px;
   }
   }
 }
 }
 
 
 .tl-shape-links-count {
 .tl-shape-links-count {
-  @apply px-1;
+  @apply px-1 rounded-sm;
   background-color: var(--ls-page-properties-background-color);
   background-color: var(--ls-page-properties-background-color);
 }
 }
 
 
-.tl-shape-links-panel {
-  @apply absolute shadow-lg rounded-lg p-3;
+.tl-shape-links-panel,
+.tl-shape-links-reference-panel {
+  @apply p-3;
   width: 320px;
   width: 320px;
-  transform: translate(20px, -8px);
-  left: 100%;
-  top: 0;
-  background-color: var(--ls-secondary-background-color);
+  color: var(--ls-primary-text-color);
+}
+
+.tl-shape-links-reference-panel {
+  @apply rounded-t-lg;
 }
 }
 
 
 .tl-shape-links-panel-item {
 .tl-shape-links-panel-item {
-  @apply rounded py-1 px-4 flex items-center justify-center gap-1;
+  @apply rounded py-1 px-4 pr-2 flex items-center justify-center gap-1;
   color: var(--color-text);
   color: var(--color-text);
-  background-color: var(--ls-tertiary-background-color);
+
+  .page-ref {
+    color: var(--ls-title-text-color);
+  }
+}
+
+.tl-popover-trigger-button {
+  @apply rounded text-sm;
+
+  min-width: 32px;
+  height: 32px;
+  padding: 3px;
+  color: var(--ls-secondary-text-color);
+
+  &[data-border='true'] {
+    border: 1px solid var(--ls-secondary-border-color);
+  }
+
+  &[data-state='open'] {
+    background-color: var(--ls-tertiary-background-color);
+    color: var(--ls-primary-text-color);
+    opacity: 1;
+  }
 }
 }

+ 1 - 1
tldraw/demo/src/App.jsx

@@ -209,7 +209,7 @@ export default function App() {
   }, [])
   }, [])
 
 
   return (
   return (
-    <div className={`h-screen w-screen`}>
+    <div className={`h-screen w-screen`} id="main-content-container">
       <ThemeSwitcher />
       <ThemeSwitcher />
       <PreviewButton model={model} />
       <PreviewButton model={model} />
       <TldrawApp
       <TldrawApp

+ 78 - 0
tldraw/demo/src/index.css

@@ -1,3 +1,81 @@
 @tailwind base;
 @tailwind base;
 @tailwind components;
 @tailwind components;
 @tailwind utilities;
 @tailwind utilities;
+
+.color-level {
+  background-color: var(--color-level-1);
+}
+
+.color-level .color-level {
+  background-color: var(--color-level-2);
+}
+
+.color-level .color-level .color-level {
+  background-color: var(--color-level-3);
+}
+
+.color-level .color-level .color-level .color-level {
+  background-color: var(--color-level-4);
+}
+
+.color-level .color-level .color-level .color-level .color-level {
+  background-color: var(--color-level-5);
+}
+
+.color-level .color-level .color-level .color-level .color-level .color-level {
+  background-color: var(--color-level-3);
+}
+
+.color-level .color-level .color-level .color-level .color-level .color-level .color-level {
+  background-color: var(--color-level-4);
+}
+
+.color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level {
+  background-color: var(--color-level-5);
+}
+
+.color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level {
+  background-color: var(--color-level-3);
+}
+
+.color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level {
+  background-color: var(--color-level-4);
+}
+
+.color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level
+  .color-level {
+  background-color: var(--color-level-5);
+}

+ 1 - 1
tldraw/package.json

@@ -21,7 +21,7 @@
     "postinstall": "yarn build",
     "postinstall": "yarn build",
     "dev": "cd demo && yarn dev",
     "dev": "cd demo && yarn dev",
     "fix:style": "yarn run pretty-quick",
     "fix:style": "yarn run pretty-quick",
-    "pretty-quick": "pretty-quick --pattern 'tldraw/**/*.{js,jsx,ts,tsx,html}'"
+    "pretty-quick": "pretty-quick --pattern 'tldraw/**/*.{js,jsx,ts,tsx,css,html}'"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@types/node": "^17.0.42",
     "@types/node": "^17.0.42",