Add a component for toolbar buttons with a dropdown, consolidate Constrain actions under one (#2327)
@ -131,6 +131,7 @@ test('Basic sketch', async ({ page }) => {
|
||||
// selected two lines therefore there should be two cursors
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
|
||||
await page.getByRole('button', { name: 'Constrain' }).click()
|
||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||
|
||||
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
|
||||
await topHorzSegmentClick()
|
||||
await page.keyboard.down('Shift')
|
||||
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await xAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
|
||||
@ -951,12 +955,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await page.waitForTimeout(100)
|
||||
// same selection but click the axis first
|
||||
await xAxisClick()
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await topHorzSegmentClick()
|
||||
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
|
||||
// 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
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
await page.keyboard.down('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).toBeDisabled()
|
||||
await page.waitForTimeout(100)
|
||||
await xAxisClick()
|
||||
await page.keyboard.up('Shift')
|
||||
await constrainButton.click()
|
||||
await expect(absYButton).not.toBeDisabled()
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
@ -1902,6 +1910,7 @@ test('Can code mod a line length', async ({ page }) => {
|
||||
const startXPx = 500
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
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.getByText('Add constraining value').click()
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
@ -11,17 +11,18 @@ import {
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { useStore } from 'useStore'
|
||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||
|
||||
export const Toolbar = () => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
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 =
|
||||
'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 =
|
||||
'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(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||
return false
|
||||
@ -57,10 +58,7 @@ export const Toolbar = () => {
|
||||
{...props}
|
||||
ref={toolbarButtonsRef}
|
||||
onWheel={handleToolbarButtonsWheelEvent}
|
||||
className={
|
||||
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
|
||||
className
|
||||
}
|
||||
className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
|
||||
style={{ scrollbarWidth: 'thin' }}
|
||||
>
|
||||
{state.nextEvents.includes('Enter sketch') && (
|
||||
@ -71,7 +69,7 @@ export const Toolbar = () => {
|
||||
onClick={() =>
|
||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||
}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -88,7 +86,7 @@ export const Toolbar = () => {
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -105,7 +103,7 @@ export const Toolbar = () => {
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Cancel' })}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'arrowLeft',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -128,7 +126,7 @@ export const Toolbar = () => {
|
||||
: send('Equip Line tool')
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'line',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -148,7 +146,7 @@ export const Toolbar = () => {
|
||||
: send('Equip tangential arc to')
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'arc',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -172,7 +170,7 @@ export const Toolbar = () => {
|
||||
: send('Equip rectangle tool')
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'rectangle',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -194,7 +192,13 @@ export const Toolbar = () => {
|
||||
</>
|
||||
)}
|
||||
{state.matches('Sketch.SketchIdle') &&
|
||||
state.nextEvents
|
||||
state.nextEvents.filter(
|
||||
(eventName) =>
|
||||
eventName.includes('Make segment') ||
|
||||
eventName.includes('Constrain')
|
||||
).length > 0 && (
|
||||
<ActionButtonDropdown
|
||||
splitMenuItems={state.nextEvents
|
||||
.filter(
|
||||
(eventName) =>
|
||||
eventName.includes('Make segment') ||
|
||||
@ -215,31 +219,27 @@ export const Toolbar = () => {
|
||||
}
|
||||
return 0
|
||||
})
|
||||
.map((eventName) => (
|
||||
<li className="contents" key={eventName}>
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
key={eventName}
|
||||
onClick={() => send(eventName)}
|
||||
disabled={
|
||||
.map((eventName) => ({
|
||||
label: eventName
|
||||
.replace('Make segment ', '')
|
||||
.replace('Constrain ', ''),
|
||||
onClick: () => send(eventName),
|
||||
disabled:
|
||||
!state.nextEvents
|
||||
.filter((event) => state.can(event as any))
|
||||
.includes(eventName) || disableAllButtons
|
||||
}
|
||||
title={eventName}
|
||||
icon={{
|
||||
icon: 'line',
|
||||
.includes(eventName) || disableAllButtons,
|
||||
}))}
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
iconStart={{
|
||||
icon: 'dimension',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
{eventName
|
||||
.replace('Make segment ', '')
|
||||
.replace('Constrain ', '')}
|
||||
</ActionButton>
|
||||
</li>
|
||||
))}
|
||||
Constrain
|
||||
</ActionButtonDropdown>
|
||||
)}
|
||||
{state.matches('idle') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
@ -257,7 +257,7 @@ export const Toolbar = () => {
|
||||
? 'extrude'
|
||||
: 'sketches need to be closed, or not already extruded'
|
||||
}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'extrude',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
@ -272,7 +272,7 @@ export const Toolbar = () => {
|
||||
}
|
||||
|
||||
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 />
|
||||
</menu>
|
||||
)
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||
import React from 'react'
|
||||
import React, { ForwardedRef, forwardRef } from 'react'
|
||||
import { paths } from 'lib/paths'
|
||||
import { Link } from 'react-router-dom'
|
||||
import type { LinkProps } from 'react-router-dom'
|
||||
|
||||
interface BaseActionButtonProps {
|
||||
icon?: ActionIconProps
|
||||
iconStart?: ActionIconProps
|
||||
iconEnd?: ActionIconProps
|
||||
className?: string
|
||||
}
|
||||
|
||||
@ -32,15 +33,15 @@ type ActionButtonAsElement = BaseActionButtonProps &
|
||||
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
|
||||
}
|
||||
|
||||
type ActionButtonProps =
|
||||
export type ActionButtonProps =
|
||||
| ActionButtonAsButton
|
||||
| ActionButtonAsLink
|
||||
| ActionButtonAsExternal
|
||||
| ActionButtonAsElement
|
||||
|
||||
export const ActionButton = (props: ActionButtonProps) => {
|
||||
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'
|
||||
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
|
||||
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.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
|
||||
} ${props.className ? props.className : ''}`
|
||||
|
||||
switch (props.Element) {
|
||||
@ -48,11 +49,23 @@ export const ActionButton = (props: ActionButtonProps) => {
|
||||
// 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: _className, ...rest } = props
|
||||
const {
|
||||
Element,
|
||||
iconStart,
|
||||
iconEnd,
|
||||
children,
|
||||
className: _className,
|
||||
...rest
|
||||
} = props
|
||||
return (
|
||||
<button className={classNames} {...rest}>
|
||||
{props.icon && <ActionIcon {...icon} />}
|
||||
<button
|
||||
ref={ref as ForwardedRef<HTMLButtonElement>}
|
||||
className={classNames}
|
||||
{...rest}
|
||||
>
|
||||
{iconStart && <ActionIcon {...iconStart} />}
|
||||
{children}
|
||||
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -60,15 +73,22 @@ export const ActionButton = (props: ActionButtonProps) => {
|
||||
const {
|
||||
Element,
|
||||
to,
|
||||
icon,
|
||||
iconStart,
|
||||
iconEnd,
|
||||
children,
|
||||
className: _className,
|
||||
...rest
|
||||
} = props
|
||||
return (
|
||||
<Link to={to || paths.INDEX} className={classNames} {...rest}>
|
||||
{icon && <ActionIcon {...icon} />}
|
||||
<Link
|
||||
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||
to={to || paths.INDEX}
|
||||
className={classNames}
|
||||
{...rest}
|
||||
>
|
||||
{iconStart && <ActionIcon {...iconStart} />}
|
||||
{children}
|
||||
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@ -76,33 +96,42 @@ export const ActionButton = (props: ActionButtonProps) => {
|
||||
const {
|
||||
Element,
|
||||
to,
|
||||
icon,
|
||||
iconStart,
|
||||
iconEnd,
|
||||
children,
|
||||
className: _className,
|
||||
...rest
|
||||
} = props
|
||||
return (
|
||||
<Link
|
||||
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||
to={to || paths.INDEX}
|
||||
className={classNames}
|
||||
{...rest}
|
||||
target="_blank"
|
||||
>
|
||||
{icon && <ActionIcon {...icon} />}
|
||||
{iconStart && <ActionIcon {...iconStart} />}
|
||||
{children}
|
||||
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
const { Element, icon, children, className: _className, ...rest } = props
|
||||
if (!Element) throw new Error('Element is required')
|
||||
const {
|
||||
Element,
|
||||
iconStart,
|
||||
children,
|
||||
className: _className,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
return (
|
||||
<Element className={classNames} {...rest}>
|
||||
{props.icon && <ActionIcon {...props.icon} />}
|
||||
{props.iconStart && <ActionIcon {...props.iconStart} />}
|
||||
{children}
|
||||
{props.iconEnd && <ActionIcon {...props.iconEnd} />}
|
||||
</Element>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
55
src/components/ActionButtonDropdown.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -29,10 +29,8 @@ export const ActionIcon = ({
|
||||
size = 'md',
|
||||
children,
|
||||
}: ActionIconProps) => {
|
||||
// By default, we reverse the icon color and background color in dark mode
|
||||
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||
|
||||
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||
const computedIconClassName = `h-auto text-inherit 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}`
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -175,7 +175,7 @@ function ReviewingButton() {
|
||||
type="submit"
|
||||
form="review-form"
|
||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'checkmark',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
iconClassName: '!text-chalkboard-10',
|
||||
@ -193,7 +193,7 @@ function GatheringArgsButton() {
|
||||
type="submit"
|
||||
form="arg-form"
|
||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'arrowRight',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
iconClassName: '!text-chalkboard-10',
|
||||
|
@ -71,6 +71,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</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: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
@ -101,6 +111,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</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: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -25,7 +25,7 @@ const DownloadAppBanner = () => {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => setIsBannerDismissed(true)}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'close',
|
||||
className: 'p-1',
|
||||
bgClassName:
|
||||
|
@ -29,7 +29,7 @@ export const ErrorPage = () => {
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={'/'}
|
||||
icon={{ icon: faHome }}
|
||||
iconStart={{ icon: faHome }}
|
||||
data-testid="unexpected-error-home"
|
||||
>
|
||||
Go Home
|
||||
@ -37,14 +37,14 @@ export const ErrorPage = () => {
|
||||
)}
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: faRefresh }}
|
||||
iconStart={{ icon: faRefresh }}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: faTrash }}
|
||||
iconStart={{ icon: faTrash }}
|
||||
onClick={() => {
|
||||
window.localStorage.clear()
|
||||
}}
|
||||
@ -53,7 +53,7 @@ export const ErrorPage = () => {
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
icon={{ icon: faBug }}
|
||||
iconStart={{ icon: faBug }}
|
||||
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||
>
|
||||
Report Bug
|
||||
|
@ -109,7 +109,7 @@ function DeleteConfirmationDialog({
|
||||
send({ type: 'Delete file', data: fileOrDir })
|
||||
setIsOpen(false)
|
||||
}}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faTrashAlt,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName:
|
||||
@ -340,7 +340,7 @@ export const FileTreeMenu = () => {
|
||||
<>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'filePlus',
|
||||
iconClassName: '!text-current',
|
||||
bgClassName: 'bg-transparent',
|
||||
@ -355,7 +355,7 @@ export const FileTreeMenu = () => {
|
||||
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'folderPlus',
|
||||
iconClassName: '!text-current',
|
||||
bgClassName: 'bg-transparent',
|
||||
|
@ -85,7 +85,7 @@ function ProjectCard({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
type="submit"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faCheck,
|
||||
size: 'sm',
|
||||
className: 'p-1',
|
||||
@ -99,7 +99,7 @@ function ProjectCard({
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faX,
|
||||
size: 'sm',
|
||||
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">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faPenAlt,
|
||||
className: 'p-1',
|
||||
iconClassName: 'dark:!text-chalkboard-20',
|
||||
@ -161,7 +161,7 @@ function ProjectCard({
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faTrashAlt,
|
||||
className: 'p-1',
|
||||
size: 'xs',
|
||||
@ -207,7 +207,7 @@ function ProjectCard({
|
||||
await handleDeleteProject(project)
|
||||
setIsConfirmingDelete(false)
|
||||
}}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faTrashAlt,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
className: 'p-1',
|
||||
|
@ -165,7 +165,7 @@ function ProjectMenuPopover({
|
||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: 'exportFile', className: 'p-1' }}
|
||||
iconStart={{ icon: 'exportFile', className: 'p-1' }}
|
||||
className="border-transparent dark:border-transparent"
|
||||
disabled={!findCommand(exportCommandInfo)}
|
||||
onClick={() =>
|
||||
@ -185,7 +185,7 @@ function ProjectMenuPopover({
|
||||
// Clear the scene and end the session.
|
||||
engineCommandManager.endSession()
|
||||
}}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'arrowLeft',
|
||||
className: 'p-1',
|
||||
}}
|
||||
|
@ -78,7 +78,7 @@ export const SetVarNameModal = ({
|
||||
Element="button"
|
||||
type="submit"
|
||||
disabled={!isNewVariableNameUnique}
|
||||
icon={{ icon: faPlus }}
|
||||
iconStart={{ icon: faPlus }}
|
||||
>
|
||||
Add variable
|
||||
</ActionButton>
|
||||
|
@ -59,7 +59,7 @@ export const UpdaterModal = ({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => onResolve({ wantUpdate: false })}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'close',
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||
@ -72,7 +72,10 @@ export const UpdaterModal = ({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
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"
|
||||
data-testid="update-button-update"
|
||||
>
|
||||
|
@ -31,7 +31,7 @@ export const UpdaterRestartModal = ({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => onResolve({ wantRestart: false })}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'close',
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||
@ -44,7 +44,10 @@ export const UpdaterRestartModal = ({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
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"
|
||||
data-testid="update-restrart-button-update"
|
||||
>
|
||||
|
@ -55,7 +55,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
) : (
|
||||
<ActionButton
|
||||
Element={Popover.Button}
|
||||
icon={{ icon: 'menu' }}
|
||||
iconStart={{ icon: 'menu' }}
|
||||
className="border-transparent !px-0"
|
||||
data-testid="user-sidebar-toggle"
|
||||
>
|
||||
@ -120,7 +120,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: 'settings' }}
|
||||
iconStart={{ icon: 'settings' }}
|
||||
className="border-transparent dark:border-transparent hover:bg-transparent"
|
||||
onClick={() => {
|
||||
// since /settings is a nested route the sidebar doesn't close
|
||||
@ -138,7 +138,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
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"
|
||||
>
|
||||
Request a feature
|
||||
@ -146,7 +146,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
<ActionButton
|
||||
Element="externalLink"
|
||||
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"
|
||||
>
|
||||
Report a bug
|
||||
@ -154,7 +154,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => send('Log out')}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faSignOutAlt,
|
||||
className: 'p-1',
|
||||
bgClassName: '!bg-transparent',
|
||||
|
@ -24,7 +24,7 @@ export function WasmErrBanner() {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => setBannerDismissed(true)}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'close',
|
||||
className: 'p-1',
|
||||
bgClassName:
|
||||
|
@ -210,7 +210,7 @@ const Home = () => {
|
||||
: '')
|
||||
}
|
||||
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: getSortIcon(sort, 'name'),
|
||||
className: 'p-1.5',
|
||||
iconClassName: !sort.includes('name')
|
||||
@ -232,7 +232,7 @@ const Home = () => {
|
||||
onClick={() =>
|
||||
setSearchParams(getNextSearchParams(sort, 'modified'))
|
||||
}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
|
||||
className: 'p-1.5',
|
||||
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
|
||||
@ -278,7 +278,7 @@ const Home = () => {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
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"
|
||||
>
|
||||
New project
|
||||
|
@ -51,7 +51,7 @@ export default function Units() {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={dismiss}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: faXmark,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName:
|
||||
@ -64,7 +64,7 @@ export default function Units() {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={next}
|
||||
icon={{ icon: faArrowRight }}
|
||||
iconStart={{ icon: faArrowRight }}
|
||||
>
|
||||
Next: Camera
|
||||
</ActionButton>
|
||||
|
@ -144,7 +144,7 @@ export function OnboardingButtons({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={dismiss}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'close',
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||
@ -161,7 +161,7 @@ export function OnboardingButtons({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
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"
|
||||
data-testid="onboarding-next"
|
||||
>
|
||||
|
@ -269,7 +269,7 @@ export const Settings = () => {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={restartOnboarding}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'refresh',
|
||||
size: 'sm',
|
||||
className: 'p-1',
|
||||
@ -300,7 +300,7 @@ export const Settings = () => {
|
||||
)
|
||||
showInFolder(paths[searchParamTab])
|
||||
}}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'folder',
|
||||
size: 'sm',
|
||||
className: 'p-1',
|
||||
@ -319,7 +319,7 @@ export const Settings = () => {
|
||||
})
|
||||
toast.success('Settings restored to default')
|
||||
}}
|
||||
icon={{
|
||||
iconStart={{
|
||||
icon: 'refresh',
|
||||
size: 'sm',
|
||||
className: 'p-1 text-chalkboard-10',
|
||||
|
@ -65,7 +65,7 @@ const SignIn = () => {
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={signInTauri}
|
||||
icon={{ icon: 'arrowRight' }}
|
||||
iconStart={{ icon: 'arrowRight' }}
|
||||
className="w-fit mt-4"
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
@ -80,7 +80,7 @@ const SignIn = () => {
|
||||
typeof window !== 'undefined' &&
|
||||
window.location.href.replace('signin', '')
|
||||
)}`}
|
||||
icon={{ icon: 'arrowRight' }}
|
||||
iconStart={{ icon: 'arrowRight' }}
|
||||
className="w-fit mt-4"
|
||||
>
|
||||
Sign in
|
||||
|