Browse Source

Adds dark mode

main
Steve Ruiz 4 years ago
parent
commit
1965c97f68

+ 1
- 1
components/canvas/bounds/corner-handle.tsx View File

52
 
52
 
53
 const StyledCornerInner = styled('rect', {
53
 const StyledCornerInner = styled('rect', {
54
   stroke: '$bounds',
54
   stroke: '$bounds',
55
-  fill: '#fff',
55
+  fill: '$panel',
56
   zStrokeWidth: 1.5,
56
   zStrokeWidth: 1.5,
57
 })
57
 })

+ 1
- 1
components/canvas/hovered-shape.tsx View File

22
 
22
 
23
   const strokeWidth = useSelector((s) => {
23
   const strokeWidth = useSelector((s) => {
24
     const shape = tld.getShape(s.data, id)
24
     const shape = tld.getShape(s.data, id)
25
-    const style = getShapeStyle(shape.style)
25
+    const style = getShapeStyle(shape.style, s.data.settings.isDarkMode)
26
     return +style.strokeWidth
26
     return +style.strokeWidth
27
   })
27
   })
28
 
28
 

+ 12
- 1
components/canvas/page.tsx View File

16
   isEditing: boolean
16
   isEditing: boolean
17
   isHovered: boolean
17
   isHovered: boolean
18
   isSelected: boolean
18
   isSelected: boolean
19
+  isDarkMode: boolean
19
   isCurrentParent: boolean
20
   isCurrentParent: boolean
20
 }
21
 }
21
 
22
 
65
 }
66
 }
66
 
67
 
67
 const ShapeNode = ({
68
 const ShapeNode = ({
68
-  node: { shape, children, isEditing, isHovered, isSelected, isCurrentParent },
69
+  node: {
70
+    shape,
71
+    children,
72
+    isEditing,
73
+    isHovered,
74
+    isDarkMode,
75
+    isSelected,
76
+    isCurrentParent,
77
+  },
69
 }: ShapeNodeProps) => {
78
 }: ShapeNodeProps) => {
70
   return (
79
   return (
71
     <>
80
     <>
74
         isEditing={isEditing}
83
         isEditing={isEditing}
75
         isHovered={isHovered}
84
         isHovered={isHovered}
76
         isSelected={isSelected}
85
         isSelected={isSelected}
86
+        isDarkMode={isDarkMode}
77
         isCurrentParent={isCurrentParent}
87
         isCurrentParent={isCurrentParent}
78
       />
88
       />
79
       {children.map((childNode) => (
89
       {children.map((childNode) => (
105
     isHovered: data.hoveredId === shape.id,
115
     isHovered: data.hoveredId === shape.id,
106
     isCurrentParent: data.currentParentId === shape.id,
116
     isCurrentParent: data.currentParentId === shape.id,
107
     isEditing: data.editingId === shape.id,
117
     isEditing: data.editingId === shape.id,
118
+    isDarkMode: data.settings.isDarkMode,
108
     isSelected: selectedIds.includes(shape.id),
119
     isSelected: selectedIds.includes(shape.id),
109
   }
120
   }
110
 
121
 

+ 16
- 2
components/canvas/shape.tsx View File

10
   isEditing: boolean
10
   isEditing: boolean
11
   isHovered: boolean
11
   isHovered: boolean
12
   isSelected: boolean
12
   isSelected: boolean
13
+  isDarkMode: boolean
13
   isCurrentParent: boolean
14
   isCurrentParent: boolean
14
 }
15
 }
15
 
16
 
19
     isEditing,
20
     isEditing,
20
     isHovered,
21
     isHovered,
21
     isSelected,
22
     isSelected,
23
+    isDarkMode,
22
     isCurrentParent,
24
     isCurrentParent,
23
   }: ShapeProps) => {
25
   }: ShapeProps) => {
24
     const rGroup = useRef<SVGGElement>(null)
26
     const rGroup = useRef<SVGGElement>(null)
39
         {...events}
41
         {...events}
40
       >
42
       >
41
         {isEditing && shape.type === ShapeType.Text ? (
43
         {isEditing && shape.type === ShapeType.Text ? (
42
-          <EditingTextShape shape={shape} />
44
+          <EditingTextShape shape={shape} isDarkMode={isDarkMode} />
43
         ) : (
45
         ) : (
44
           <RenderedShape
46
           <RenderedShape
45
             shape={shape}
47
             shape={shape}
46
             isEditing={isEditing}
48
             isEditing={isEditing}
47
             isHovered={isHovered}
49
             isHovered={isHovered}
48
             isSelected={isSelected}
50
             isSelected={isSelected}
51
+            isDarkMode={isDarkMode}
49
             isCurrentParent={isCurrentParent}
52
             isCurrentParent={isCurrentParent}
50
           />
53
           />
51
         )}
54
         )}
62
   isEditing: boolean
65
   isEditing: boolean
63
   isHovered: boolean
66
   isHovered: boolean
64
   isSelected: boolean
67
   isSelected: boolean
68
+  isDarkMode: boolean
65
   isCurrentParent: boolean
69
   isCurrentParent: boolean
66
 }
70
 }
67
 
71
 
71
     isEditing,
75
     isEditing,
72
     isHovered,
76
     isHovered,
73
     isSelected,
77
     isSelected,
78
+    isDarkMode,
74
     isCurrentParent,
79
     isCurrentParent,
75
   }: RenderedShapeProps) {
80
   }: RenderedShapeProps) {
76
     return getShapeUtils(shape).render(shape, {
81
     return getShapeUtils(shape).render(shape, {
77
       isEditing,
82
       isEditing,
78
       isHovered,
83
       isHovered,
79
       isSelected,
84
       isSelected,
85
+      isDarkMode,
80
       isCurrentParent,
86
       isCurrentParent,
81
     })
87
     })
82
   },
