Compare commits
8 Commits
jtran/upda
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
727a7e87f1 | |||
00b779a767 | |||
124ecc11ba | |||
ea330c3b2e | |||
dfe7a32f1e | |||
3600354c13 | |||
1f705ca6df | |||
5a1b0777b7 |
@ -43,6 +43,7 @@ import {
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||
import { ActionSidebar } from './components/ModelingSidebar/ActionSidebar'
|
||||
|
||||
// CYCLIC REF
|
||||
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
||||
@ -168,7 +169,10 @@ export function App() {
|
||||
enableMenu={true}
|
||||
/>
|
||||
<ModalContainer />
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<div className="relative flex-1 flex flex-col">
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<ActionSidebar />
|
||||
</div>
|
||||
<EngineStream pool={pool} authToken={authToken} />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
|
@ -127,7 +127,7 @@ export const CreateNewVariable = ({
|
||||
autoFocus={true}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
className={`font-mono flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
|
||||
className={`flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
|
||||
!shouldCreateVariable ? 'opacity-50' : ''
|
||||
}`}
|
||||
value={newVariableName}
|
||||
|
@ -628,8 +628,8 @@ const CustomIconMap = {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13.8123 17.3904L16.3123 15.3904L15.6877 14.6096L14 15.9597V12H13V15.9597L11.3123 14.6096L10.6877 15.3904L13.1877 17.3904L13.5 17.6403L13.8123 17.3904Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
|
140
src/components/ModelingSidebar/ActionSidebar.tsx
Normal file
140
src/components/ModelingSidebar/ActionSidebar.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { isDesktop } from '@src/lib/isDesktop'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { SidebarAction } from './ModelingPanes'
|
||||
import { useAppState } from '@src/AppState'
|
||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
||||
import { useKclContext } from '@src/lang/KclProvider'
|
||||
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { MachineManagerContext } from '../MachineManagerProvider'
|
||||
import { useSettings } from '@src/machines/appMachine'
|
||||
import { getPlatformString } from '@src/lib/utils'
|
||||
import { ModelingPaneButton } from './ModelingSidebarButton'
|
||||
|
||||
export function ActionSidebar() {
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const kclContext = useKclContext()
|
||||
const settings = useSettings()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useAppState()
|
||||
const reliesOnEngine =
|
||||
(overallState !== NetworkHealthState.Ok &&
|
||||
overallState !== NetworkHealthState.Weak) ||
|
||||
isExecuting ||
|
||||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
||||
!isStreamReady
|
||||
const paneCallbackProps = useMemo(
|
||||
() => ({
|
||||
kclContext,
|
||||
settings,
|
||||
platform: getPlatformString(),
|
||||
}),
|
||||
[kclContext.diagnostics, settings]
|
||||
)
|
||||
|
||||
const sidebarActions: SidebarAction[] = [
|
||||
{
|
||||
id: 'load-external-model',
|
||||
title: 'Load external model',
|
||||
sidebarName: 'Load external model',
|
||||
icon: 'importFile',
|
||||
keybinding: 'Ctrl + Shift + I',
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'load-external-model', groupId: 'code' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'share-link',
|
||||
title: 'Create share link',
|
||||
sidebarName: 'Create share link',
|
||||
icon: 'link',
|
||||
keybinding: 'Mod + Alt + S',
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'share-file-link', groupId: 'code' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'export',
|
||||
title: 'Export part',
|
||||
sidebarName: 'Export part',
|
||||
icon: 'floppyDiskArrow',
|
||||
keybinding: 'Ctrl + Shift + E',
|
||||
disable: () =>
|
||||
reliesOnEngine ? 'Need engine connection to export' : undefined,
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Export', groupId: 'modeling' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'make',
|
||||
title: 'Make part',
|
||||
sidebarName: 'Make part',
|
||||
icon: 'printer3d',
|
||||
keybinding: 'Ctrl + Shift + M',
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
action: async () => {
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Make', groupId: 'modeling' },
|
||||
})
|
||||
},
|
||||
hide: () => !isDesktop(),
|
||||
disable: () => {
|
||||
return machineManager.noMachinesReason()
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const filteredActions = sidebarActions.filter(
|
||||
(action) =>
|
||||
!action.hide ||
|
||||
(action.hide instanceof Function && !action.hide(paneCallbackProps))
|
||||
)
|
||||
|
||||
return (
|
||||
<aside
|
||||
id="action-sidebar"
|
||||
className="absolute right-0 top-0 bottom-0 flex flex-col z-10 my-2"
|
||||
>
|
||||
<ul
|
||||
className={
|
||||
'relative rounded-l z-[2] pointer-events-auto p-0 col-start-1 col-span-1 h-fit w-fit flex flex-col ' +
|
||||
'bg-chalkboard-10 border border-solid border-chalkboard-30 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 shadow-sm '
|
||||
}
|
||||
>
|
||||
{filteredActions.length > 0 && (
|
||||
<>
|
||||
<ul id="sidebar-actions" className="w-fit p-2 flex flex-col gap-2">
|
||||
{filteredActions.map((action) => (
|
||||
<li className="contents">
|
||||
<ModelingPaneButton
|
||||
key={action.id}
|
||||
paneConfig={{
|
||||
id: action.id,
|
||||
sidebarName: action.sidebarName,
|
||||
icon: action.icon,
|
||||
keybinding: action.keybinding,
|
||||
iconClassName: action.iconClassName,
|
||||
iconSize: 'md',
|
||||
}}
|
||||
onClick={action.action}
|
||||
disabledText={action.disable?.()}
|
||||
tooltipPosition="left"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</aside>
|
||||
)
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
.header {
|
||||
@apply z-10 relative rounded-tr;
|
||||
@apply flex h-[41px] items-center justify-between gap-2 px-2;
|
||||
@apply font-mono text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply text-xs select-none text-chalkboard-90;
|
||||
@apply bg-chalkboard-10 border-b border-chalkboard-30;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
.button {
|
||||
@apply flex justify-between items-center gap-2 px-2 py-1 text-left border-none rounded-sm;
|
||||
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply !no-underline text-xs select-none text-chalkboard-90;
|
||||
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
||||
@apply transition-colors ease-out;
|
||||
@apply m-0;
|
||||
|
@ -1,48 +1,28 @@
|
||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Resizable } from 're-resizable'
|
||||
import type { MouseEventHandler } from 'react'
|
||||
import { useCallback, useContext, useEffect, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
import { useAppState } from '@src/AppState'
|
||||
import { ActionIcon } from '@src/components/ActionIcon'
|
||||
import type { CustomIconName } from '@src/components/CustomIcon'
|
||||
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
|
||||
import { ModelingPane } from '@src/components/ModelingSidebar/ModelingPane'
|
||||
import type {
|
||||
SidebarAction,
|
||||
SidebarType,
|
||||
} from '@src/components/ModelingSidebar/ModelingPanes'
|
||||
import type { SidebarType } from '@src/components/ModelingSidebar/ModelingPanes'
|
||||
import { sidebarPanes } from '@src/components/ModelingSidebar/ModelingPanes'
|
||||
import Tooltip from '@src/components/Tooltip'
|
||||
import { useModelingContext } from '@src/hooks/useModelingContext'
|
||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
||||
import { useKclContext } from '@src/lang/KclProvider'
|
||||
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
|
||||
import { isDesktop } from '@src/lib/isDesktop'
|
||||
import { useSettings } from '@src/machines/appMachine'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||
import { getPlatformString } from '@src/lib/utils'
|
||||
import { BadgeInfoComputed, ModelingPaneButton } from './ModelingSidebarButton'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
}
|
||||
|
||||
interface BadgeInfoComputed {
|
||||
value: number | boolean | string
|
||||
onClick?: MouseEventHandler<any>
|
||||
className?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
function getPlatformString(): 'web' | 'desktop' {
|
||||
return isDesktop() ? 'desktop' : 'web'
|
||||
}
|
||||
|
||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const kclContext = useKclContext()
|
||||
const settings = useSettings()
|
||||
const onboardingStatus = settings.app.onboardingStatus
|
||||
@ -54,16 +34,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
: 'pointer-events-auto '
|
||||
const showDebugPanel = settings.app.showDebugPanel
|
||||
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { isStreamReady } = useAppState()
|
||||
const reliesOnEngine =
|
||||
(overallState !== NetworkHealthState.Ok &&
|
||||
overallState !== NetworkHealthState.Weak) ||
|
||||
isExecuting ||
|
||||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
||||
!isStreamReady
|
||||
|
||||
const paneCallbackProps = useMemo(
|
||||
() => ({
|
||||
kclContext,
|
||||
@ -73,70 +43,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
[kclContext.diagnostics, settings]
|
||||
)
|
||||
|
||||
const sidebarActions: SidebarAction[] = [
|
||||
{
|
||||
id: 'load-external-model',
|
||||
title: 'Load external model',
|
||||
sidebarName: 'Load external model',
|
||||
icon: 'importFile',
|
||||
keybinding: 'Ctrl + Shift + I',
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'load-external-model', groupId: 'code' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'share-link',
|
||||
title: 'Create share link',
|
||||
sidebarName: 'Create share link',
|
||||
icon: 'link',
|
||||
keybinding: 'Mod + Alt + S',
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'share-file-link', groupId: 'code' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'export',
|
||||
title: 'Export part',
|
||||
sidebarName: 'Export part',
|
||||
icon: 'floppyDiskArrow',
|
||||
keybinding: 'Ctrl + Shift + E',
|
||||
disable: () =>
|
||||
reliesOnEngine ? 'Need engine connection to export' : undefined,
|
||||
action: () =>
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Export', groupId: 'modeling' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'make',
|
||||
title: 'Make part',
|
||||
sidebarName: 'Make part',
|
||||
icon: 'printer3d',
|
||||
keybinding: 'Ctrl + Shift + M',
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
action: async () => {
|
||||
commandBarActor.send({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Make', groupId: 'modeling' },
|
||||
})
|
||||
},
|
||||
hide: () => !isDesktop(),
|
||||
disable: () => {
|
||||
return machineManager.noMachinesReason()
|
||||
},
|
||||
},
|
||||
]
|
||||
const filteredActions: SidebarAction[] = sidebarActions.filter(
|
||||
(action) =>
|
||||
!action.hide ||
|
||||
(action.hide instanceof Function && !action.hide(paneCallbackProps))
|
||||
)
|
||||
|
||||
// // Filter out the debug panel if it's not supposed to be shown
|
||||
// // TODO: abstract out for allowing user to configure which panes to show
|
||||
const filteredPanes = useMemo(
|
||||
@ -257,31 +163,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{filteredActions.length > 0 && (
|
||||
<>
|
||||
<hr className="w-full border-chalkboard-30 dark:border-chalkboard-80" />
|
||||
<ul
|
||||
id="sidebar-actions"
|
||||
className="w-fit p-2 flex flex-col gap-2"
|
||||
>
|
||||
{filteredActions.map((action) => (
|
||||
<ModelingPaneButton
|
||||
key={action.id}
|
||||
paneConfig={{
|
||||
id: action.id,
|
||||
sidebarName: action.sidebarName,
|
||||
icon: action.icon,
|
||||
keybinding: action.keybinding,
|
||||
iconClassName: action.iconClassName,
|
||||
iconSize: 'md',
|
||||
}}
|
||||
onClick={action.action}
|
||||
disabledText={action.disable?.()}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<ul
|
||||
id="pane-section"
|
||||
@ -315,105 +196,3 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
</Resizable>
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelingPaneButtonProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
paneConfig: {
|
||||
id: string
|
||||
sidebarName: string
|
||||
icon: CustomIconName | IconDefinition
|
||||
keybinding: string
|
||||
iconClassName?: string
|
||||
iconSize?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
onClick: () => void
|
||||
paneIsOpen?: boolean
|
||||
showBadge?: BadgeInfoComputed
|
||||
disabledText?: string
|
||||
}
|
||||
|
||||
function ModelingPaneButton({
|
||||
paneConfig,
|
||||
onClick,
|
||||
paneIsOpen,
|
||||
showBadge,
|
||||
disabledText,
|
||||
...props
|
||||
}: ModelingPaneButtonProps) {
|
||||
useHotkeys(paneConfig.keybinding, onClick, {
|
||||
scopes: ['modeling'],
|
||||
})
|
||||
|
||||
return (
|
||||
<div id={paneConfig.id + '-button-holder'} className="relative">
|
||||
<button
|
||||
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||
onClick={onClick}
|
||||
name={paneConfig.sidebarName}
|
||||
data-testid={paneConfig.id + SIDEBAR_BUTTON_SUFFIX}
|
||||
disabled={disabledText !== undefined}
|
||||
aria-disabled={disabledText !== undefined}
|
||||
{...props}
|
||||
>
|
||||
<ActionIcon
|
||||
icon={paneConfig.icon}
|
||||
className={paneConfig.iconClassName || ''}
|
||||
size={paneConfig.iconSize || 'md'}
|
||||
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
|
||||
bgClassName={
|
||||
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
||||
}
|
||||
/>
|
||||
<span className="sr-only">
|
||||
{paneConfig.sidebarName}
|
||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||
</span>
|
||||
<Tooltip
|
||||
position="right"
|
||||
contentClassName="max-w-none flex items-center gap-4"
|
||||
hoverOnly
|
||||
>
|
||||
<span className="flex-1">
|
||||
{paneConfig.sidebarName}
|
||||
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||
</span>
|
||||
<kbd className="hotkey text-xs capitalize">
|
||||
{paneConfig.keybinding}
|
||||
</kbd>
|
||||
</Tooltip>
|
||||
</button>
|
||||
{!!showBadge?.value && (
|
||||
<p
|
||||
id={`${paneConfig.id}-badge`}
|
||||
className={
|
||||
showBadge.className
|
||||
? showBadge.className
|
||||
: 'absolute m-0 p-0 bottom-4 left-4 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||
}
|
||||
onClick={showBadge.onClick}
|
||||
title={
|
||||
showBadge.title
|
||||
? showBadge.title
|
||||
: `Click to view ${showBadge.value} notification${
|
||||
Number(showBadge.value) > 1 ? 's' : ''
|
||||
}`
|
||||
}
|
||||
>
|
||||
<span className="sr-only"> has </span>
|
||||
{typeof showBadge.value === 'number' ||
|
||||
typeof showBadge.value === 'string' ? (
|
||||
<span>{showBadge.value}</span>
|
||||
) : (
|
||||
<span className="sr-only">a</span>
|
||||
)}
|
||||
{typeof showBadge.value === 'number' && (
|
||||
<span className="sr-only">
|
||||
notification{Number(showBadge.value) > 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
118
src/components/ModelingSidebar/ModelingSidebarButton.tsx
Normal file
118
src/components/ModelingSidebar/ModelingSidebarButton.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { ActionIcon } from '../ActionIcon'
|
||||
import { CustomIconName } from '../CustomIcon'
|
||||
import Tooltip from '../Tooltip'
|
||||
import { MouseEventHandler } from 'react'
|
||||
|
||||
export interface BadgeInfoComputed {
|
||||
value: number | boolean | string
|
||||
onClick?: MouseEventHandler<any>
|
||||
className?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
interface ModelingPaneButtonProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
paneConfig: {
|
||||
id: string
|
||||
sidebarName: string
|
||||
icon: CustomIconName | IconDefinition
|
||||
keybinding: string
|
||||
iconClassName?: string
|
||||
iconSize?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
onClick: () => void
|
||||
paneIsOpen?: boolean
|
||||
showBadge?: BadgeInfoComputed
|
||||
disabledText?: string
|
||||
tooltipPosition?: 'right' | 'left'
|
||||
}
|
||||
|
||||
export function ModelingPaneButton({
|
||||
paneConfig,
|
||||
onClick,
|
||||
paneIsOpen,
|
||||
showBadge,
|
||||
disabledText,
|
||||
tooltipPosition = 'right',
|
||||
...props
|
||||
}: ModelingPaneButtonProps) {
|
||||
useHotkeys(paneConfig.keybinding, onClick, {
|
||||
scopes: ['modeling'],
|
||||
})
|
||||
|
||||
return (
|
||||
<div id={paneConfig.id + '-button-holder'} className="relative">
|
||||
<button
|
||||
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||
onClick={onClick}
|
||||
name={paneConfig.sidebarName}
|
||||
data-testid={paneConfig.id + SIDEBAR_BUTTON_SUFFIX}
|
||||
disabled={disabledText !== undefined}
|
||||
aria-disabled={disabledText !== undefined}
|
||||
{...props}
|
||||
>
|
||||
<ActionIcon
|
||||
icon={paneConfig.icon}
|
||||
className={paneConfig.iconClassName || ''}
|
||||
size={paneConfig.iconSize || 'md'}
|
||||
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
|
||||
bgClassName={
|
||||
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
||||
}
|
||||
/>
|
||||
<span className="sr-only">
|
||||
{paneConfig.sidebarName}
|
||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||
</span>
|
||||
<Tooltip
|
||||
position={tooltipPosition}
|
||||
contentClassName="max-w-none flex items-center gap-4"
|
||||
hoverOnly
|
||||
>
|
||||
<span className="flex-1">
|
||||
{paneConfig.sidebarName}
|
||||
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||
</span>
|
||||
<kbd className="hotkey text-xs capitalize">
|
||||
{paneConfig.keybinding}
|
||||
</kbd>
|
||||
</Tooltip>
|
||||
</button>
|
||||
{!!showBadge?.value && (
|
||||
<p
|
||||
id={`${paneConfig.id}-badge`}
|
||||
className={
|
||||
showBadge.className
|
||||
? showBadge.className
|
||||
: 'absolute m-0 p-0 bottom-4 left-4 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||
}
|
||||
onClick={showBadge.onClick}
|
||||
title={
|
||||
showBadge.title
|
||||
? showBadge.title
|
||||
: `Click to view ${showBadge.value} notification${
|
||||
Number(showBadge.value) > 1 ? 's' : ''
|
||||
}`
|
||||
}
|
||||
>
|
||||
<span className="sr-only"> has </span>
|
||||
{typeof showBadge.value === 'number' ||
|
||||
typeof showBadge.value === 'string' ? (
|
||||
<span>{showBadge.value}</span>
|
||||
) : (
|
||||
<span className="sr-only">a</span>
|
||||
)}
|
||||
{typeof showBadge.value === 'number' && (
|
||||
<span className="sr-only">
|
||||
notification{Number(showBadge.value) > 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -8,6 +8,13 @@ import type { AsyncFn } from '@src/lib/types'
|
||||
|
||||
export const uuidv4 = v4
|
||||
|
||||
/**
|
||||
* Get the current platform as a string.
|
||||
*/
|
||||
export function getPlatformString(): 'web' | 'desktop' {
|
||||
return isDesktop() ? 'desktop' : 'web'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all labels for a keyword call expression.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user