Add a component for toolbar buttons with a dropdown, consolidate Constrain actions under one (#2327)

This commit is contained in:
Frank Noirot
2024-05-10 19:02:11 -04:00
committed by GitHub
parent d0f9ae475f
commit 726fd02bad
36 changed files with 238 additions and 121 deletions

View File

@ -131,6 +131,7 @@ test('Basic sketch', async ({ page }) => {
// selected two lines therefore there should be two cursors // selected two lines therefore there should be two cursors
await expect(page.locator('.cm-cursor')).toHaveCount(2) await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.getByRole('button', { name: 'Constrain' }).click()
await page.getByRole('button', { name: 'Equal Length' }).click() await page.getByRole('button', { name: 'Equal Length' }).click()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
@ -937,11 +938,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// click a segment hold shift and click an axis, see that a relevant constraint is enabled // click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick() await topHorzSegmentClick()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { name: 'Constrain' })
const absYButton = page.getByRole('button', { name: 'ABS Y' }) const absYButton = page.getByRole('button', { name: 'ABS Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled() await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await xAxisClick() await xAxisClick()
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor() await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled() await expect(absYButton).not.toBeDisabled()
@ -951,12 +955,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
// same selection but click the axis first // same selection but click the axis first
await xAxisClick() await xAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled() await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await topHorzSegmentClick() await topHorzSegmentClick()
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled() await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing // clear selection by clicking on nothing
@ -965,10 +971,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check the same selection again by putting cursor in code first then selecting axis // check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click() await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled() await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await xAxisClick() await xAxisClick()
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled() await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing // clear selection by clicking on nothing
@ -1902,6 +1910,7 @@ test('Can code mod a line length', async ({ page }) => {
const startXPx = 500 const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 102) await page.mouse.click(615, 102)
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
await page.getByRole('button', { name: 'length', exact: true }).click() await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click() await page.getByText('Add constraining value').click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -11,17 +11,18 @@ import {
useNetworkStatus, useNetworkStatus,
} from 'components/NetworkHealthIndicator' } from 'components/NetworkHealthIndicator'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
export const Toolbar = () => { export const Toolbar = () => {
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const { state, send, context } = useModelingContext() const { state, send, context } = useModelingContext()
const toolbarButtonsRef = useRef<HTMLUListElement>(null) const toolbarButtonsRef = useRef<HTMLUListElement>(null)
const iconClassName = const iconClassName =
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10' 'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10'
const bgClassName = const bgClassName =
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary' 'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary'
const buttonClassName = const buttonClassName =
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100' 'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary'
const pathId = useMemo(() => { const pathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) { if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
return false return false
@ -57,10 +58,7 @@ export const Toolbar = () => {
{...props} {...props}
ref={toolbarButtonsRef} ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent} onWheel={handleToolbarButtonsWheelEvent}
className={ className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
className
}
style={{ scrollbarWidth: 'thin' }} style={{ scrollbarWidth: 'thin' }}
> >
{state.nextEvents.includes('Enter sketch') && ( {state.nextEvents.includes('Enter sketch') && (
@ -71,7 +69,7 @@ export const Toolbar = () => {
onClick={() => onClick={() =>
send({ type: 'Enter sketch', data: { forceNewSketch: true } }) send({ type: 'Enter sketch', data: { forceNewSketch: true } })
} }
icon={{ iconStart={{
icon: 'sketch', icon: 'sketch',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -88,7 +86,7 @@ export const Toolbar = () => {
className={buttonClassName} className={buttonClassName}
Element="button" Element="button"
onClick={() => send({ type: 'Enter sketch' })} onClick={() => send({ type: 'Enter sketch' })}
icon={{ iconStart={{
icon: 'sketch', icon: 'sketch',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -105,7 +103,7 @@ export const Toolbar = () => {
className={buttonClassName} className={buttonClassName}
Element="button" Element="button"
onClick={() => send({ type: 'Cancel' })} onClick={() => send({ type: 'Cancel' })}
icon={{ iconStart={{
icon: 'arrowLeft', icon: 'arrowLeft',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -128,7 +126,7 @@ export const Toolbar = () => {
: send('Equip Line tool') : send('Equip Line tool')
} }
aria-pressed={state?.matches('Sketch.Line tool')} aria-pressed={state?.matches('Sketch.Line tool')}
icon={{ iconStart={{
icon: 'line', icon: 'line',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -148,7 +146,7 @@ export const Toolbar = () => {
: send('Equip tangential arc to') : send('Equip tangential arc to')
} }
aria-pressed={state.matches('Sketch.Tangential arc to')} aria-pressed={state.matches('Sketch.Tangential arc to')}
icon={{ iconStart={{
icon: 'arc', icon: 'arc',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -172,7 +170,7 @@ export const Toolbar = () => {
: send('Equip rectangle tool') : send('Equip rectangle tool')
} }
aria-pressed={state.matches('Sketch.Rectangle tool')} aria-pressed={state.matches('Sketch.Rectangle tool')}
icon={{ iconStart={{
icon: 'rectangle', icon: 'rectangle',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -194,7 +192,13 @@ export const Toolbar = () => {
</> </>
)} )}
{state.matches('Sketch.SketchIdle') && {state.matches('Sketch.SketchIdle') &&
state.nextEvents state.nextEvents.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
).length > 0 && (
<ActionButtonDropdown
splitMenuItems={state.nextEvents
.filter( .filter(
(eventName) => (eventName) =>
eventName.includes('Make segment') || eventName.includes('Make segment') ||
@ -215,31 +219,27 @@ export const Toolbar = () => {
} }
return 0 return 0
}) })
.map((eventName) => ( .map((eventName) => ({
<li className="contents" key={eventName}> label: eventName
<ActionButton .replace('Make segment ', '')
className={buttonClassName} .replace('Constrain ', ''),
Element="button" onClick: () => send(eventName),
key={eventName} disabled:
onClick={() => send(eventName)}
disabled={
!state.nextEvents !state.nextEvents
.filter((event) => state.can(event as any)) .filter((event) => state.can(event as any))
.includes(eventName) || disableAllButtons .includes(eventName) || disableAllButtons,
} }))}
title={eventName} className={buttonClassName}
icon={{ Element="button"
icon: 'line', iconStart={{
icon: 'dimension',
iconClassName, iconClassName,
bgClassName, bgClassName,
}} }}
> >
{eventName Constrain
.replace('Make segment ', '') </ActionButtonDropdown>
.replace('Constrain ', '')} )}
</ActionButton>
</li>
))}
{state.matches('idle') && ( {state.matches('idle') && (
<li className="contents"> <li className="contents">
<ActionButton <ActionButton
@ -257,7 +257,7 @@ export const Toolbar = () => {
? 'extrude' ? 'extrude'
: 'sketches need to be closed, or not already extruded' : 'sketches need to be closed, or not already extruded'
} }
icon={{ iconStart={{
icon: 'extrude', icon: 'extrude',
iconClassName, iconClassName,
bgClassName, bgClassName,
@ -272,7 +272,7 @@ export const Toolbar = () => {
} }
return ( return (
<menu className="max-w-full overflow-hidden whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative"> <menu className="max-w-full whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
<ToolbarButtons /> <ToolbarButtons />
</menu> </menu>
) )

View File

@ -1,11 +1,12 @@
import { ActionIcon, ActionIconProps } from './ActionIcon' import { ActionIcon, ActionIconProps } from './ActionIcon'
import React from 'react' import React, { ForwardedRef, forwardRef } from 'react'
import { paths } from 'lib/paths' import { paths } from 'lib/paths'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import type { LinkProps } from 'react-router-dom' import type { LinkProps } from 'react-router-dom'
interface BaseActionButtonProps { interface BaseActionButtonProps {
icon?: ActionIconProps iconStart?: ActionIconProps
iconEnd?: ActionIconProps
className?: string className?: string
} }
@ -32,15 +33,15 @@ type ActionButtonAsElement = BaseActionButtonProps &
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>> Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
} }
type ActionButtonProps = export type ActionButtonProps =
| ActionButtonAsButton | ActionButtonAsButton
| ActionButtonAsLink | ActionButtonAsLink
| ActionButtonAsExternal | ActionButtonAsExternal
| ActionButtonAsElement | ActionButtonAsElement
export const ActionButton = (props: ActionButtonProps) => { export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
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 ${ const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
props.icon ? 'pr-2' : 'px-2' props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
} ${props.className ? props.className : ''}` } ${props.className ? props.className : ''}`
switch (props.Element) { switch (props.Element) {
@ -48,11 +49,23 @@ export const ActionButton = (props: ActionButtonProps) => {
// Note we have to destructure 'className' and 'Element' out of props // Note we have to destructure 'className' and 'Element' out of props
// because we don't want to pass them to the button element; // because we don't want to pass them to the button element;
// the same is true for the other cases below. // the same is true for the other cases below.
const { Element, icon, children, className: _className, ...rest } = props const {
Element,
iconStart,
iconEnd,
children,
className: _className,
...rest
} = props
return ( return (
<button className={classNames} {...rest}> <button
{props.icon && <ActionIcon {...icon} />} ref={ref as ForwardedRef<HTMLButtonElement>}
className={classNames}
{...rest}
>
{iconStart && <ActionIcon {...iconStart} />}
{children} {children}
{iconEnd && <ActionIcon {...iconEnd} />}
</button> </button>
) )
} }
@ -60,15 +73,22 @@ export const ActionButton = (props: ActionButtonProps) => {
const { const {
Element, Element,
to, to,
icon, iconStart,
iconEnd,
children, children,
className: _className, className: _className,
...rest ...rest
} = props } = props
return ( return (
<Link to={to || paths.INDEX} className={classNames} {...rest}> <Link
{icon && <ActionIcon {...icon} />} ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX}
className={classNames}
{...rest}
>
{iconStart && <ActionIcon {...iconStart} />}
{children} {children}
{iconEnd && <ActionIcon {...iconEnd} />}
</Link> </Link>
) )
} }
@ -76,33 +96,42 @@ export const ActionButton = (props: ActionButtonProps) => {
const { const {
Element, Element,
to, to,
icon, iconStart,
iconEnd,
children, children,
className: _className, className: _className,
...rest ...rest
} = props } = props
return ( return (
<Link <Link
ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX} to={to || paths.INDEX}
className={classNames} className={classNames}
{...rest} {...rest}
target="_blank" target="_blank"
> >
{icon && <ActionIcon {...icon} />} {iconStart && <ActionIcon {...iconStart} />}
{children} {children}
{iconEnd && <ActionIcon {...iconEnd} />}
</Link> </Link>
) )
} }
default: { default: {
const { Element, icon, children, className: _className, ...rest } = props const {
if (!Element) throw new Error('Element is required') Element,
iconStart,
children,
className: _className,
...rest
} = props
return ( return (
<Element className={classNames} {...rest}> <Element className={classNames} {...rest}>
{props.icon && <ActionIcon {...props.icon} />} {props.iconStart && <ActionIcon {...props.iconStart} />}
{children} {children}
{props.iconEnd && <ActionIcon {...props.iconEnd} />}
</Element> </Element>
) )
} }
} }
} })

View File

@ -0,0 +1,55 @@
import { Popover } from '@headlessui/react'
import { ActionButton, ActionButtonProps } from './ActionButton'
type ActionButtonSplitProps = Omit<ActionButtonProps, 'iconEnd'> & {
splitMenuItems: {
label: string
shortcut?: string
onClick: () => void
disabled?: boolean
}[]
}
export function ActionButtonDropdown({
splitMenuItems,
className,
...props
}: ActionButtonSplitProps) {
return (
<Popover className="relative">
<Popover.Button
as={ActionButton}
className={className}
{...props}
Element="button"
iconEnd={{
icon: 'caretDown',
className: 'ui-open:rotate-180',
bgClassName:
'bg-chalkboard-20 dark:bg-chalkboard-80 ui-open:bg-primary ui-open:text-chalkboard-10',
}}
/>
<Popover.Panel
as="ul"
className="absolute z-20 left-1/2 -translate-x-1/2 top-full mt-1 w-fit max-h-[80vh] overflow-y-auto py-2 flex flex-col gap-1 align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
>
{splitMenuItems.map((item) => (
<li className="contents" key={item.label}>
<button
onClick={item.onClick}
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
disabled={item.disabled}
>
<span className="capitalize">{item.label}</span>
{item.shortcut && (
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{item.shortcut}
</kbd>
)}
</button>
</li>
))}
</Popover.Panel>
</Popover>
)
}

View File

@ -29,10 +29,8 @@ export const ActionIcon = ({
size = 'md', size = 'md',
children, children,
}: ActionIconProps) => { }: ActionIconProps) => {
// By default, we reverse the icon color and background color in dark mode const computedIconClassName = `h-auto text-inherit dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}` const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
return ( return (
<div <div

View File

@ -175,7 +175,7 @@ function ReviewingButton() {
type="submit" type="submit"
form="review-form" form="review-form"
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow" className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
icon={{ iconStart={{
icon: 'checkmark', icon: 'checkmark',
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110', bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
iconClassName: '!text-chalkboard-10', iconClassName: '!text-chalkboard-10',
@ -193,7 +193,7 @@ function GatheringArgsButton() {
type="submit" type="submit"
form="arg-form" form="arg-form"
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow" className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
icon={{ iconStart={{
icon: 'arrowRight', icon: 'arrowRight',
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110', bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
iconClassName: '!text-chalkboard-10', iconClassName: '!text-chalkboard-10',

View File

@ -71,6 +71,16 @@ const CustomIconMap = {
/> />
</svg> </svg>
), ),
caretDown: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 11.2929L6.35346 7.64642L5.64636 8.35354L9.64648 12.3536L10.3536 12.3536L14.3535 8.35353L13.6464 7.64643L10 11.2929Z"
fill="currentColor"
/>
</svg>
),
clipboardCheckmark: ( clipboardCheckmark: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
@ -101,6 +111,16 @@ const CustomIconMap = {
/> />
</svg> </svg>
), ),
dimension: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.6455 3.6455L14 2V5.291L5.291 14H2L6 18V14.7052L14.7052 6H18L16.3526 4.35261L16.3546 4.35065L15.6475 3.64354L15.6455 3.6455Z"
fill="currentColor"
/>
</svg>
),
equal: ( equal: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path

View File

@ -25,7 +25,7 @@ const DownloadAppBanner = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => setIsBannerDismissed(true)} onClick={() => setIsBannerDismissed(true)}
icon={{ iconStart={{
icon: 'close', icon: 'close',
className: 'p-1', className: 'p-1',
bgClassName: bgClassName:

View File

@ -29,7 +29,7 @@ export const ErrorPage = () => {
<ActionButton <ActionButton
Element="link" Element="link"
to={'/'} to={'/'}
icon={{ icon: faHome }} iconStart={{ icon: faHome }}
data-testid="unexpected-error-home" data-testid="unexpected-error-home"
> >
Go Home Go Home
@ -37,14 +37,14 @@ export const ErrorPage = () => {
)} )}
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ icon: faRefresh }} iconStart={{ icon: faRefresh }}
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
> >
Reload Reload
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ icon: faTrash }} iconStart={{ icon: faTrash }}
onClick={() => { onClick={() => {
window.localStorage.clear() window.localStorage.clear()
}} }}
@ -53,7 +53,7 @@ export const ErrorPage = () => {
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
icon={{ icon: faBug }} iconStart={{ icon: faBug }}
to="https://github.com/KittyCAD/modeling-app/issues/new" to="https://github.com/KittyCAD/modeling-app/issues/new"
> >
Report Bug Report Bug

View File

@ -109,7 +109,7 @@ function DeleteConfirmationDialog({
send({ type: 'Delete file', data: fileOrDir }) send({ type: 'Delete file', data: fileOrDir })
setIsOpen(false) setIsOpen(false)
}} }}
icon={{ iconStart={{
icon: faTrashAlt, icon: faTrashAlt,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
iconClassName: iconClassName:
@ -340,7 +340,7 @@ export const FileTreeMenu = () => {
<> <>
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ iconStart={{
icon: 'filePlus', icon: 'filePlus',
iconClassName: '!text-current', iconClassName: '!text-current',
bgClassName: 'bg-transparent', bgClassName: 'bg-transparent',
@ -355,7 +355,7 @@ export const FileTreeMenu = () => {
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ iconStart={{
icon: 'folderPlus', icon: 'folderPlus',
iconClassName: '!text-current', iconClassName: '!text-current',
bgClassName: 'bg-transparent', bgClassName: 'bg-transparent',

View File

@ -85,7 +85,7 @@ function ProjectCard({
<ActionButton <ActionButton
Element="button" Element="button"
type="submit" type="submit"
icon={{ iconStart={{
icon: faCheck, icon: faCheck,
size: 'sm', size: 'sm',
className: 'p-1', className: 'p-1',
@ -99,7 +99,7 @@ function ProjectCard({
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ iconStart={{
icon: faX, icon: faX,
size: 'sm', size: 'sm',
iconClassName: 'dark:!text-chalkboard-20', iconClassName: 'dark:!text-chalkboard-20',
@ -141,7 +141,7 @@ function ProjectCard({
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"> <div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ iconStart={{
icon: faPenAlt, icon: faPenAlt,
className: 'p-1', className: 'p-1',
iconClassName: 'dark:!text-chalkboard-20', iconClassName: 'dark:!text-chalkboard-20',
@ -161,7 +161,7 @@ function ProjectCard({
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ iconStart={{
icon: faTrashAlt, icon: faTrashAlt,
className: 'p-1', className: 'p-1',
size: 'xs', size: 'xs',
@ -207,7 +207,7 @@ function ProjectCard({
await handleDeleteProject(project) await handleDeleteProject(project)
setIsConfirmingDelete(false) setIsConfirmingDelete(false)
}} }}
icon={{ iconStart={{
icon: faTrashAlt, icon: faTrashAlt,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
className: 'p-1', className: 'p-1',

View File

@ -165,7 +165,7 @@ function ProjectMenuPopover({
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90"> <div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ icon: 'exportFile', className: 'p-1' }} iconStart={{ icon: 'exportFile', className: 'p-1' }}
className="border-transparent dark:border-transparent" className="border-transparent dark:border-transparent"
disabled={!findCommand(exportCommandInfo)} disabled={!findCommand(exportCommandInfo)}
onClick={() => onClick={() =>
@ -185,7 +185,7 @@ function ProjectMenuPopover({
// Clear the scene and end the session. // Clear the scene and end the session.
engineCommandManager.endSession() engineCommandManager.endSession()
}} }}
icon={{ iconStart={{
icon: 'arrowLeft', icon: 'arrowLeft',
className: 'p-1', className: 'p-1',
}} }}

View File

@ -78,7 +78,7 @@ export const SetVarNameModal = ({
Element="button" Element="button"
type="submit" type="submit"
disabled={!isNewVariableNameUnique} disabled={!isNewVariableNameUnique}
icon={{ icon: faPlus }} iconStart={{ icon: faPlus }}
> >
Add variable Add variable
</ActionButton> </ActionButton>

View File

@ -59,7 +59,7 @@ export const UpdaterModal = ({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onResolve({ wantUpdate: false })} onClick={() => onResolve({ wantUpdate: false })}
icon={{ iconStart={{
icon: 'close', icon: 'close',
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10', iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
@ -72,7 +72,10 @@ export const UpdaterModal = ({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onResolve({ wantUpdate: true })} onClick={() => onResolve({ wantUpdate: true })}
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }} iconStart={{
icon: 'arrowRight',
bgClassName: 'dark:bg-chalkboard-80',
}}
className="dark:hover:bg-chalkboard-80/50" className="dark:hover:bg-chalkboard-80/50"
data-testid="update-button-update" data-testid="update-button-update"
> >

View File

@ -31,7 +31,7 @@ export const UpdaterRestartModal = ({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onResolve({ wantRestart: false })} onClick={() => onResolve({ wantRestart: false })}
icon={{ iconStart={{
icon: 'close', icon: 'close',
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10', iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
@ -44,7 +44,10 @@ export const UpdaterRestartModal = ({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onResolve({ wantRestart: true })} onClick={() => onResolve({ wantRestart: true })}
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }} iconStart={{
icon: 'arrowRight',
bgClassName: 'dark:bg-chalkboard-80',
}}
className="dark:hover:bg-chalkboard-80/50" className="dark:hover:bg-chalkboard-80/50"
data-testid="update-restrart-button-update" data-testid="update-restrart-button-update"
> >

View File

@ -55,7 +55,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
) : ( ) : (
<ActionButton <ActionButton
Element={Popover.Button} Element={Popover.Button}
icon={{ icon: 'menu' }} iconStart={{ icon: 'menu' }}
className="border-transparent !px-0" className="border-transparent !px-0"
data-testid="user-sidebar-toggle" data-testid="user-sidebar-toggle"
> >
@ -120,7 +120,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<div className="p-4 flex flex-col gap-2"> <div className="p-4 flex flex-col gap-2">
<ActionButton <ActionButton
Element="button" Element="button"
icon={{ icon: 'settings' }} iconStart={{ icon: 'settings' }}
className="border-transparent dark:border-transparent hover:bg-transparent" className="border-transparent dark:border-transparent hover:bg-transparent"
onClick={() => { onClick={() => {
// since /settings is a nested route the sidebar doesn't close // since /settings is a nested route the sidebar doesn't close
@ -138,7 +138,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/discussions" to="https://github.com/KittyCAD/modeling-app/discussions"
icon={{ icon: faGithub, className: 'p-1', size: 'sm' }} iconStart={{ icon: faGithub, className: 'p-1', size: 'sm' }}
className="border-transparent dark:border-transparent" className="border-transparent dark:border-transparent"
> >
Request a feature Request a feature
@ -146,7 +146,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/issues/new/choose" to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
icon={{ icon: faBug, className: 'p-1', size: 'sm' }} iconStart={{ icon: faBug, className: 'p-1', size: 'sm' }}
className="border-transparent dark:border-transparent" className="border-transparent dark:border-transparent"
> >
Report a bug Report a bug
@ -154,7 +154,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => send('Log out')} onClick={() => send('Log out')}
icon={{ iconStart={{
icon: faSignOutAlt, icon: faSignOutAlt,
className: 'p-1', className: 'p-1',
bgClassName: '!bg-transparent', bgClassName: '!bg-transparent',

View File

@ -24,7 +24,7 @@ export function WasmErrBanner() {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => setBannerDismissed(true)} onClick={() => setBannerDismissed(true)}
icon={{ iconStart={{
icon: 'close', icon: 'close',
className: 'p-1', className: 'p-1',
bgClassName: bgClassName:

View File

@ -210,7 +210,7 @@ const Home = () => {
: '') : '')
} }
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))} onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
icon={{ iconStart={{
icon: getSortIcon(sort, 'name'), icon: getSortIcon(sort, 'name'),
className: 'p-1.5', className: 'p-1.5',
iconClassName: !sort.includes('name') iconClassName: !sort.includes('name')
@ -232,7 +232,7 @@ const Home = () => {
onClick={() => onClick={() =>
setSearchParams(getNextSearchParams(sort, 'modified')) setSearchParams(getNextSearchParams(sort, 'modified'))
} }
icon={{ iconStart={{
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown, icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
className: 'p-1.5', className: 'p-1.5',
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '', iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
@ -278,7 +278,7 @@ const Home = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => send('Create project')} onClick={() => send('Create project')}
icon={{ icon: faPlus, iconClassName: 'p-1 w-4' }} iconStart={{ icon: faPlus, iconClassName: 'p-1 w-4' }}
data-testid="home-new-file" data-testid="home-new-file"
> >
New project New project

View File

@ -51,7 +51,7 @@ export default function Units() {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={dismiss} onClick={dismiss}
icon={{ iconStart={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
iconClassName: iconClassName:
@ -64,7 +64,7 @@ export default function Units() {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={next} onClick={next}
icon={{ icon: faArrowRight }} iconStart={{ icon: faArrowRight }}
> >
Next: Camera Next: Camera
</ActionButton> </ActionButton>

View File

@ -144,7 +144,7 @@ export function OnboardingButtons({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={dismiss} onClick={dismiss}
icon={{ iconStart={{
icon: 'close', icon: 'close',
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10', iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
@ -161,7 +161,7 @@ export function OnboardingButtons({
<ActionButton <ActionButton
Element="button" Element="button"
onClick={next} onClick={next}
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }} iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
className="dark:hover:bg-chalkboard-80/50" className="dark:hover:bg-chalkboard-80/50"
data-testid="onboarding-next" data-testid="onboarding-next"
> >

View File

@ -269,7 +269,7 @@ export const Settings = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={restartOnboarding} onClick={restartOnboarding}
icon={{ iconStart={{
icon: 'refresh', icon: 'refresh',
size: 'sm', size: 'sm',
className: 'p-1', className: 'p-1',
@ -300,7 +300,7 @@ export const Settings = () => {
) )
showInFolder(paths[searchParamTab]) showInFolder(paths[searchParamTab])
}} }}
icon={{ iconStart={{
icon: 'folder', icon: 'folder',
size: 'sm', size: 'sm',
className: 'p-1', className: 'p-1',
@ -319,7 +319,7 @@ export const Settings = () => {
}) })
toast.success('Settings restored to default') toast.success('Settings restored to default')
}} }}
icon={{ iconStart={{
icon: 'refresh', icon: 'refresh',
size: 'sm', size: 'sm',
className: 'p-1 text-chalkboard-10', className: 'p-1 text-chalkboard-10',

View File

@ -65,7 +65,7 @@ const SignIn = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={signInTauri} onClick={signInTauri}
icon={{ icon: 'arrowRight' }} iconStart={{ icon: 'arrowRight' }}
className="w-fit mt-4" className="w-fit mt-4"
data-testid="sign-in-button" data-testid="sign-in-button"
> >
@ -80,7 +80,7 @@ const SignIn = () => {
typeof window !== 'undefined' && typeof window !== 'undefined' &&
window.location.href.replace('signin', '') window.location.href.replace('signin', '')
)}`} )}`}
icon={{ icon: 'arrowRight' }} iconStart={{ icon: 'arrowRight' }}
className="w-fit mt-4" className="w-fit mt-4"
> >
Sign in Sign in