88
   },
85
       prev.isEditing !== next.isEditing ||
91
       prev.isEditing !== next.isEditing ||
86
       prev.isHovered !== next.isHovered ||
92
       prev.isHovered !== next.isHovered ||
87
       prev.isSelected !== next.isSelected ||
93
       prev.isSelected !== next.isSelected ||
94
+      prev.isDarkMode !== next.isDarkMode ||
88
       prev.isCurrentParent !== next.isCurrentParent
95
       prev.isCurrentParent !== next.isCurrentParent
89
     ) {
96
     ) {
90
       return false
97
       return false
98
   }
105
   }
99
 )
106
 )
100
 
107
 
101
-function EditingTextShape({ shape }: { shape: TextShape }) {
108
+function EditingTextShape({
109
+  shape,
110
+  isDarkMode,
111
+}: {
112
+  shape: TextShape
113
+  isDarkMode: boolean
114
+}) {
102
   const ref = useRef<HTMLTextAreaElement>(null)
115
   const ref = useRef<HTMLTextAreaElement>(null)
103
 
116
 
104
   return getShapeUtils(shape).render(shape, {
117
   return getShapeUtils(shape).render(shape, {
106
     isEditing: true,
119
     isEditing: true,
107
     isHovered: false,
120
     isHovered: false,
108
     isSelected: false,
121
     isSelected: false,
122
+    isDarkMode,
109
     isCurrentParent: false,
123
     isCurrentParent: false,
110
   })
124
   })
111
 }
125
 }

+ 25
- 0
components/icons/check.tsx View File

1
+import * as React from 'react'
2
+
3
+function SvgCheck(props: React.SVGProps<SVGSVGElement>): JSX.Element {
4
+  return (
5
+    <svg
6
+      viewBox="0 0 15 15"
7
+      fill="currentColor"
8
+      xmlns="http://www.w3.org/2000/svg"
9
+      {...props}
10
+    >
11
+      <path
12
+        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"
13
+        fill="currentColor"
14
+      />
15
+      <path
16
+        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"
17
+        stroke="currentColor"
18
+        strokeLinecap="round"
19
+        strokeLinejoin="round"
20
+      />
21
+    </svg>
22
+  )
23
+}
24
+
25
+export default SvgCheck

+ 1
- 0
components/icons/index.tsx View File

1
 export { default as Redo } from './redo'
1
 export { default as Redo } from './redo'
2
 export { default as Trash } from './trash'
2
 export { default as Trash } from './trash'
3
 export { default as Undo } from './undo'
3
 export { default as Undo } from './undo'
4
+export { default as Check } from './check'

+ 12
- 11
components/menu/menu.tsx View File

11
   DropdownMenuButton,
11
   DropdownMenuButton,
12
   DropdownMenuSubMenu,
12
   DropdownMenuSubMenu,
13
   DropdownMenuDivider,
13
   DropdownMenuDivider,
14
+  DropdownMenuCheckboxItem,
14
 } from '../shared'
15
 } from '../shared'
15
-import state from 'state'
16
+import state, { useSelector } from 'state'
16
 import { commandKey } from 'utils'
17
 import { commandKey } from 'utils'
17
 
18
 
18
 const handleNew = () => state.send('CREATED_NEW_PROJECT')
19
 const handleNew = () => state.send('CREATED_NEW_PROJECT')
28
           <HamburgerMenuIcon />
29
           <HamburgerMenuIcon />
29
         </IconButton>
30
         </IconButton>
30
         <Content as={MenuContent} sideOffset={8}>
31
         <Content as={MenuContent} sideOffset={8}>
31
-          <DropdownMenuButton onSelect={handleNew}>
32
+          <DropdownMenuButton onSelect={handleNew} disabled>
32
             <span>New Project</span>
33
             <span>New Project</span>
33
             <kbd>
34
             <kbd>
34
               <span>{commandKey()}</span>
35
               <span>{commandKey()}</span>
72
 
73
 
73
 function RecentFiles() {
74
 function RecentFiles() {
74
   return (
75
   return (
75
-    <DropdownMenuSubMenu label="Open Recent...">
76
+    <DropdownMenuSubMenu label="Open Recent..." disabled>
76
       <DropdownMenuButton>
77
       <DropdownMenuButton>
77
         <span>Project A</span>
78
         <span>Project A</span>
78
       </DropdownMenuButton>
79
       </DropdownMenuButton>
87
 }
88
 }
88
 
89
 
89
 function Preferences() {
90
 function Preferences() {
91
+  const isDarkMode = useSelector((s) => s.data.settings.isDarkMode)
92
+
90
   return (
93
   return (
91
     <DropdownMenuSubMenu label="Preferences">
94
     <DropdownMenuSubMenu label="Preferences">
92
-      <DropdownMenuButton onSelect={toggleDarkMode}>
93
-        <span>Toggle Dark Mode</span>
94
-        <kbd>
95
-          <span>⇧</span>
96
-          <span>{commandKey()}</span>
97
-          <span>D</span>
98
-        </kbd>
99
-      </DropdownMenuButton>
95
+      <DropdownMenuCheckboxItem
96
+        checked={isDarkMode}
97
+        onCheckedChange={toggleDarkMode}
98
+      >
99
+        <span>Dark Mode</span>
100
+      </DropdownMenuCheckboxItem>
100
     </DropdownMenuSubMenu>
101
     </DropdownMenuSubMenu>
101
   )
102
   )
102
 }
103
 }

+ 61
- 145
components/page-panel/page-panel.tsx View File

1
-import styled from 'styles'
2
-import * as ContextMenu from '@radix-ui/react-context-menu'
3
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
4
-
5
-import { breakpoints, IconWrapper, RowButton } from 'components/shared'
6
-import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
7
-import * as Panel from '../panel'
2
+import styled from 'styles'
3
+import {
4
+  breakpoints,
5
+  DropdownMenuButton,
6
+  DropdownMenuDivider,
7
+  RowButton,
8
+  MenuContent,
9
+  FloatingContainer,
10
+  IconButton,
11
+  IconWrapper,
12
+} from 'components/shared'
13
+import { MixerVerticalIcon, PlusIcon, CheckIcon } from '@radix-ui/react-icons'
8
 import state, { useSelector } from 'state'
14
 import state, { useSelector } from 'state'
9
 import { useEffect, useRef, useState } from 'react'
15
 import { useEffect, useRef, useState } from 'react'
10
 
16
 
37
         }
43
         }
