Adds dark mode
This commit is contained in:
parent
8807aaf1b9
commit
1965c97f68
27 changed files with 428 additions and 265 deletions
|
@ -52,6 +52,6 @@ const StyledCorner = styled('rect', {
|
||||||
|
|
||||||
const StyledCornerInner = styled('rect', {
|
const StyledCornerInner = styled('rect', {
|
||||||
stroke: '$bounds',
|
stroke: '$bounds',
|
||||||
fill: '#fff',
|
fill: '$panel',
|
||||||
zStrokeWidth: 1.5,
|
zStrokeWidth: 1.5,
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ function HoveredShape({ id }: { id: string }) {
|
||||||
|
|
||||||
const strokeWidth = useSelector((s) => {
|
const strokeWidth = useSelector((s) => {
|
||||||
const shape = tld.getShape(s.data, id)
|
const shape = tld.getShape(s.data, id)
|
||||||
const style = getShapeStyle(shape.style)
|
const style = getShapeStyle(shape.style, s.data.settings.isDarkMode)
|
||||||
return +style.strokeWidth
|
return +style.strokeWidth
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ interface Node {
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
isHovered: boolean
|
isHovered: boolean
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
|
isDarkMode: boolean
|
||||||
isCurrentParent: boolean
|
isCurrentParent: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,15 @@ interface ShapeNodeProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShapeNode = ({
|
const ShapeNode = ({
|
||||||
node: { shape, children, isEditing, isHovered, isSelected, isCurrentParent },
|
node: {
|
||||||
|
shape,
|
||||||
|
children,
|
||||||
|
isEditing,
|
||||||
|
isHovered,
|
||||||
|
isDarkMode,
|
||||||
|
isSelected,
|
||||||
|
isCurrentParent,
|
||||||
|
},
|
||||||
}: ShapeNodeProps) => {
|
}: ShapeNodeProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -74,6 +83,7 @@ const ShapeNode = ({
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
isHovered={isHovered}
|
isHovered={isHovered}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
isCurrentParent={isCurrentParent}
|
isCurrentParent={isCurrentParent}
|
||||||
/>
|
/>
|
||||||
{children.map((childNode) => (
|
{children.map((childNode) => (
|
||||||
|
@ -105,6 +115,7 @@ function addToTree(
|
||||||
isHovered: data.hoveredId === shape.id,
|
isHovered: data.hoveredId === shape.id,
|
||||||
isCurrentParent: data.currentParentId === shape.id,
|
isCurrentParent: data.currentParentId === shape.id,
|
||||||
isEditing: data.editingId === shape.id,
|
isEditing: data.editingId === shape.id,
|
||||||
|
isDarkMode: data.settings.isDarkMode,
|
||||||
isSelected: selectedIds.includes(shape.id),
|
isSelected: selectedIds.includes(shape.id),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ interface ShapeProps {
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
isHovered: boolean
|
isHovered: boolean
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
|
isDarkMode: boolean
|
||||||
isCurrentParent: boolean
|
isCurrentParent: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ const Shape = memo(
|
||||||
isEditing,
|
isEditing,
|
||||||
isHovered,
|
isHovered,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isDarkMode,
|
||||||
isCurrentParent,
|
isCurrentParent,
|
||||||
}: ShapeProps) => {
|
}: ShapeProps) => {
|
||||||
const rGroup = useRef<SVGGElement>(null)
|
const rGroup = useRef<SVGGElement>(null)
|
||||||
|
@ -39,13 +41,14 @@ const Shape = memo(
|
||||||
{...events}
|
{...events}
|
||||||
>
|
>
|
||||||
{isEditing && shape.type === ShapeType.Text ? (
|
{isEditing && shape.type === ShapeType.Text ? (
|
||||||
<EditingTextShape shape={shape} />
|
<EditingTextShape shape={shape} isDarkMode={isDarkMode} />
|
||||||
) : (
|
) : (
|
||||||
<RenderedShape
|
<RenderedShape
|
||||||
shape={shape}
|
shape={shape}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
isHovered={isHovered}
|
isHovered={isHovered}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
isCurrentParent={isCurrentParent}
|
isCurrentParent={isCurrentParent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -62,6 +65,7 @@ interface RenderedShapeProps {
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
isHovered: boolean
|
isHovered: boolean
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
|
isDarkMode: boolean
|
||||||
isCurrentParent: boolean
|
isCurrentParent: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,12 +75,14 @@ const RenderedShape = memo(
|
||||||
isEditing,
|
isEditing,
|
||||||
isHovered,
|
isHovered,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isDarkMode,
|
||||||
isCurrentParent,
|
isCurrentParent,
|
||||||
}: RenderedShapeProps) {
|
}: RenderedShapeProps) {
|
||||||
return getShapeUtils(shape).render(shape, {
|
return getShapeUtils(shape).render(shape, {
|
||||||
isEditing,
|
isEditing,
|
||||||
isHovered,
|
isHovered,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isDarkMode,
|
||||||
isCurrentParent,
|
isCurrentParent,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -85,6 +91,7 @@ const RenderedShape = memo(
|
||||||
prev.isEditing !== next.isEditing ||
|
prev.isEditing !== next.isEditing ||
|
||||||
prev.isHovered !== next.isHovered ||
|
prev.isHovered !== next.isHovered ||
|
||||||
prev.isSelected !== next.isSelected ||
|
prev.isSelected !== next.isSelected ||
|
||||||
|
prev.isDarkMode !== next.isDarkMode ||
|
||||||
prev.isCurrentParent !== next.isCurrentParent
|
prev.isCurrentParent !== next.isCurrentParent
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
|
@ -98,7 +105,13 @@ const RenderedShape = memo(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function EditingTextShape({ shape }: { shape: TextShape }) {
|
function EditingTextShape({
|
||||||
|
shape,
|
||||||
|
isDarkMode,
|
||||||
|
}: {
|
||||||
|
shape: TextShape
|
||||||
|
isDarkMode: boolean
|
||||||
|
}) {
|
||||||
const ref = useRef<HTMLTextAreaElement>(null)
|
const ref = useRef<HTMLTextAreaElement>(null)
|
||||||
|
|
||||||
return getShapeUtils(shape).render(shape, {
|
return getShapeUtils(shape).render(shape, {
|
||||||
|
@ -106,6 +119,7 @@ function EditingTextShape({ shape }: { shape: TextShape }) {
|
||||||
isEditing: true,
|
isEditing: true,
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
|
isDarkMode,
|
||||||
isCurrentParent: false,
|
isCurrentParent: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
25
components/icons/check.tsx
Normal file
25
components/icons/check.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
function SvgCheck(props: React.SVGProps<SVGSVGElement>): JSX.Element {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1.06555 6.39147C1.06555 6.39147 1.18669 6.37676 1.32187 6.60547C1.41196 6.75789 1.43336 6.79297 1.54896 6.97656C1.67649 7.1791 1.90215 7.46571 1.98273 7.55604C1.89684 7.45413 2.06332 7.64638 1.98273 7.55604C2.06863 7.65796 2.15476 7.76164 2.24114 7.86709C2.32752 7.97255 2.43047 8.09537 2.54999 8.23556C2.66951 8.37575 2.82609 8.54986 3.01971 8.75788C3.21333 8.96589 3.41752 9.17647 3.63228 9.38961C3.43268 9.19463 3.84705 9.60274 3.63228 9.38961C3.83189 9.58458 3.98492 9.65483 3.97262 9.74102C4.18567 9.85603 3.96031 9.82721 3.97262 9.74102C3.91266 9.99944 3.75956 9.626 3.97262 9.74102C4.03257 9.4826 4.10858 9.33351 4.21165 9.13551C4.31473 8.93751 4.42218 8.73459 4.534 8.52675C4.64583 8.31892 4.76102 8.10882 4.87957 7.89646C4.99812 7.68411 5.13449 7.4489 5.28871 7.19083C5.44292 6.93276 5.63515 6.62674 5.86541 6.27278C6.09567 5.91882 6.34391 5.55345 6.61015 5.17668C6.87639 4.7999 7.15756 4.4262 7.45365 4.05557C7.74975 3.68495 8.06403 3.32626 8.39649 2.97952C8.72895 2.63277 9.04339 2.32894 9.33982 2.06804C9.63624 1.80713 9.87576 1.60935 10.0584 1.4747C10.241 1.34004 10.4399 1.20541 10.655 1.07079C10.8702 0.936165 10.9439 0.993562 10.8761 1.24297C10.8084 1.49239 10.7342 1.72411 10.6535 1.93813C10.5728 2.15215 10.452 2.43667 10.291 2.79168C10.13 3.14669 9.94264 3.53252 9.7288 3.94917C9.51497 4.36582 9.29528 4.77352 9.06973 5.17228C8.84418 5.57104 8.62072 5.96246 8.39936 6.34654C8.178 6.73061 7.9685 7.0987 7.77088 7.45081C7.57326 7.80292 7.40426 8.10581 7.26389 8.35948C7.12353 8.61314 6.99519 8.84594 6.87889 9.05785C6.76259 9.26977 6.64515 9.48087 6.52659 9.69115C6.40803 9.90143 6.25752 10.1532 6.07507 10.4466C5.89262 10.7399 5.72254 11.0063 5.56483 11.2458C5.40712 11.4852 5.23545 11.6777 5.04981 11.8232C4.86416 11.9686 4.59686 12.0243 4.2479 11.9903C3.89894 11.9563 3.61525 11.8614 3.39684 11.7055C3.17844 11.5497 2.99758 11.3507 2.85427 11.1085C2.71096 10.8663 2.56178 10.5997 2.40673 10.3088C2.25167 10.0179 2.11187 9.72784 1.98731 9.4386C1.86275 9.14937 1.76835 8.91301 1.70411 8.72952C1.63988 8.54604 1.58816 8.38956 1.54896 8.26008C1.50977 8.13061 1.47123 8.0037 1.43336 7.87934C1.39549 7.75498 1.35833 7.63676 1.32187 7.52466C1.28541 7.41257 1.24607 7.2993 1.20384 7.18486C1.16161 7.07041 1.21291 7.20988 1.06555 6.85296C0.918183 6.49603 1.06555 6.39147 1.06555 6.39147Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M1.98273 7.55604C1.90215 7.46571 1.67649 7.1791 1.54896 6.97656C1.43336 6.79297 1.41196 6.75789 1.32187 6.60547C1.18669 6.37676 1.06555 6.39147 1.06555 6.39147C1.06555 6.39147 0.918183 6.49603 1.06555 6.85296C1.21291 7.20988 1.16161 7.07041 1.20384 7.18486C1.24607 7.2993 1.28541 7.41257 1.32187 7.52466C1.35833 7.63676 1.39549 7.75498 1.43336 7.87934C1.47123 8.0037 1.50977 8.13061 1.54896 8.26008C1.58816 8.38956 1.63988 8.54604 1.70411 8.72952C1.76835 8.91301 1.86275 9.14937 1.98731 9.4386C2.11187 9.72784 2.25167 10.0179 2.40673 10.3088C2.56178 10.5997 2.71096 10.8663 2.85427 11.1085C2.99758 11.3507 3.17844 11.5497 3.39684 11.7055C3.61525 11.8614 3.89894 11.9563 4.2479 11.9903C4.59686 12.0243 4.86416 11.9686 5.04981 11.8232C5.23545 11.6777 5.40712 11.4852 5.56483 11.2458C5.72254 11.0063 5.89262 10.7399 6.07507 10.4466C6.25752 10.1532 6.40803 9.90143 6.52659 9.69115C6.64515 9.48087 6.76259 9.26977 6.87889 9.05785C6.99519 8.84594 7.12353 8.61314 7.26389 8.35948C7.40426 8.10581 7.57326 7.80292 7.77088 7.45081C7.9685 7.0987 8.178 6.73061 8.39936 6.34654C8.62072 5.96246 8.84418 5.57104 9.06973 5.17228C9.29528 4.77352 9.51497 4.36582 9.7288 3.94917C9.94264 3.53252 10.13 3.14669 10.291 2.79168C10.452 2.43667 10.5728 2.15215 10.6535 1.93813C10.7342 1.72411 10.8084 1.49239 10.8761 1.24297C10.9439 0.993562 10.8702 0.936165 10.655 1.07079C10.4399 1.20541 10.241 1.34004 10.0584 1.4747C9.87576 1.60935 9.63624 1.80713 9.33982 2.06804C9.04339 2.32894 8.72895 2.63277 8.39649 2.97952C8.06403 3.32626 7.74975 3.68495 7.45365 4.05557C7.15756 4.4262 6.87639 4.7999 6.61015 5.17668C6.34391 5.55345 6.09567 5.91882 5.86541 6.27278C5.63515 6.62674 5.44292 6.93276 5.28871 7.19083C5.13449 7.4489 4.99812 7.68411 4.87957 7.89646C4.76102 8.10882 4.64583 8.31892 4.534 8.52675C4.42218 8.73459 4.31473 8.93751 4.21165 9.13551C4.10858 9.33351 4.03257 9.4826 3.97262 9.74102M1.98273 7.55604C2.06332 7.64638 1.89684 7.45413 1.98273 7.55604ZM1.98273 7.55604C2.06863 7.65796 2.15476 7.76164 2.24114 7.86709C2.32752 7.97255 2.43047 8.09537 2.54999 8.23556C2.66951 8.37575 2.82609 8.54986 3.01971 8.75788C3.21333 8.96589 3.41752 9.17647 3.63228 9.38961M3.63228 9.38961C3.84705 9.60274 3.43268 9.19463 3.63228 9.38961ZM3.63228 9.38961C3.83189 9.58458 3.98492 9.65483 3.97262 9.74102M3.97262 9.74102C3.96031 9.82721 4.18567 9.85603 3.97262 9.74102ZM3.97262 9.74102C3.75956 9.626 3.91266 9.99944 3.97262 9.74102Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SvgCheck
|
|
@ -1,3 +1,4 @@
|
||||||
export { default as Redo } from './redo'
|
export { default as Redo } from './redo'
|
||||||
export { default as Trash } from './trash'
|
export { default as Trash } from './trash'
|
||||||
export { default as Undo } from './undo'
|
export { default as Undo } from './undo'
|
||||||
|
export { default as Check } from './check'
|
||||||
|
|
|
@ -11,8 +11,9 @@ import {
|
||||||
DropdownMenuButton,
|
DropdownMenuButton,
|
||||||
DropdownMenuSubMenu,
|
DropdownMenuSubMenu,
|
||||||
DropdownMenuDivider,
|
DropdownMenuDivider,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
} from '../shared'
|
} from '../shared'
|
||||||
import state from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { commandKey } from 'utils'
|
import { commandKey } from 'utils'
|
||||||
|
|
||||||
const handleNew = () => state.send('CREATED_NEW_PROJECT')
|
const handleNew = () => state.send('CREATED_NEW_PROJECT')
|
||||||
|
@ -28,7 +29,7 @@ function Menu() {
|
||||||
<HamburgerMenuIcon />
|
<HamburgerMenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Content as={MenuContent} sideOffset={8}>
|
<Content as={MenuContent} sideOffset={8}>
|
||||||
<DropdownMenuButton onSelect={handleNew}>
|
<DropdownMenuButton onSelect={handleNew} disabled>
|
||||||
<span>New Project</span>
|
<span>New Project</span>
|
||||||
<kbd>
|
<kbd>
|
||||||
<span>{commandKey()}</span>
|
<span>{commandKey()}</span>
|
||||||
|
@ -72,7 +73,7 @@ export default memo(Menu)
|
||||||
|
|
||||||
function RecentFiles() {
|
function RecentFiles() {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuSubMenu label="Open Recent...">
|
<DropdownMenuSubMenu label="Open Recent..." disabled>
|
||||||
<DropdownMenuButton>
|
<DropdownMenuButton>
|
||||||
<span>Project A</span>
|
<span>Project A</span>
|
||||||
</DropdownMenuButton>
|
</DropdownMenuButton>
|
||||||
|
@ -87,16 +88,16 @@ function RecentFiles() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Preferences() {
|
function Preferences() {
|
||||||
|
const isDarkMode = useSelector((s) => s.data.settings.isDarkMode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuSubMenu label="Preferences">
|
<DropdownMenuSubMenu label="Preferences">
|
||||||
<DropdownMenuButton onSelect={toggleDarkMode}>
|
<DropdownMenuCheckboxItem
|
||||||
<span>Toggle Dark Mode</span>
|
checked={isDarkMode}
|
||||||
<kbd>
|
onCheckedChange={toggleDarkMode}
|
||||||
<span>⇧</span>
|
>
|
||||||
<span>{commandKey()}</span>
|
<span>Dark Mode</span>
|
||||||
<span>D</span>
|
</DropdownMenuCheckboxItem>
|
||||||
</kbd>
|
|
||||||
</DropdownMenuButton>
|
|
||||||
</DropdownMenuSubMenu>
|
</DropdownMenuSubMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import styled from 'styles'
|
|
||||||
import * as ContextMenu from '@radix-ui/react-context-menu'
|
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import styled from 'styles'
|
||||||
import { breakpoints, IconWrapper, RowButton } from 'components/shared'
|
import {
|
||||||
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
|
breakpoints,
|
||||||
import * as Panel from '../panel'
|
DropdownMenuButton,
|
||||||
|
DropdownMenuDivider,
|
||||||
|
RowButton,
|
||||||
|
MenuContent,
|
||||||
|
FloatingContainer,
|
||||||
|
IconButton,
|
||||||
|
IconWrapper,
|
||||||
|
} from 'components/shared'
|
||||||
|
import { MixerVerticalIcon, PlusIcon, CheckIcon } from '@radix-ui/react-icons'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
@ -37,152 +43,62 @@ export default function PagePanel(): JSX.Element {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PanelRoot dir="ltr">
|
<FloatingContainer>
|
||||||
<DropdownMenu.Trigger
|
<RowButton as={DropdownMenu.Trigger} bp={breakpoints}>
|
||||||
as={RowButton}
|
|
||||||
bp={breakpoints}
|
|
||||||
variant="pageButton"
|
|
||||||
>
|
|
||||||
<span>{documentPages[currentPageId].name}</span>
|
<span>{documentPages[currentPageId].name}</span>
|
||||||
</DropdownMenu.Trigger>
|
</RowButton>
|
||||||
<DropdownMenu.Content sideOffset={8}>
|
</FloatingContainer>
|
||||||
<PanelRoot>
|
<MenuContent as={DropdownMenu.Content} sideOffset={8}>
|
||||||
<DropdownMenu.RadioGroup
|
<DropdownMenu.RadioGroup
|
||||||
as={Content}
|
value={currentPageId}
|
||||||
value={currentPageId}
|
onChange={(id) => {
|
||||||
onValueChange={(id) => {
|
setIsOpen(false)
|
||||||
setIsOpen(false)
|
state.send('CHANGED_PAGE', { id })
|
||||||
state.send('CHANGED_PAGE', { id })
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{sorted.map(({ id, name }) => (
|
||||||
{sorted.map(({ id, name }) => (
|
<ButtonWithOptions key={id}>
|
||||||
<ContextMenu.Root dir="ltr" key={id}>
|
<DropdownMenu.RadioItem
|
||||||
<ContextMenu.Trigger>
|
as={RowButton}
|
||||||
<StyledRadioItem key={id} value={id} bp={breakpoints}>
|
bp={breakpoints}
|
||||||
<span>{name}</span>
|
value={id}
|
||||||
<DropdownMenu.ItemIndicator as={IconWrapper} size="small">
|
variant="pageButton"
|
||||||
<CheckIcon />
|
>
|
||||||
</DropdownMenu.ItemIndicator>
|
<span>{name}</span>
|
||||||
</StyledRadioItem>
|
<DropdownMenu.ItemIndicator>
|
||||||
</ContextMenu.Trigger>
|
<IconWrapper>
|
||||||
<StyledContextMenuContent>
|
<CheckIcon />
|
||||||
<ContextMenu.Group>
|
</IconWrapper>
|
||||||
<StyledContextMenuItem
|
</DropdownMenu.ItemIndicator>
|
||||||
onSelect={() => state.send('RENAMED_PAGE', { id })}
|
</DropdownMenu.RadioItem>
|
||||||
>
|
<IconButton bp={breakpoints} size="small" data-shy="true">
|
||||||
Rename
|
<MixerVerticalIcon />
|
||||||
</StyledContextMenuItem>
|
</IconButton>
|
||||||
<StyledContextMenuItem
|
</ButtonWithOptions>
|
||||||
onSelect={() => {
|
))}
|
||||||
setIsOpen(false)
|
</DropdownMenu.RadioGroup>
|
||||||
state.send('DELETED_PAGE', { id })
|
<DropdownMenuDivider />
|
||||||
}}
|
<DropdownMenuButton onSelect={() => state.send('CREATED_PAGE')}>
|
||||||
>
|
<span>Create Page</span>
|
||||||
Delete
|
<IconWrapper size="small">
|
||||||
</StyledContextMenuItem>
|
<PlusIcon />
|
||||||
</ContextMenu.Group>
|
</IconWrapper>
|
||||||
</StyledContextMenuContent>
|
</DropdownMenuButton>
|
||||||
</ContextMenu.Root>
|
</MenuContent>
|
||||||
))}
|
|
||||||
</DropdownMenu.RadioGroup>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<RowButton
|
|
||||||
bp={breakpoints}
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpen(false)
|
|
||||||
state.send('CREATED_PAGE')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>Create Page</span>
|
|
||||||
<IconWrapper size="small">
|
|
||||||
<PlusIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
</RowButton>
|
|
||||||
</PanelRoot>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</PanelRoot>
|
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelRoot = styled('div', {
|
const ButtonWithOptions = styled('div', {
|
||||||
zIndex: 200,
|
display: 'grid',
|
||||||
overflow: 'hidden',
|
gridTemplateColumns: '1fr auto',
|
||||||
position: 'relative',
|
gridAutoFlow: 'column',
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
pointerEvents: 'all',
|
|
||||||
padding: '$0',
|
|
||||||
borderRadius: '4px',
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
border: '1px solid $panel',
|
|
||||||
boxShadow: '$4',
|
|
||||||
userSelect: 'none',
|
|
||||||
})
|
|
||||||
|
|
||||||
const Content = styled(Panel.Content, {
|
'& > *[data-shy="true"]': {
|
||||||
width: '100%',
|
opacity: 0,
|
||||||
minWidth: 128,
|
},
|
||||||
})
|
|
||||||
|
|
||||||
const StyledRadioItem = styled(DropdownMenu.RadioItem, {
|
'&:hover > *[data-shy="true"]': {
|
||||||
height: 32,
|
opacity: 1,
|
||||||
width: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '0 6px 0 12px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '$1',
|
|
||||||
fontFamily: '$ui',
|
|
||||||
fontWeight: 400,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
outline: 'none',
|
|
||||||
variants: {
|
|
||||||
bp: {
|
|
||||||
mobile: {},
|
|
||||||
small: {
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
},
|
|
||||||
'&:focus-within': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledContextMenuContent = styled(ContextMenu.Content, {
|
|
||||||
padding: '$0',
|
|
||||||
borderRadius: '4px',
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
border: '1px solid $panel',
|
|
||||||
boxShadow: '$4',
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledContextMenuItem = styled(ContextMenu.Item, {
|
|
||||||
height: 32,
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '0 12px 0 12px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '$1',
|
|
||||||
fontFamily: '$ui',
|
|
||||||
fontWeight: 400,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
outline: 'none',
|
|
||||||
bp: {
|
|
||||||
mobile: {},
|
|
||||||
small: {
|
|
||||||
'&:hover:not(:disabled)': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -35,12 +35,13 @@ const StatusBarContainer = styled('div', {
|
||||||
zIndex: 300,
|
zIndex: 300,
|
||||||
height: 40,
|
height: 40,
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
borderTop: '1px solid black',
|
borderTop: '1px solid $border',
|
||||||
gridArea: 'status',
|
gridArea: 'status',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
|
color: '$text',
|
||||||
gridTemplateColumns: 'auto 1fr auto',
|
gridTemplateColumns: 'auto 1fr auto',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'white',
|
backgroundColor: '$panel',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
fontSize: '$0',
|
fontSize: '$0',
|
||||||
padding: '0 16px',
|
padding: '0 16px',
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Square } from 'react-feather'
|
||||||
import { DropdownContent } from '../shared'
|
import { DropdownContent } from '../shared'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
|
import useTheme from 'hooks/useTheme'
|
||||||
|
|
||||||
function handleColorChange(
|
function handleColorChange(
|
||||||
e: Event & { currentTarget: { value: ColorStyle } }
|
e: Event & { currentTarget: { value: ColorStyle } }
|
||||||
|
@ -14,9 +15,11 @@ function handleColorChange(
|
||||||
}
|
}
|
||||||
|
|
||||||
function ColorContent(): JSX.Element {
|
function ColorContent(): JSX.Element {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownContent sideOffset={8} side="bottom">
|
<DropdownContent sideOffset={8} side="bottom">
|
||||||
{Object.keys(strokes).map((color: ColorStyle) => (
|
{Object.keys(strokes[theme]).map((color: ColorStyle) => (
|
||||||
<DropdownMenu.DropdownMenuItem
|
<DropdownMenu.DropdownMenuItem
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
key={color}
|
key={color}
|
||||||
|
@ -24,7 +27,7 @@ function ColorContent(): JSX.Element {
|
||||||
value={color}
|
value={color}
|
||||||
onSelect={handleColorChange}
|
onSelect={handleColorChange}
|
||||||
>
|
>
|
||||||
<Square fill={strokes[color]} stroke="none" size="22" />
|
<Square fill={strokes[theme][color]} stroke="none" size="22" />
|
||||||
</DropdownMenu.DropdownMenuItem>
|
</DropdownMenu.DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownContent>
|
</DropdownContent>
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { breakpoints, IconButton } from 'components/shared'
|
import { breakpoints, IconButton } from 'components/shared'
|
||||||
import Tooltip from 'components/tooltip'
|
import Tooltip from 'components/tooltip'
|
||||||
import { strokes } from 'state/shape-styles'
|
import { fills, strokes } from 'state/shape-styles'
|
||||||
import { useSelector } from 'state'
|
import { useSelector } from 'state'
|
||||||
import ColorContent from './color-content'
|
import ColorContent from './color-content'
|
||||||
import { BoxIcon } from '../shared'
|
import { BoxIcon } from '../shared'
|
||||||
|
import useTheme from 'hooks/useTheme'
|
||||||
|
|
||||||
export default function QuickColorSelect(): JSX.Element {
|
export default function QuickColorSelect(): JSX.Element {
|
||||||
const color = useSelector((s) => s.values.selectedStyle.color)
|
const color = useSelector((s) => s.values.selectedStyle.color)
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root dir="ltr">
|
<DropdownMenu.Root dir="ltr">
|
||||||
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
|
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
|
||||||
<Tooltip label="Color">
|
<Tooltip label="Color">
|
||||||
<BoxIcon fill={strokes[color]} stroke={strokes[color]} />
|
<BoxIcon fill={fills[theme][color]} stroke={strokes[theme][color]} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<ColorContent />
|
<ColorContent />
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
|
import { Theme } from 'types'
|
||||||
|
|
||||||
export default function useTheme() {
|
export default function useTheme() {
|
||||||
const theme = useSelector((state) =>
|
const theme: Theme = useSelector((state) =>
|
||||||
state.data.settings.isDarkMode ? 'dark' : 'light'
|
state.data.settings.isDarkMode ? 'dark' : 'light'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,50 @@
|
||||||
import { ColorStyle, DashStyle, ShapeStyles, SizeStyle } from 'types'
|
import { Theme, ColorStyle, DashStyle, ShapeStyles, SizeStyle } from 'types'
|
||||||
|
import { lerpColor } from 'utils'
|
||||||
|
|
||||||
export const strokes: Record<ColorStyle, string> = {
|
const canvasLight = '#fafafa'
|
||||||
[ColorStyle.White]: 'rgba(248, 249, 250, 1.000)',
|
|
||||||
[ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
|
const canvasDark = '#343d45'
|
||||||
[ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
|
|
||||||
[ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
|
const colors = {
|
||||||
[ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
|
[ColorStyle.White]: '#f0f1f3',
|
||||||
[ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
|
[ColorStyle.LightGray]: '#c6cbd1',
|
||||||
[ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
|
[ColorStyle.Gray]: '#788492',
|
||||||
[ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
|
[ColorStyle.Black]: '#212528',
|
||||||
[ColorStyle.Violet]: 'rgba(112, 72, 232, 1.000)',
|
[ColorStyle.Green]: '#36b24d',
|
||||||
[ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
|
[ColorStyle.Cyan]: '#0e98ad',
|
||||||
[ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
|
[ColorStyle.Blue]: '#1c7ed6',
|
||||||
[ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
|
[ColorStyle.Indigo]: '#4263eb',
|
||||||
|
[ColorStyle.Violet]: '#7746f1',
|
||||||
|
[ColorStyle.Red]: '#ff2133',
|
||||||
|
[ColorStyle.Orange]: '#ff9433',
|
||||||
|
[ColorStyle.Yellow]: '#ffc936',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fills = {
|
export const strokes: Record<Theme, Record<ColorStyle, string>> = {
|
||||||
[ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
|
light: colors,
|
||||||
[ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
|
dark: {
|
||||||
[ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
|
...(Object.fromEntries(
|
||||||
[ColorStyle.Black]: 'rgba(255, 255, 255, 1.000)',
|
Object.entries(colors).map(([k, v]) => [k, lerpColor(v, canvasDark, 0.1)])
|
||||||
[ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
|
) as Record<ColorStyle, string>),
|
||||||
[ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
|
[ColorStyle.White]: '#ffffff',
|
||||||
[ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
|
[ColorStyle.Black]: '#000',
|
||||||
[ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
|
},
|
||||||
[ColorStyle.Violet]: 'rgba(242, 240, 255, 1.000)',
|
}
|
||||||
[ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
|
|
||||||
[ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
|
export const fills: Record<Theme, Record<ColorStyle, string>> = {
|
||||||
[ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
|
light: {
|
||||||
|
...(Object.fromEntries(
|
||||||
|
Object.entries(colors).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
lerpColor(v, canvasLight, 0.82),
|
||||||
|
])
|
||||||
|
) as Record<ColorStyle, string>),
|
||||||
|
[ColorStyle.White]: '#ffffff',
|
||||||
|
[ColorStyle.Black]: '#ffffff',
|
||||||
|
},
|
||||||
|
dark: Object.fromEntries(
|
||||||
|
Object.entries(colors).map(([k, v]) => [k, lerpColor(v, canvasDark, 0.618)])
|
||||||
|
) as Record<ColorStyle, string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const strokeWidths = {
|
const strokeWidths = {
|
||||||
|
@ -57,7 +74,10 @@ export function getFontStyle(scale: number, style: ShapeStyles): string {
|
||||||
return `${fontSize * scale}px/1.4 Verveine Regular`
|
return `${fontSize * scale}px/1.4 Verveine Regular`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShapeStyle(style: ShapeStyles): {
|
export function getShapeStyle(
|
||||||
|
style: ShapeStyles,
|
||||||
|
isDarkMode = false
|
||||||
|
): {
|
||||||
stroke: string
|
stroke: string
|
||||||
fill: string
|
fill: string
|
||||||
strokeWidth: number
|
strokeWidth: number
|
||||||
|
@ -66,9 +86,11 @@ export function getShapeStyle(style: ShapeStyles): {
|
||||||
|
|
||||||
const strokeWidth = getStrokeWidth(size)
|
const strokeWidth = getStrokeWidth(size)
|
||||||
|
|
||||||
|
const theme: Theme = isDarkMode ? 'dark' : 'light'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stroke: strokes[color],
|
stroke: strokes[theme][color],
|
||||||
fill: isFilled ? fills[color] : 'none',
|
fill: isFilled ? fills[theme][color] : 'none',
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
return shape.handles !== prev.handles || shape.style !== prev.style
|
return shape.handles !== prev.handles || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape) {
|
render(shape, { isDarkMode }) {
|
||||||
const { bend, handles, style } = shape
|
const { bend, handles, style } = shape
|
||||||
const { start, end, bend: _bend } = handles
|
const { start, end, bend: _bend } = handles
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
|
|
||||||
const isDraw = shape.style.dash === DashStyle.Draw
|
const isDraw = shape.style.dash === DashStyle.Draw
|
||||||
|
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
|
|
||||||
const { strokeWidth } = styles
|
const { strokeWidth } = styles
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ const dot = registerShapeUtils<DotShape>({
|
||||||
style: defaultStyle,
|
style: defaultStyle,
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape) {
|
render(shape, { isDarkMode }) {
|
||||||
const styles = getShapeStyle(shape.style)
|
const styles = getShapeStyle(shape.style, isDarkMode)
|
||||||
|
|
||||||
return <use href="#dot" stroke={styles.stroke} fill={styles.stroke} />
|
return <use href="#dot" stroke={styles.stroke} fill={styles.stroke} />
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,10 +40,10 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
return shape.points !== prev.points || shape.style !== prev.style
|
return shape.points !== prev.points || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape, { isHovered }) {
|
render(shape, { isHovered, isDarkMode }) {
|
||||||
const { points, style } = shape
|
const { points, style } = shape
|
||||||
|
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
|
|
||||||
const strokeWidth = +styles.strokeWidth
|
const strokeWidth = +styles.strokeWidth
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,9 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape) {
|
render(shape, { isDarkMode }) {
|
||||||
const { radiusX, radiusY, style } = shape
|
const { radiusX, radiusY, style } = shape
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
const strokeWidth = +styles.strokeWidth
|
const strokeWidth = +styles.strokeWidth
|
||||||
|
|
||||||
const rx = Math.max(0, radiusX - strokeWidth / 2)
|
const rx = Math.max(0, radiusX - strokeWidth / 2)
|
||||||
|
|
|
@ -26,12 +26,12 @@ const line = registerShapeUtils<LineShape>({
|
||||||
return shape.direction !== prev.direction || shape.style !== prev.style
|
return shape.direction !== prev.direction || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape, { isHovered }) {
|
render(shape, { isHovered, isDarkMode }) {
|
||||||
const { id, direction } = shape
|
const { id, direction } = shape
|
||||||
const [x1, y1] = vec.add([0, 0], vec.mul(direction, 10000))
|
const [x1, y1] = vec.add([0, 0], vec.mul(direction, 10000))
|
||||||
const [x2, y2] = vec.sub([0, 0], vec.mul(direction, 10000))
|
const [x2, y2] = vec.sub([0, 0], vec.mul(direction, 10000))
|
||||||
|
|
||||||
const styles = getShapeStyle(shape.style)
|
const styles = getShapeStyle(shape.style, isDarkMode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g id={id} filter={isHovered ? 'url(#expand)' : 'none'}>
|
<g id={id} filter={isHovered ? 'url(#expand)' : 'none'}>
|
||||||
|
|
|
@ -28,10 +28,10 @@ const polyline = registerShapeUtils<PolylineShape>({
|
||||||
shouldRender(shape, prev) {
|
shouldRender(shape, prev) {
|
||||||
return shape.points !== prev.points || shape.style !== prev.style
|
return shape.points !== prev.points || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
render(shape) {
|
render(shape, { isDarkMode }) {
|
||||||
const { points, style } = shape
|
const { points, style } = shape
|
||||||
|
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<polyline
|
<polyline
|
||||||
|
|
|
@ -25,10 +25,10 @@ const ray = registerShapeUtils<RayShape>({
|
||||||
shouldRender(shape, prev) {
|
shouldRender(shape, prev) {
|
||||||
return shape.direction !== prev.direction || shape.style !== prev.style
|
return shape.direction !== prev.direction || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
render(shape) {
|
render(shape, { isDarkMode }) {
|
||||||
const { direction } = shape
|
const { direction } = shape
|
||||||
|
|
||||||
const styles = getShapeStyle(shape.style)
|
const styles = getShapeStyle(shape.style, isDarkMode)
|
||||||
|
|
||||||
const [x2, y2] = vec.add([0, 0], vec.mul(direction, 10000))
|
const [x2, y2] = vec.add([0, 0], vec.mul(direction, 10000))
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,9 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
return shape.size !== prev.size || shape.style !== prev.style
|
return shape.size !== prev.size || shape.style !== prev.style
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape, { isHovered }) {
|
render(shape, { isHovered, isDarkMode }) {
|
||||||
const { id, size, radius, style } = shape
|
const { id, size, radius, style } = shape
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
const strokeWidth = +styles.strokeWidth
|
const strokeWidth = +styles.strokeWidth
|
||||||
|
|
||||||
if (style.dash === DashStyle.Draw) {
|
if (style.dash === DashStyle.Draw) {
|
||||||
|
|
|
@ -70,9 +70,9 @@ const text = registerShapeUtils<TextShape>({
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape, { isEditing, ref }) {
|
render(shape, { isEditing, isDarkMode, ref }) {
|
||||||
const { id, text, style } = shape
|
const { id, text, style } = shape
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style, isDarkMode)
|
||||||
const font = getFontStyle(shape.scale, shape.style)
|
const font = getFontStyle(shape.scale, shape.style)
|
||||||
|
|
||||||
const bounds = this.getBounds(shape)
|
const bounds = this.getBounds(shape)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import storage from './storage'
|
||||||
import session from './session'
|
import session from './session'
|
||||||
import clipboard from './clipboard'
|
import clipboard from './clipboard'
|
||||||
import commands from './commands'
|
import commands from './commands'
|
||||||
|
import { dark, light } from 'styles'
|
||||||
import {
|
import {
|
||||||
vec,
|
vec,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
|
@ -43,7 +44,7 @@ const initialData: Data = {
|
||||||
settings: {
|
settings: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
isTestMode: false,
|
isTestMode: false,
|
||||||
isDarkMode: false,
|
isDarkMode: true,
|
||||||
isCodeOpen: false,
|
isCodeOpen: false,
|
||||||
isDebugMode: false,
|
isDebugMode: false,
|
||||||
isDebugOpen: false,
|
isDebugOpen: false,
|
||||||
|
@ -144,6 +145,7 @@ for (let i = 0; i < count; i++) {
|
||||||
|
|
||||||
const state = createState({
|
const state = createState({
|
||||||
data: initialData,
|
data: initialData,
|
||||||
|
onEnter: 'applyTheme',
|
||||||
on: {
|
on: {
|
||||||
TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
|
TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
|
||||||
TOGGLED_DEBUG_MODE: 'toggleDebugMode',
|
TOGGLED_DEBUG_MODE: 'toggleDebugMode',
|
||||||
|
@ -168,12 +170,15 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ready: {
|
ready: {
|
||||||
onEnter: {
|
onEnter: [
|
||||||
wait: 0.01,
|
'applyTheme',
|
||||||
if: 'hasSelection',
|
{
|
||||||
do: 'zoomCameraToSelectionActual',
|
wait: 0.01,
|
||||||
else: ['zoomCameraToActual'],
|
if: 'hasSelection',
|
||||||
},
|
do: 'zoomCameraToSelectionActual',
|
||||||
|
else: ['zoomCameraToActual'],
|
||||||
|
},
|
||||||
|
],
|
||||||
on: {
|
on: {
|
||||||
UNMOUNTED: {
|
UNMOUNTED: {
|
||||||
do: ['saveDocumentState', 'resetDocumentState'],
|
do: ['saveDocumentState', 'resetDocumentState'],
|
||||||
|
@ -204,7 +209,7 @@ const state = createState({
|
||||||
do: 'pasteShapesFromClipboard',
|
do: 'pasteShapesFromClipboard',
|
||||||
},
|
},
|
||||||
TOGGLED_DARK_MODE: {
|
TOGGLED_DARK_MODE: {
|
||||||
do: 'toggleDarkMode',
|
do: ['toggleDarkMode', 'applyTheme'],
|
||||||
},
|
},
|
||||||
TOGGLED_SHAPE_LOCK: {
|
TOGGLED_SHAPE_LOCK: {
|
||||||
unlessAny: ['isReadOnly', 'isInSession'],
|
unlessAny: ['isReadOnly', 'isInSession'],
|
||||||
|
@ -1971,6 +1976,15 @@ const state = createState({
|
||||||
toggleDarkMode(data) {
|
toggleDarkMode(data) {
|
||||||
data.settings.isDarkMode = !data.settings.isDarkMode
|
data.settings.isDarkMode = !data.settings.isDarkMode
|
||||||
},
|
},
|
||||||
|
applyTheme(data) {
|
||||||
|
if (data.settings.isDarkMode && typeof document !== 'undefined') {
|
||||||
|
document.body.classList.remove(light)
|
||||||
|
document.body.classList.add(dark)
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove(dark)
|
||||||
|
document.body.classList.add(light)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/* --------------------- Styles --------------------- */
|
/* --------------------- Styles --------------------- */
|
||||||
|
|
||||||
|
|
|
@ -96,26 +96,34 @@ const light = theme({})
|
||||||
|
|
||||||
const dark = theme({
|
const dark = theme({
|
||||||
colors: {
|
colors: {
|
||||||
codeHl: 'rgba(144, 144, 144, .15)',
|
|
||||||
brushFill: 'rgba(0,0,0,.05)',
|
brushFill: 'rgba(0,0,0,.05)',
|
||||||
brushStroke: 'rgba(0,0,0,.25)',
|
brushStroke: 'rgba(0,0,0,.25)',
|
||||||
hint: 'rgba(216, 226, 249, 1.000)',
|
hint: 'rgba(216, 226, 249, 1.000)',
|
||||||
selected: 'rgba(66, 133, 244, 1.000)',
|
selected: 'rgba(82, 143, 245, 1.000)',
|
||||||
bounds: 'rgba(65, 132, 244, 1.000)',
|
bounds: 'rgba(82, 143, 245, 1.000)',
|
||||||
boundsBg: 'rgba(65, 132, 244, 0.05)',
|
boundsBg: 'rgba(82, 143, 245, 0.05)',
|
||||||
highlight: 'rgba(65, 132, 244, 0.15)',
|
highlight: 'rgba(82, 143, 245, 0.15)',
|
||||||
overlay: 'rgba(0, 0, 0, 0.15)',
|
overlay: 'rgba(0, 0, 0, 0.15)',
|
||||||
border: '#aaa',
|
border: '#202529',
|
||||||
canvas: '#fafafa',
|
canvas: '#343d45',
|
||||||
panel: '#fefefe',
|
panel: '#49555f',
|
||||||
inactive: '#cccccf',
|
inactive: '#cccccf',
|
||||||
hover: '#efefef',
|
hover: '#343d45',
|
||||||
text: '#333',
|
text: '#f8f9fa',
|
||||||
muted: '#777',
|
muted: '#e0e2e6',
|
||||||
input: '#f3f3f3',
|
input: '#f3f3f3',
|
||||||
inputBorder: '#ddd',
|
inputBorder: '#ddd',
|
||||||
|
codeHl: 'rgba(144, 144, 144, .15)',
|
||||||
lineError: 'rgba(255, 0, 0, .1)',
|
lineError: 'rgba(255, 0, 0, .1)',
|
||||||
},
|
},
|
||||||
|
shadows: {
|
||||||
|
2: '0px 1px 1px rgba(0, 0, 0, 0.24)',
|
||||||
|
3: '0px 2px 3px rgba(0, 0, 0, 0.24)',
|
||||||
|
4: '0px 4px 5px -1px rgba(0, 0, 0, 0.24)',
|
||||||
|
8: '0px 12px 17px rgba(0, 0, 0, 0.24)',
|
||||||
|
12: '0px 12px 17px rgba(0, 0, 0, 0.24)',
|
||||||
|
24: '0px 24px 38px rgba(0, 0, 0, 0.24)',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const globalStyles = global({
|
const globalStyles = global({
|
||||||
|
@ -130,6 +138,7 @@ const globalStyles = global({
|
||||||
overscrollBehavior: 'none',
|
overscrollBehavior: 'none',
|
||||||
fontFamily: '$ui',
|
fontFamily: '$ui',
|
||||||
fontSize: '$2',
|
fontSize: '$2',
|
||||||
|
color: '$text',
|
||||||
backgroundColor: '$canvas',
|
backgroundColor: '$canvas',
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
|
|
3
types.ts
3
types.ts
|
@ -124,6 +124,8 @@ export enum FontSize {
|
||||||
ExtraLarge = 'ExtraLarge',
|
ExtraLarge = 'ExtraLarge',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Theme = 'dark' | 'light'
|
||||||
|
|
||||||
export type ShapeStyles = {
|
export type ShapeStyles = {
|
||||||
color: ColorStyle
|
color: ColorStyle
|
||||||
size: SizeStyle
|
size: SizeStyle
|
||||||
|
@ -612,6 +614,7 @@ export interface ShapeUtility<K extends Shape> {
|
||||||
isHovered?: boolean
|
isHovered?: boolean
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
isCurrentParent?: boolean
|
isCurrentParent?: boolean
|
||||||
|
isDarkMode?: boolean
|
||||||
ref?: React.MutableRefObject<HTMLTextAreaElement>
|
ref?: React.MutableRefObject<HTMLTextAreaElement>
|
||||||
}
|
}
|
||||||
): JSX.Element
|
): JSX.Element
|
||||||
|
|
|
@ -20,6 +20,47 @@ export function lerp(y1: number, y2: number, mu: number): number {
|
||||||
return y1 * (1 - mu) + y2 * mu
|
return y1 * (1 - mu) + y2 * mu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear interpolation between two colors.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
*```ts
|
||||||
|
* lerpColor("#000000", "#0099FF", .25)
|
||||||
|
*```
|
||||||
|
*/
|
||||||
|
function h2r(hex: string) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
|
return result
|
||||||
|
? [
|
||||||
|
parseInt(result[1], 16),
|
||||||
|
parseInt(result[2], 16),
|
||||||
|
parseInt(result[3], 16),
|
||||||
|
]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function r2h(rgb: number[]) {
|
||||||
|
return (
|
||||||
|
'#' +
|
||||||
|
((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lerpColor(
|
||||||
|
color1: string,
|
||||||
|
color2: string,
|
||||||
|
factor = 0.5
|
||||||
|
): string {
|
||||||
|
const c1 = h2r(color1)
|
||||||
|
const c2 = h2r(color2)
|
||||||
|
const result = c1.slice()
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
result[i] = Math.round(result[i] + factor * (c2[i] - c1[i]))
|
||||||
|
}
|
||||||
|
return r2h(result)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modulate a value between two ranges.
|
* Modulate a value between two ranges.
|
||||||
* @param value
|
* @param value
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue