diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts
index acd57f176..192feb4ab 100644
--- a/e2e/playwright/flow-tests.spec.ts
+++ b/e2e/playwright/flow-tests.spec.ts
@@ -4,6 +4,7 @@ import { EngineCommand } from '../../src/lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
+import { Themes } from '../../src/lib/theme'
/*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@@ -631,3 +632,46 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// hover again and check it works
await selectionSequence()
})
+
+test('Command bar works and can change a setting', async ({ page }) => {
+ // Brief boilerplate
+ const u = getUtils(page)
+ await page.setViewportSize({ width: 1200, height: 500 })
+ await page.goto('/')
+ await u.waitForAuthSkipAppStart()
+
+ let cmdSearchBar = page.getByPlaceholder('Search commands')
+
+ // First try opening the command bar and closing it
+ await page.getByRole('button', { name: '⌘K' }).click()
+ await expect(cmdSearchBar).toBeVisible()
+ await page.keyboard.press('Escape')
+ await expect(cmdSearchBar).not.toBeVisible()
+
+ // Now try the same, but with the keyboard shortcut, check focus
+ await page.keyboard.press('Meta+K')
+ await expect(cmdSearchBar).toBeVisible()
+ await expect(cmdSearchBar).toBeFocused()
+
+ // Try typing in the command bar
+ await page.keyboard.type('theme')
+ const themeOption = page.getByRole('option', { name: 'Set Theme' })
+ await expect(themeOption).toBeVisible()
+ await themeOption.click()
+ const themeInput = page.getByPlaceholder(Themes.System)
+ await expect(themeInput).toBeVisible()
+ await expect(themeInput).toBeFocused()
+ // Select dark theme
+ await page.keyboard.press('ArrowDown')
+ await page.keyboard.press('ArrowUp')
+ await expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
+ 'data-headlessui-state',
+ 'active'
+ )
+ await page.keyboard.press('Enter')
+
+ // Check the toast appeared
+ await expect(page.getByText(`Set Theme to "${Themes.Dark}"`)).toBeVisible()
+ // Check that the theme changed
+ await expect(page.locator('body')).toHaveClass(`body-bg ${Themes.Dark}`)
+})
diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-1-Google-Chrome-linux.png
index d693b74b4..b05191d7d 100644
Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-1-Google-Chrome-linux.png differ
diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-2-Google-Chrome-linux.png
index 43ee467ca..caa4f8793 100644
Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-2-Google-Chrome-linux.png differ
diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-3-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-3-Google-Chrome-linux.png
index 9fd5080b0..f63be3c6d 100644
Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-3-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/change-camera-show-planes-3-Google-Chrome-linux.png differ
diff --git a/public/Icon/Icon/Projects/Create File.svg b/public/Icon/Icon/Projects/Create File.svg
deleted file mode 100644
index 757ee17b3..000000000
--- a/public/Icon/Icon/Projects/Create File.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/public/Icon/Icon/Projects/Create Folder.svg b/public/Icon/Icon/Projects/Create Folder.svg
deleted file mode 100644
index a0a23431d..000000000
--- a/public/Icon/Icon/Projects/Create Folder.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/public/Icon/Icon/Projects/File.svg b/public/Icon/Icon/Projects/File.svg
deleted file mode 100644
index 2b8287a46..000000000
--- a/public/Icon/Icon/Projects/File.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/Toolbar.module.css b/src/Toolbar.module.css
deleted file mode 100644
index d807a6831..000000000
--- a/src/Toolbar.module.css
+++ /dev/null
@@ -1,106 +0,0 @@
-.toolbarWrapper {
- @apply relative;
-}
-
-.toolbar {
- @apply flex gap-4 items-center rounded-full;
- @apply border border-cool-20/30 bg-cool-10/50;
-}
-
-:global(.dark) .toolbar {
- @apply border-cool-100/50 bg-cool-120/50;
-}
-
-:global(.sketch) .toolbar {
- @apply border-fern-20/20 bg-fern-10/20;
-}
-
-:global(.dark .sketch) .toolbar {
- @apply border-fern-120/50 bg-fern-100/30;
-}
-
-.toolbarCap {
- @apply text-sm font-bold;
- @apply bg-cool-20/50 text-cool-100;
-}
-
-:global(.dark) .toolbarCap {
- @apply bg-cool-90/50 text-cool-30;
-}
-
-:global(.sketch) .toolbarCap {
- @apply bg-fern-20/50 text-fern-100;
-}
-
-:global(.dark .sketch) .toolbarCap {
- @apply bg-fern-90/50 text-fern-30;
-}
-
-.label {
- @apply self-stretch flex items-center px-4 py-1;
- @apply rounded-l-full;
-}
-
-.popoverToggle {
- @apply self-stretch m-0 flex items-center px-4 py-1;
- @apply rounded-r-full border-none;
- @apply hover:bg-cool-20;
-}
-
-.toolbarButtons::-webkit-scrollbar {
- @apply h-0.5;
-}
-
-.toolbarButtons {
- @apply flex items-center overflow-x-auto;
- scrollbar-width: thin;
-}
-
-.toolbarButtons button {
- @apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- @apply gap-1.5 p-0.5 pr-1;
- @apply rounded-sm;
-}
-:global(.dark) .toolbarButtons button {
- @apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
-}
-.toolbarButtons button:hover {
- @apply text-cool-90 bg-cool-10;
-}
-:global(.sketch) .toolbarButtons button:hover {
- @apply text-fern-90 bg-fern-10;
-}
-.toolbarButtons button:disabled {
- @apply text-chalkboard-70 bg-chalkboard-30;
-}
-.toolbarButtons button:disabled:hover {
- @apply !bg-inherit !text-inherit cursor-not-allowed;
-}
-
-:global(.dark) .toolbarButtons button {
- @apply text-chalkboard-20 border-chalkboard-50;
-}
-:global(.dark) .toolbarButtons button:hover {
- @apply text-cool-10 border-chalkboard-50 bg-cool-90;
-}
-:global(.dark .sketch) .toolbarButtons button:hover {
- @apply text-fern-10 border-chalkboard-50 bg-fern-90;
-}
-:global(.dark) .toolbarButtons button:disabled {
- @apply text-chalkboard-40 bg-chalkboard-80;
-}
-
-:global(.dark) .popoverToggle {
- @apply hover:bg-cool-90;
-}
-
-:global(.sketch) .popoverToggle {
- @apply hover:bg-fern-20;
-}
-
-:global(.dark .sketch) .popoverToggle {
- @apply hover:bg-fern-90;
-}
diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx
index f1b57d806..4c6d9712b 100644
--- a/src/Toolbar.tsx
+++ b/src/Toolbar.tsx
@@ -1,22 +1,16 @@
-import { Fragment, WheelEvent, useRef, useMemo } from 'react'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
-import { Popover, Transition } from '@headlessui/react'
-import styles from './Toolbar.module.css'
+import { WheelEvent, useRef, useMemo } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util'
-import { ActionIcon } from 'components/ActionIcon'
import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
-
-export const sketchButtonClassnames = {
- background:
- 'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
- icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
-}
+import { useCommandsContext } from 'hooks/useCommandsContext'
+import { ActionButton } from 'components/ActionButton'
export const Toolbar = () => {
+ const { setCommandBarOpen } = useCommandsContext()
const { state, send, context } = useModelingContext()
- const toolbarButtonsRef = useRef(null)
+ const toolbarButtonsRef = useRef(null)
+ const bgClassName =
+ 'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
const pathId = useMemo(
() =>
isCursorInSketchCommandRange(
@@ -35,72 +29,102 @@ export const Toolbar = () => {
span.scrollLeft = span.scrollLeft += ev.deltaY
}
- function ToolbarButtons({ className }: React.HTMLAttributes) {
+ function ToolbarButtons({
+ className = '',
+ ...props
+ }: React.HTMLAttributes) {
return (
-
{state.nextEvents.includes('Enter sketch') && (
-
+
+ send({ type: 'Enter sketch' })}
+ icon={{
+ icon: 'sketch',
+ bgClassName,
+ }}
+ >
+ Start Sketch
+
+
)}
{state.nextEvents.includes('Enter sketch') && pathId && (
-
+
+ send({ type: 'Enter sketch' })}
+ icon={{
+ icon: 'sketch',
+ bgClassName,
+ }}
+ >
+ Edit Sketch
+
+
)}
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
-
+
+ send({ type: 'Cancel' })}
+ icon={{
+ icon: 'arrowLeft',
+ bgClassName,
+ }}
+ >
+ Exit Sketch
+
+
)}
{state.matches('Sketch') && !state.matches('idle') && (
-
+
+
+ state.matches('Sketch.Line Tool')
+ ? send('CancelSketch')
+ : send('Equip tool')
+ }
+ aria-pressed={state.matches('Sketch.Line Tool')}
+ className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
+ icon={{
+ icon: 'line',
+ bgClassName,
+ }}
+ >
+ Line
+
+
)}
{state.matches('Sketch') && (
-
+
+
+ state.matches('Sketch.Move Tool')
+ ? send('CancelSketch')
+ : send('Equip move tool')
+ }
+ aria-pressed={state.matches('Sketch.Move Tool')}
+ className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
+ icon={{
+ icon: 'move',
+ bgClassName,
+ }}
+ >
+ Move
+
+
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents
@@ -125,102 +149,66 @@ export const Toolbar = () => {
return 0
})
.map((eventName) => (
-
+
+ send(eventName)}
+ disabled={
+ !state.nextEvents
+ .filter((event) => state.can(event as any))
+ .includes(eventName)
+ }
+ title={eventName}
+ icon={{
+ icon: 'line',
+ bgClassName,
+ }}
+ >
+ {eventName
+ .replace('Make segment ', '')
+ .replace('Constrain ', '')}
+
+
))}
{state.matches('idle') && (
-
+
+ send('extrude intent')}
+ disabled={!state.can('extrude intent')}
+ title={
+ state.can('extrude intent')
+ ? 'extrude'
+ : 'sketches need to be closed, or not already extruded'
+ }
+ icon={{
+ icon: 'extrude',
+ bgClassName,
+ }}
+ >
+ Extrude
+
+
)}
-
+
)
}
return (
-
-
-
- {state.matches('Sketch') ? '2D' : '3D'}
-
-
-
-
-
-
-
+
+ setCommandBarOpen(true)}
+ className="rounded-r-full pr-4 self-stretch border-energy-10 hover:border-energy-10 dark:border-chalkboard-80 bg-energy-10/50 hover:bg-energy-10 dark:bg-chalkboard-80 dark:text-energy-10"
>
-
-
-
-
-
-
- You're in {state.matches('Sketch') ? '2D' : '3D'}
-
-
-
-
-
-
-
-
-
+ ⌘K
+
+
)
}
diff --git a/src/components/ActionButton.tsx b/src/components/ActionButton.tsx
index 1cca9f939..e79c76b63 100644
--- a/src/components/ActionButton.tsx
+++ b/src/components/ActionButton.tsx
@@ -39,16 +39,16 @@ type ActionButtonProps =
| ActionButtonAsElement
export const ActionButton = (props: ActionButtonProps) => {
- const classNames = `group mono text-base flex items-center gap-2 rounded-sm border border-chalkboard-40 dark:border-chalkboard-60 hover:border-liquid-40 dark:hover:bg-chalkboard-90 p-[3px] text-chalkboard-110 dark:text-chalkboard-10 hover:text-chalkboard-110 hover:dark:text-chalkboard-10 ${
+ const classNames = `action-button m-0 group mono text-sm flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 p-[3px] text-chalkboard-100 dark:text-chalkboard-10 ${
props.icon ? 'pr-2' : 'px-2'
- } ${props.className || ''}`
+ } ${props.className ? props.className : ''}`
switch (props.Element) {
case 'button': {
// Note we have to destructure 'className' and 'Element' out of props
// because we don't want to pass them to the button element;
// the same is true for the other cases below.
- const { Element, icon, children, className, ...rest } = props
+ const { Element, icon, children, className: _className, ...rest } = props
return (
-
-
- Dismiss
-
-
- Next: Project Menu
-
-
+
)
diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx
index 6e36cb4d7..36b9465ad 100644
--- a/src/routes/Onboarding/index.tsx
+++ b/src/routes/Onboarding/index.tsx
@@ -17,6 +17,7 @@ import Export from './Export'
import FutureWork from './FutureWork'
import { paths } from 'Router'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
+import { ActionButton } from 'components/ActionButton'
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
@@ -120,6 +121,45 @@ export function useDismiss() {
}, [send, navigate, filePath])
}
+export function OnboardingButtons({
+ next,
+ nextText,
+ dismiss,
+ className,
+ ...props
+}: {
+ next: () => void
+ nextText?: string
+ dismiss: () => void
+ className?: string
+} & React.HTMLAttributes) {
+ return (
+
+
+ Dismiss
+
+
+ {nextText ?? 'Next'}
+
+
+ )
+}
+
const Onboarding = () => {
const dismiss = useDismiss()
useHotkeys('esc', dismiss)
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index 6ffb68a02..0e152b1b1 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -1,8 +1,4 @@
-import {
- faArrowRotateBack,
- faFolder,
- faXmark,
-} from '@fortawesome/free-solid-svg-icons'
+import { faArrowRotateBack, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../components/ActionButton'
import { AppHeader } from '../components/AppHeader'
import { open } from '@tauri-apps/api/dialog'
@@ -185,14 +181,9 @@ export const Settings = () => {
/>
Choose a folder
@@ -305,7 +296,7 @@ export const Settings = () => {
Replay Onboarding
diff --git a/tailwind.config.js b/tailwind.config.js
index 0509de0ad..715cd3bda 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,3 +1,5 @@
+const plugin = require('tailwindcss/plugin')
+
const themeColorRamps = [
{ name: 'chalkboard', stops: 12 },
{ name: 'energy', stops: 12 },
@@ -10,27 +12,23 @@ const themeColorRamps = [
{ name: 'warn', stops: 8 },
{ name: 'succeed', stops: 8 },
]
-const toOKLCHVar = val => `oklch(var(${val}) / ) `
+const toOKLCHVar = (val) => `oklch(var(${val}) / ) `
const themeColors = Object.fromEntries(
- themeColorRamps.map(({name, stops}) => [
- name,
- Object.fromEntries(
- new Array(stops)
- .fill(0)
- .map((_, i) => [
- (i + 1) * 10,
- toOKLCHVar(`--_${name}-${(i + 1) * 10}`),
- ])
- ),
+ themeColorRamps.map(({ name, stops }) => [
+ name,
+ Object.fromEntries(
+ new Array(stops)
+ .fill(0)
+ .map((_, i) => [(i + 1) * 10, toOKLCHVar(`--_${name}-${(i + 1) * 10}`)])
+ ),
])
)
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: [
- "./src/**/*.{js,jsx,ts,tsx}",
- ],
+ mode: 'jit',
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
@@ -41,5 +39,22 @@ module.exports = {
darkMode: 'class',
plugins: [
require('@headlessui/tailwindcss'),
+ // custom plugin to add variants for aria-pressed
+ // To use, just add a class of 'group-pressed:' or 'pressed:'
+ // to your element. Based on https://dev.to/philw_/tying-tailwind-styling-to-aria-attributes-502f
+ plugin(function ({ addVariant, e }) {
+ addVariant('group-pressed', ({ modifySelectors, separator }) => {
+ modifySelectors(({ className }) => {
+ return `.group[aria-pressed='true'] .${e(
+ `group-pressed${separator}${className}`
+ )}`
+ })
+ })
+ addVariant('pressed', ({ modifySelectors, separator }) => {
+ modifySelectors(({ className }) => {
+ return `.${e(`pressed${separator}${className}`)}[aria-pressed='true']`
+ })
+ })
+ }),
],
}