38
       }}
44
       }}
39
     >
45
     >
40
-      <PanelRoot dir="ltr">
41
-        <DropdownMenu.Trigger
42
-          as={RowButton}
43
-          bp={breakpoints}
44
-          variant="pageButton"
45
-        >
46
+      <FloatingContainer>
47
+        <RowButton as={DropdownMenu.Trigger} bp={breakpoints}>
46
           <span>{documentPages[currentPageId].name}</span>
48
           <span>{documentPages[currentPageId].name}</span>
47
-        </DropdownMenu.Trigger>
48
-        <DropdownMenu.Content sideOffset={8}>
49
-          <PanelRoot>
50
-            <DropdownMenu.RadioGroup
51
-              as={Content}
52
-              value={currentPageId}
53
-              onValueChange={(id) => {
54
-                setIsOpen(false)
55
-                state.send('CHANGED_PAGE', { id })
56
-              }}
57
-            >
58
-              {sorted.map(({ id, name }) => (
59
-                <ContextMenu.Root dir="ltr" key={id}>
60
-                  <ContextMenu.Trigger>
61
-                    <StyledRadioItem key={id} value={id} bp={breakpoints}>
62
-                      <span>{name}</span>
63
-                      <DropdownMenu.ItemIndicator as={IconWrapper} size="small">
64
-                        <CheckIcon />
65
-                      </DropdownMenu.ItemIndicator>
66
-                    </StyledRadioItem>
67
-                  </ContextMenu.Trigger>
68
-                  <StyledContextMenuContent>
69
-                    <ContextMenu.Group>
70
-                      <StyledContextMenuItem
71
-                        onSelect={() => state.send('RENAMED_PAGE', { id })}
72
-                      >
73
-                        Rename
74
-                      </StyledContextMenuItem>
75
-                      <StyledContextMenuItem
76
-                        onSelect={() => {
77
-                          setIsOpen(false)
78
-                          state.send('DELETED_PAGE', { id })
79
-                        }}
80
-                      >
81
-                        Delete
82
-                      </StyledContextMenuItem>
83
-                    </ContextMenu.Group>
84
-                  </StyledContextMenuContent>
85
-                </ContextMenu.Root>
86
-              ))}
87
-            </DropdownMenu.RadioGroup>
88
-            <DropdownMenu.Separator />
89
-            <RowButton
90
-              bp={breakpoints}
91
-              onClick={() => {
92
-                setIsOpen(false)
93
-                state.send('CREATED_PAGE')
94
-              }}
95
-            >
96
-              <span>Create Page</span>
97
-              <IconWrapper size="small">
98
-                <PlusIcon />
99
-              </IconWrapper>
100
-            </RowButton>
101
-          </PanelRoot>
102
-        </DropdownMenu.Content>
103
-      </PanelRoot>
49
+        </RowButton>
50
+      </FloatingContainer>
51
+      <MenuContent as={DropdownMenu.Content} sideOffset={8}>
52
+        <DropdownMenu.RadioGroup
53
+          value={currentPageId}
54
+          onChange={(id) => {
55
+            setIsOpen(false)
56
+            state.send('CHANGED_PAGE', { id })
57
+          }}
58
+        >
59
+          {sorted.map(({ id, name }) => (
60
+            <ButtonWithOptions key={id}>
61
+              <DropdownMenu.RadioItem
62
+                as={RowButton}
63
+                bp={breakpoints}
64
+                value={id}
65
+                variant="pageButton"
66
+              >
67
+                <span>{name}</span>
68
+                <DropdownMenu.ItemIndicator>
69
+                  <IconWrapper>
70
+                    <CheckIcon />
71
+                  </IconWrapper>
72
+                </DropdownMenu.ItemIndicator>
73
+              </DropdownMenu.RadioItem>
74
+              <IconButton bp={breakpoints} size="small" data-shy="true">
75
+                <MixerVerticalIcon />
76
+              </IconButton>
77
+            </ButtonWithOptions>
78
+          ))}
79
+        </DropdownMenu.RadioGroup>
80
+        <DropdownMenuDivider />
81
+        <DropdownMenuButton onSelect={() => state.send('CREATED_PAGE')}>
82
+          <span>Create Page</span>
83
+          <IconWrapper size="small">
84
+            <PlusIcon />
85
+          </IconWrapper>
86
+        </DropdownMenuButton>
87
+      </MenuContent>
104
     </DropdownMenu.Root>
88
     </DropdownMenu.Root>
105
   )
89
   )
106
 }
90
 }
107
 
91
 
108
-const PanelRoot = styled('div', {
109
-  zIndex: 200,
110
-  overflow: 'hidden',
111
-  position: 'relative',
112
-  display: 'flex',
113
-  flexDirection: 'column',
114
-  alignItems: 'center',
115
-  pointerEvents: 'all',
116
-  padding: '$0',
117
-  borderRadius: '4px',
118
-  backgroundColor: '$panel',
119
-  border: '1px solid $panel',
120
-  boxShadow: '$4',
121
-  userSelect: 'none',
122
-})
92
+const ButtonWithOptions = styled('div', {
93
+  display: 'grid',
94
+  gridTemplateColumns: '1fr auto',
95
+  gridAutoFlow: 'column',
123
 
96
 
124
-const Content = styled(Panel.Content, {
125
-  width: '100%',
126
-  minWidth: 128,
127
-})
128
-
129
-const StyledRadioItem = styled(DropdownMenu.RadioItem, {
130
-  height: 32,
131
-  width: 'auto',
132
-  display: 'flex',
133
-  alignItems: 'center',
134
-  justifyContent: 'space-between',
135
-  padding: '0 6px 0 12px',
136
-  cursor: 'pointer',
137
-  borderRadius: '4px',
138
-  fontSize: '$1',
139
-  fontFamily: '$ui',
140
-  fontWeight: 400,
141
-  backgroundColor: 'transparent',
142
-  outline: 'none',
143
-  variants: {
144
-    bp: {
145
-      mobile: {},
146
-      small: {
147
-        '&:hover': {
148
-          backgroundColor: '$hover',
149
-        },
150
-        '&:focus-within': {
151
-          backgroundColor: '$hover',
152
-        },
153
-      },
154
-    },
97
+  '& > *[data-shy="true"]': {
98
+    opacity: 0,
155
   },
99
   },
156
-})
157
-
158
-const StyledContextMenuContent = styled(ContextMenu.Content, {
159
-  padding: '$0',
160
-  borderRadius: '4px',
161
-  backgroundColor: '$panel',
162
-  border: '1px solid $panel',
163
-  boxShadow: '$4',
164
-})
165
 
100
 
166
-const StyledContextMenuItem = styled(ContextMenu.Item, {
167
-  height: 32,
168
-  width: '100%',
169
-  display: 'flex',
170
-  alignItems: 'center',
171
-  justifyContent: 'space-between',
172
-  padding: '0 12px 0 12px',
173
-  cursor: 'pointer',
174
-  borderRadius: '4px',
175
-  fontSize: '$1',
176
-  fontFamily: '$ui',
177
-  fontWeight: 400,
178
-  backgroundColor: 'transparent',
179
-  outline: 'none',
180
-  bp: {
181
-    mobile: {},
182
-    small: {
183
-      '&:hover:not(:disabled)': {
184
-        backgroundColor: '$hover',
185
-      },
186
-    },
101
+  '&:hover > *[data-shy="true"]': {
102
+    opacity: 1,
187
   },
103
   },
188
 })
104
 })

+ 127
- 28
components/shared.tsx
File diff suppressed because it is too large
View File


+ 3
- 2
components/status-bar.tsx View File

35
   zIndex: 300,
35
   zIndex: 300,
36
   height: 40,
36
   height: 40,
37
   userSelect: 'none',
37
   userSelect: 'none',
38
-  borderTop: '1px solid black',
38
+  borderTop: '1px solid $border',
39
   gridArea: 'status',
39
   gridArea: 'status',
40
   display: 'grid',
40
   display: 'grid',
41
+  color: '$text',
41
   gridTemplateColumns: 'auto 1fr auto',
42
   gridTemplateColumns: 'auto 1fr auto',
42
   alignItems: 'center',
43
   alignItems: 'center',
43
-  backgroundColor: 'white',
44
+  backgroundColor: '$panel',
44
   gap: 8,
45
   gap: 8,
45
   fontSize: '$0',
46
   fontSize: '$0',
46
   padding: '0 16px',
47
   padding: '0 16px',

+ 5
- 2
components/style-panel/color-content.tsx View File

6
 import { DropdownContent } from '../shared'
6
 import { DropdownContent } from '../shared'
7
 import { memo } from 'react'
7
 import { memo } from 'react'
8
 import state from 'state'
8
 import state from 'state'
9
+import useTheme from 'hooks/useTheme'
9
 
10
 
10
 function handleColorChange(
11
 function handleColorChange(
11
   e: Event & { currentTarget: { value: ColorStyle } }
12
   e: Event & { currentTarget: { value: ColorStyle } }
14
 }
15
 }
15
 
16
 
16
 function ColorContent(): JSX.Element {
17
 function ColorContent(): JSX.Element {
18
+  const { theme } = useTheme()
19
+
17
   return (
20
   return (
18
     <DropdownContent sideOffset={8} side="bottom">
21
     <DropdownContent sideOffset={8} side="bottom">
19
-      {Object.keys(strokes).map((color: ColorStyle) => (
22
+      {Object.keys(strokes[theme]).map((color: ColorStyle) => (
20
         <DropdownMenu.DropdownMenuItem
23
         <DropdownMenu.DropdownMenuItem
21
           as={IconButton}
24
           as={IconButton}
22
           key={color}
25
           key={color}
24
           value={color}
27
           value={color}
25
           onSelect={handleColorChange}
28
           onSelect={handleColorChange}
26
         >
29
         >
27
-          <Square fill={strokes[color]} stroke="none" size="22" />
30
+          <Square fill={strokes[theme][color]} stroke="none" size="22" />
28
         </DropdownMenu.DropdownMenuItem>
31
         </DropdownMenu.DropdownMenuItem>
29
       ))}
32
       ))}
30
     </DropdownContent>
33
     </DropdownContent>

+ 4
- 2
components/style-panel/quick-color-select.tsx View File

1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
 import { breakpoints, IconButton } from 'components/shared'
2
 import { breakpoints, IconButton } from 'components/shared'
3
 import Tooltip from 'components/tooltip'
3
 import Tooltip from 'components/tooltip'
4
-import { strokes } from 'state/shape-styles'
4
+import { fills, strokes } from 'state/shape-styles'
5
 import { useSelector } from 'state'
5
 import { useSelector } from 'state'
6
 import ColorContent from './color-content'
6
 import ColorContent from './color-content'
7
 import { BoxIcon } from '../shared'
7
 import { BoxIcon } from '../shared'
8
+import useTheme from 'hooks/useTheme'
8
 
9
 
9
 export default function QuickColorSelect(): JSX.Element {
10
 export default function QuickColorSelect(): JSX.Element {
10
   const color = useSelector((s) => s.values.selectedStyle.color)
11
   const color = useSelector((s) => s.values.selectedStyle.color)
12
+  const { theme } = useTheme()
11
 
13
 
12
   return (
14
   return (
13
     <DropdownMenu.Root dir="ltr">
15
     <DropdownMenu.Root dir="ltr">
14
       <DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
16
       <DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
15
         <Tooltip label="Color">
17
         <Tooltip label="Color">
16
-          <BoxIcon fill={strokes[color]} stroke={strokes[color]} />
18
+          <BoxIcon fill={fills[theme][color]} stroke={strokes[theme][color]} />
17
         </Tooltip>
19
         </Tooltip>
18
       </DropdownMenu.Trigger>
20
       </DropdownMenu.Trigger>
19
       <ColorContent />
21
       <ColorContent />

+ 2
- 1
hooks/useTheme.ts View File

1
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
1
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
 import { useCallback } from 'react'
2
 import { useCallback } from 'react'
3
 import state, { useSelector } from 'state'
3
 import state, { useSelector } from 'state'
4
+import { Theme } from 'types'
4
 
5
 
5
 export default function useTheme() {
6
 export default function useTheme() {
6
-  const theme = useSelector((state) =>
7
+  const theme: Theme = useSelector((state) =>
7
     state.data.settings.isDarkMode ? 'dark' : 'light'
8
     state.data.settings.isDarkMode ? 'dark' : 'light'
8
   )
9
   )
9
 
10
 

+ 53
- 31
state/shape-styles.ts View File

1
-import { ColorStyle, DashStyle, ShapeStyles, SizeStyle } from 'types'
2
-
3
-export const strokes: Record<ColorStyle, string> = {
4
-  [ColorStyle.White]: 'rgba(248, 249, 250, 1.000)',
5
-  [ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
6
-  [ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
7
-  [ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
8
-  [ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
9
-  [ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
10
-  [ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
11
-  [ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
12
-  [ColorStyle.Violet]: 'rgba(112, 72, 232, 1.000)',
13
-  [ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
14
-  [ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
15
-  [ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
1
+import { Theme, ColorStyle, DashStyle, ShapeStyles, SizeStyle } from 'types'
2
+import { lerpColor } from 'utils'
3
+
4
+const canvasLight = '#fafafa'
5
+
6
+const canvasDark = '#343d45'
7
+
8
+const colors = {
9
+  [ColorStyle.White]: '#f0f1f3',
10
+  [ColorStyle.LightGray]: '#c6cbd1',
11
+  [ColorStyle.Gray]: '#788492',
12
+  [ColorStyle.Black]: '#212528',
13
+  [ColorStyle.Green]: '#36b24d',
14
+  [ColorStyle.Cyan]: '#0e98ad',
15
+  [ColorStyle.Blue]: '#1c7ed6',
16
+  [ColorStyle.Indigo]: '#4263eb',
17
+  [ColorStyle.Violet]: '#7746f1',
18
+  [ColorStyle.Red]: '#ff2133',
19
+  [ColorStyle.Orange]: '#ff9433',
20
+  [ColorStyle.Yellow]: '#ffc936',
16
 }
21
 }
17
 
22
 
18
-export const fills = {
19
-  [ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
20
-  [ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
21
-  [ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
22
-  [ColorStyle.Black]: 'rgba(255, 255, 255, 1.000)',
23
-  [ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
24
-  [ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
25
-  [ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
26
-  [ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
27
-  [ColorStyle.Violet]: 'rgba(242, 240, 255, 1.000)',
28
-  [ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
29
-  [ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
30
-  [ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
23
+export const strokes: Record<Theme, Record<ColorStyle, string>> = {
24
+  light: colors,
25
+  dark: {
26
+    ...(Object.fromEntries(
27
+      Object.entries(colors).map(([k, v]) => [k, lerpColor(v, canvasDark, 0.1)])
28
+    ) as Record<ColorStyle, string>),
29
+    [ColorStyle.White]: '#ffffff',
30
+    [ColorStyle.Black]: '#000',
31
+  },
32
+}
33
+
34
+export const fills: Record<Theme, Record<ColorStyle, string>> = {
35
+  light: {
36
+    ...(Object.fromEntries(
37
+      Object.entries(colors).map(([k, v]) => [
38
+        k,
39
+        lerpColor(v, canvasLight, 0.82),
40
+      ])
41
+    ) as Record<ColorStyle, string>),
42
+    [ColorStyle.White]: '#ffffff',
43
+    [ColorStyle.Black]: '#ffffff',
44
+  },
45
+  dark: Object.fromEntries(
46
+    Object.entries(colors).map(([k, v]) => [k, lerpColor(v, canvasDark, 0.618)])
47
+  ) as Record<ColorStyle, string>,
31
 }
48
 }
32
 
49
 
33
 const strokeWidths = {
50
 const strokeWidths = {
57
   return `${fontSize * scale}px/1.4 Verveine Regular`
74
   return `${fontSize * scale}px/1.4 Verveine Regular`
58
 }
75
 }
59
 
76
 
60
-export function getShapeStyle(style: ShapeStyles): {
77
+export function getShapeStyle(
78
+  style: ShapeStyles,
79
+  isDarkMode = false
80
+): {
61
   stroke: string
81
   stroke: string
62
   fill: string
82
   fill: string
63
   strokeWidth: number
83
   strokeWidth: number
66
 
86
 
67
   const strokeWidth = getStrokeWidth(size)
87
   const strokeWidth = getStrokeWidth(size)
68
 
88
 
89
+  const theme: Theme = isDarkMode ? 'dark' : 'light'
90
+
69
   return {
91
   return {
70
-    stroke: strokes[color],
71
-    fill: isFilled ? fills[color] : 'none',
92
+    stroke: strokes[theme][color],
93
+    fill: isFilled ? fills[theme][color] : 'none',
72
     strokeWidth,
94
     strokeWidth,
73
   }
95
   }
74
 }
96
 }

+ 2
- 2
state/shape-utils/arrow.tsx View File

101
     return shape.handles !== prev.handles || shape.style !== prev.style
101
     return shape.handles !== prev.handles || shape.style !== prev.style
102
   },
102
   },
103
 
103
 
104
-  render(shape) {
104
+  render(shape, { isDarkMode }) {
105
     const { bend, handles, style } = shape
105
     const { bend, handles, style } = shape
106
     const { start, end, bend: _bend } = handles
106
     const { start, end, bend: _bend } = handles
107
 
107
 
110
 
110
 
111
     const isDraw = shape.style.dash === DashStyle.Draw
111
     const isDraw = shape.style.dash === DashStyle.Draw
112
 
112
 
113
-    const styles = getShapeStyle(style)
113
+    const styles = getShapeStyle(style, isDarkMode)
114
 
114
 
115
     const { strokeWidth } = styles
115
     const { strokeWidth } = styles
116
 
116
 

+ 2
- 2
state/shape-utils/dot.tsx View File

19
     style: defaultStyle,
19
     style: defaultStyle,
20
   },
20
   },
21
 
21
 
22
-  render(shape) {
23
-    const styles = getShapeStyle(shape.style)
22
+  render(shape, { isDarkMode }) {
23
+    const styles = getShapeStyle(shape.style, isDarkMode)
24
 
24
 
25
     return <use href="#dot" stroke={styles.stroke} fill={styles.stroke} />
25
     return <use href="#dot" stroke={styles.stroke} fill={styles.stroke} />
26
   },
26
   },

+ 2
- 2
state/shape-utils/draw.tsx View File

40
     return shape.points !== prev.points || shape.style !== prev.style
40
     return shape.points !== prev.points || shape.style !== prev.style
41
   },
41
   },
42
 
42
 
43
-  render(shape, { isHovered }) {
43
+  render(shape, { isHovered, isDarkMode }) {
44
     const { points, style } = shape
44
     const { points, style } = shape
45
 
45
 
46
-    const styles = getShapeStyle(style)
46
+    const styles = getShapeStyle(style, isDarkMode)
47
 
47
 
48
     const strokeWidth = +styles.strokeWidth
48
     const strokeWidth = +styles.strokeWidth
49
 
49
 

+ 2
- 2
state/shape-utils/ellipse.tsx View File

42
     )
42
     )
43
   },
43
   },
44
 
44
 
45
-  render(shape) {
45
+  render(shape, { isDarkMode }) {
46
     const { radiusX, radiusY, style } = shape
46
     const { radiusX, radiusY, style } = shape
47
-    const styles = getShapeStyle(style)
47
+    const styles = getShapeStyle(style, isDarkMode)
48
     const strokeWidth = +styles.strokeWidth
48
     const strokeWidth = +styles.strokeWidth
49
 
49
 
50
     const rx = Math.max(0, radiusX - strokeWidth / 2)
50
     const rx = Math.max(0, radiusX - strokeWidth / 2)

+ 2
- 2
state/shape-utils/line.tsx View File

26
     return shape.direction !== prev.direction || shape.style !== prev.style
26
     return shape.direction !== prev.direction || shape.style !== prev.style
27
   },
27
   },
28
 
28
 
29
-  render(shape, { isHovered }) {
29
+  render(shape, { isHovered, isDarkMode }) {
30
     const { id, direction } = shape
30
     const { id, direction } = shape
31
     const [x1, y1] = vec.add([0, 0], vec.mul(direction, 10000))
31
     const [x1, y1] = vec.add([0, 0], vec.mul(direction, 10000))
32
     const [x2, y2] = vec.sub([0, 0], vec.mul(direction, 10000))
32
     const [x2, y2] = vec.sub([0, 0], vec.mul(direction, 10000))
33
 
33
 
34
-    const styles = getShapeStyle(shape.style)
34
+    const styles = getShapeStyle(shape.style, isDarkMode)
35
 
35
 
36
     return (
36
     return (
37
       <g id={id} filter={isHovered ? 'url(#expand)' : 'none'}>
37
       <g id={id} filter={isHovered ? 'url(#expand)' : 'none'}>

+ 2
- 2
state/shape-utils/polyline.tsx View File

28
   shouldRender(shape, prev) {
28
   shouldRender(shape, prev) {
29
     return shape.points !== prev.points || shape.style !== prev.style
29
     return shape.points !== prev.points || shape.style !== prev.style
30
   },
30
   },
31
-  render(shape) {
31
+  render(shape, { isDarkMode }) {
32
     const { points, style } = shape
32
     const { points, style } = shape
33
 
33
 
34
-    const styles = getShapeStyle(style)
34
+    const styles = getShapeStyle(style, isDarkMode)
35
 
35
 
36
     return (
36
     return (
37
       <polyline
37
       <polyline

+ 2
- 2
state/shape-utils/ray.tsx View File

25
   shouldRender(shape, prev) {
25
   shouldRender(shape, prev) {
26
     return shape.direction !== prev.direction || shape.style !== prev.style
26
     return shape.direction !== prev.direction || shape.style !== prev.style
27
   },
27
   },
28
-  render(shape) {
28
+  render(shape, { isDarkMode }) {
29
     const { direction } = shape
29
     const { direction } = shape
30
 
30
 
31
-    const styles = getShapeStyle(shape.style)
31
+    const styles = getShapeStyle(shape.style, isDarkMode)
32
 
32
 
33
     const [x2, y2] = vec.add([0, 0], vec.mul(direction, 10000))
33
     const [x2, y2] = vec.add([0, 0], vec.mul(direction, 10000))
34
 
34
 

+ 2
- 2
state/shape-utils/rectangle.tsx View File

28
     return shape.size !== prev.size || shape.style !== prev.style
28
     return shape.size !== prev.size || shape.style !== prev.style
29
   },
29
   },
30
 
30
 
31
-  render(shape, { isHovered }) {
31
+  render(shape, { isHovered, isDarkMode }) {
32
     const { id, size, radius, style } = shape
32
     const { id, size, radius, style } = shape
33
-    const styles = getShapeStyle(style)
33
+    const styles = getShapeStyle(style, isDarkMode)
34
     const strokeWidth = +styles.strokeWidth
34
     const strokeWidth = +styles.strokeWidth
35
 
35
 
36
     if (style.dash === DashStyle.Draw) {
36
     if (style.dash === DashStyle.Draw) {

+ 2
- 2
state/shape-utils/text.tsx View File

70
     )
70
     )
71
   },
71
   },
72
 
72
 
73
-  render(shape, { isEditing, ref }) {
73
+  render(shape, { isEditing, isDarkMode, ref }) {
74
     const { id, text, style } = shape
74
     const { id, text, style } = shape
75
-    const styles = getShapeStyle(style)
75
+    const styles = getShapeStyle(style, isDarkMode)
76
     const font = getFontStyle(shape.scale, shape.style)
76
     const font = getFontStyle(shape.scale, shape.style)
77
 
77
 
78
     const bounds = this.getBounds(shape)
78
     const bounds = this.getBounds(shape)

+ 22
- 8
state/state.ts View File

8
 import session from './session'
8
 import session from './session'
9
 import clipboard from './clipboard'
9
 import clipboard from './clipboard'
10
 import commands from './commands'
10
 import commands from './commands'
11
+import { dark, light } from 'styles'
11
 import {
12
 import {
12
   vec,
13
   vec,
13
   getCommonBounds,
14
   getCommonBounds,
43
   settings: {
44
   settings: {
44
     fontSize: 13,
45
     fontSize: 13,
45
     isTestMode: false,
46
     isTestMode: false,
46
-    isDarkMode: false,
47
+    isDarkMode: true,
47
     isCodeOpen: false,
48
     isCodeOpen: false,
48
     isDebugMode: false,
49
     isDebugMode: false,
49
     isDebugOpen: false,
50
     isDebugOpen: false,
144
 
145
 
145
 const state = createState({
146
 const state = createState({
146
   data: initialData,
147
   data: initialData,
148
+  onEnter: 'applyTheme',
147
   on: {
149
   on: {
148
     TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
150
     TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
149
     TOGGLED_DEBUG_MODE: 'toggleDebugMode',
151
     TOGGLED_DEBUG_MODE: 'toggleDebugMode',
168
       },
170
       },
169
     },
171
     },
170
     ready: {
172
     ready: {
171
-      onEnter: {
172
-        wait: 0.01,
173
-        if: 'hasSelection',
174
-        do: 'zoomCameraToSelectionActual',
175
-        else: ['zoomCameraToActual'],
176
-      },
173
+      onEnter: [
174
+        'applyTheme',
175
+        {
176
+          wait: 0.01,
177
+          if: 'hasSelection',
178
+          do: 'zoomCameraToSelectionActual',
179
+          else: ['zoomCameraToActual'],
180
+        },
181
+      ],
177
       on: {
182
       on: {
178
         UNMOUNTED: {
183
         UNMOUNTED: {
179
           do: ['saveDocumentState', 'resetDocumentState'],
184
           do: ['saveDocumentState', 'resetDocumentState'],
204
           do: 'pasteShapesFromClipboard',
209
           do: 'pasteShapesFromClipboard',
205
         },
210
         },
206
         TOGGLED_DARK_MODE: {
211
         TOGGLED_DARK_MODE: {
207
-          do: 'toggleDarkMode',
212
+          do: ['toggleDarkMode', 'applyTheme'],
208
         },
213
         },
209
         TOGGLED_SHAPE_LOCK: {
214
         TOGGLED_SHAPE_LOCK: {
210
           unlessAny: ['isReadOnly', 'isInSession'],
215
           unlessAny: ['isReadOnly', 'isInSession'],
1971
     toggleDarkMode(data) {
1976
     toggleDarkMode(data) {
1972
       data.settings.isDarkMode = !data.settings.isDarkMode
1977
       data.settings.isDarkMode = !data.settings.isDarkMode
1973
     },
1978
     },
1979
+    applyTheme(data) {
1980
+      if (data.settings.isDarkMode && typeof document !== 'undefined') {
1981
+        document.body.classList.remove(light)
1982
+        document.body.classList.add(dark)
1983
+      } else {
1984
+        document.body.classList.remove(dark)
1985
+        document.body.classList.add(light)
1986
+      }
1987
+    },
1974
 
1988
 
1975
     /* --------------------- Styles --------------------- */
1989
     /* --------------------- Styles --------------------- */
1976
 
1990
 

+ 20
- 11
styles/stitches.config.ts View File

96
 
96
 
97
 const dark = theme({
97
 const dark = theme({
98
   colors: {
98
   colors: {
99
-    codeHl: 'rgba(144, 144, 144, .15)',
100
     brushFill: 'rgba(0,0,0,.05)',
99
     brushFill: 'rgba(0,0,0,.05)',
101
     brushStroke: 'rgba(0,0,0,.25)',
100
     brushStroke: 'rgba(0,0,0,.25)',
102
     hint: 'rgba(216, 226, 249, 1.000)',
101
     hint: 'rgba(216, 226, 249, 1.000)',
103
-    selected: 'rgba(66, 133, 244, 1.000)',
104
-    bounds: 'rgba(65, 132, 244, 1.000)',
105
-    boundsBg: 'rgba(65, 132, 244, 0.05)',
106
-    highlight: 'rgba(65, 132, 244, 0.15)',
102
+    selected: 'rgba(82, 143, 245, 1.000)',
103
+    bounds: 'rgba(82, 143, 245, 1.000)',
104
+    boundsBg: 'rgba(82, 143, 245, 0.05)',
105
+    highlight: 'rgba(82, 143, 245, 0.15)',
107
     overlay: 'rgba(0, 0, 0, 0.15)',
106
     overlay: 'rgba(0, 0, 0, 0.15)',
108
-    border: '#aaa',
109
-    canvas: '#fafafa',
110
-    panel: '#fefefe',
107
+    border: '#202529',
108
+    canvas: '#343d45',
109
+    panel: '#49555f',
111
     inactive: '#cccccf',
110
     inactive: '#cccccf',
112
-    hover: '#efefef',
113
-    text: '#333',
114
-    muted: '#777',
111
+    hover: '#343d45',
112
+    text: '#f8f9fa',
113
+    muted: '#e0e2e6',
115
     input: '#f3f3f3',
114
     input: '#f3f3f3',
116
     inputBorder: '#ddd',
115
     inputBorder: '#ddd',
116
+    codeHl: 'rgba(144, 144, 144, .15)',
117
     lineError: 'rgba(255, 0, 0, .1)',
117
     lineError: 'rgba(255, 0, 0, .1)',
118
   },
118
   },
119
+  shadows: {
120
+    2: '0px 1px 1px rgba(0, 0, 0, 0.24)',
121
+    3: '0px 2px 3px rgba(0, 0, 0, 0.24)',
122
+    4: '0px 4px 5px -1px rgba(0, 0, 0, 0.24)',
123
+    8: '0px 12px 17px rgba(0, 0, 0, 0.24)',
124
+    12: '0px 12px 17px rgba(0, 0, 0, 0.24)',
125
+    24: '0px 24px 38px rgba(0, 0, 0, 0.24)',
126
+  },
119
 })
127
 })
120
 
128
 
121
 const globalStyles = global({
129
 const globalStyles = global({
130
     overscrollBehavior: 'none',
138
     overscrollBehavior: 'none',
131
     fontFamily: '$ui',
139
     fontFamily: '$ui',
132
     fontSize: '$2',
140
     fontSize: '$2',
141
+    color: '$text',
133
     backgroundColor: '$canvas',
142
     backgroundColor: '$canvas',
134
   },
143
   },
135
   body: {
144
   body: {

+ 3
- 0
types.ts View File

124
   ExtraLarge = 'ExtraLarge',
124
   ExtraLarge = 'ExtraLarge',
125
 }
125
 }
126
 
126
 
127
+export type Theme = 'dark' | 'light'
128
+
127
 export type ShapeStyles = {
129
 export type ShapeStyles = {
128
   color: ColorStyle
130
   color: ColorStyle
129
   size: SizeStyle
131
   size: SizeStyle
612
       isHovered?: boolean
614
       isHovered?: boolean
613
       isSelected?: boolean
615
       isSelected?: boolean
614
       isCurrentParent?: boolean
616
       isCurrentParent?: boolean
617
+      isDarkMode?: boolean
615
       ref?: React.MutableRefObject<HTMLTextAreaElement>
618
       ref?: React.MutableRefObject<HTMLTextAreaElement>
616
     }
619
     }
617
   ): JSX.Element
620
   ): JSX.Element

+ 41
- 0
utils/utils.ts View File

20
   return y1 * (1 - mu) + y2 * mu
20
   return y1 * (1 - mu) + y2 * mu
21
 }
21
 }
22
 
22
 
23
+/**
24
+ * Linear interpolation between two colors.
25
+ *
26
+ * ### Example
27
+ *
28
+ *```ts
29
+ * lerpColor("#000000", "#0099FF", .25)
30
+ *```
31
+ */
32
+function h2r(hex: string) {
33
+  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
34
+  return result
35
+    ? [
36
+        parseInt(result[1], 16),
37
+        parseInt(result[2], 16),
38
+        parseInt(result[3], 16),
39
+      ]
40
+    : null
41
+}
42
+
43
+function r2h(rgb: number[]) {
44
+  return (
45
+    '#' +
46
+    ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1)
47
+  )
48
+}
49
+
50
+export function lerpColor(
51
+  color1: string,
52
+  color2: string,
53
+  factor = 0.5
54
+): string {
55
+  const c1 = h2r(color1)
56
+  const c2 = h2r(color2)
57
+  const result = c1.slice()
58
+  for (let i = 0; i < 3; i++) {
59
+    result[i] = Math.round(result[i] + factor * (c2[i] - c1[i]))
60
+  }
61
+  return r2h(result)
62
+}
63
+
23
 /**
64
 /**
24
  * Modulate a value between two ranges.
65
  * Modulate a value between two ranges.
25
  * @param value
66
  * @param value

Loading…
Cancel
Save