Merge pull request #108 from findthelorax/fix/modal-accessibility
fix: modal accessibility
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import modalStyles from './modal-interactive.module.css'
|
||||||
import { RotateCcw } from 'lucide-react'
|
import { RotateCcw } from 'lucide-react'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -68,7 +69,7 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Link Type</Label>
|
<Label className="text-xs text-muted-foreground">Link Type</Label>
|
||||||
<Select value={type} onValueChange={(v) => setType(v as EdgeType)}>
|
<Select value={type} onValueChange={(v) => setType(v as EdgeType)}>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Edge type selector">
|
||||||
<SelectValue>{EDGE_TYPE_LABELS[type]}</SelectValue>
|
<SelectValue>{EDGE_TYPE_LABELS[type]}</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -89,7 +90,7 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
value={vlanId}
|
value={vlanId}
|
||||||
onChange={(e) => setVlanId(e.target.value)}
|
onChange={(e) => setVlanId(e.target.value)}
|
||||||
placeholder="e.g. 20"
|
placeholder="e.g. 20"
|
||||||
className="bg-[#21262d] border-[#30363d] font-mono text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -100,19 +101,21 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
placeholder="e.g. 1G, trunk..."
|
placeholder="e.g. 1G, trunk..."
|
||||||
className="bg-[#21262d] border-[#30363d] text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Path Style</Label>
|
<Label className="text-xs text-muted-foreground">Path Style</Label>
|
||||||
<div className="flex rounded-md overflow-hidden border border-[#30363d]">
|
<div className={`flex rounded-md overflow-hidden border border-[#30363d] ${modalStyles['modal-interactive']}`}>
|
||||||
{(['bezier', 'smooth'] as EdgePathStyle[]).map((style) => (
|
{(['bezier', 'smooth'] as EdgePathStyle[]).map((style) => (
|
||||||
<button
|
<button
|
||||||
key={style}
|
key={style}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setPathStyle(style)}
|
onClick={() => setPathStyle(style)}
|
||||||
className="flex-1 py-1 text-xs capitalize transition-colors"
|
className="flex-1 py-1 text-xs capitalize transition-colors cursor-pointer"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`Path style ${style}`}
|
||||||
style={{
|
style={{
|
||||||
background: pathStyle === style ? '#00d4ff22' : '#21262d',
|
background: pathStyle === style ? '#00d4ff22' : '#21262d',
|
||||||
color: pathStyle === style ? '#00d4ff' : '#8b949e',
|
color: pathStyle === style ? '#00d4ff' : '#8b949e',
|
||||||
@@ -127,13 +130,15 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Animation</Label>
|
<Label className="text-xs text-muted-foreground">Animation</Label>
|
||||||
<div className="flex rounded-md overflow-hidden border border-[#30363d]">
|
<div className={`flex rounded-md overflow-hidden border border-[#30363d] ${modalStyles['modal-interactive']}`}>
|
||||||
{(['none', 'basic', 'snake', 'flow'] as AnimMode[]).map((mode, i) => (
|
{(['none', 'basic', 'snake', 'flow'] as AnimMode[]).map((mode, i) => (
|
||||||
<button
|
<button
|
||||||
key={mode}
|
key={mode}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAnimation(mode)}
|
onClick={() => setAnimation(mode)}
|
||||||
className="flex-1 py-1 text-xs capitalize transition-colors"
|
className="flex-1 py-1 text-xs capitalize transition-colors cursor-pointer"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`Animation mode ${mode}`}
|
||||||
style={{
|
style={{
|
||||||
background: animation === mode ? '#00d4ff22' : '#21262d',
|
background: animation === mode ? '#00d4ff22' : '#21262d',
|
||||||
color: animation === mode ? '#00d4ff' : '#8b949e',
|
color: animation === mode ? '#00d4ff' : '#8b949e',
|
||||||
@@ -160,8 +165,10 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<label
|
<label
|
||||||
className="relative flex items-center gap-2.5 px-2.5 h-8 rounded-md border cursor-pointer"
|
className={`relative flex items-center gap-2.5 px-2.5 h-8 rounded-md border cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{ borderColor: customColor ? effectiveColor : '#30363d', background: '#21262d' }}
|
style={{ borderColor: customColor ? effectiveColor : '#30363d', background: '#21262d' }}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Edge color picker"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@@ -189,13 +196,13 @@ export function EdgeModal({ open, onClose, onSubmit, onDelete, onClearWaypoints,
|
|||||||
|
|
||||||
<div className="flex justify-between gap-2 pt-1">
|
<div className="flex justify-between gap-2 pt-1">
|
||||||
{onDelete ? (
|
{onDelete ? (
|
||||||
<Button type="button" variant="ghost" size="sm" className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10" onClick={handleDelete}>
|
<Button type="button" variant="ghost" size="sm" className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10 cursor-pointer" onClick={handleDelete}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
) : <span />}
|
) : <span />}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button type="button" variant="ghost" size="sm" onClick={onClose}>Cancel</Button>
|
<Button type="button" variant="ghost" size="sm" className="cursor-pointer" onClick={onClose}>Cancel</Button>
|
||||||
<Button type="submit" size="sm" className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90">
|
<Button type="submit" size="sm" className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90 cursor-pointer">
|
||||||
{onDelete ? 'Save' : 'Connect'}
|
{onDelete ? 'Save' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import modalStyles from './modal-interactive.module.css'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -129,7 +130,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
value={form.label}
|
value={form.label}
|
||||||
onChange={(e) => set('label', e.target.value)}
|
onChange={(e) => set('label', e.target.value)}
|
||||||
placeholder="Zone name…"
|
placeholder="Zone name…"
|
||||||
className="bg-[#21262d] border-[#30363d] text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Font</Label>
|
<Label className="text-xs text-muted-foreground">Font</Label>
|
||||||
<Select value={form.font} onValueChange={(v: string | null) => set('font', v ?? 'inter')}>
|
<Select value={form.font} onValueChange={(v: string | null) => set('font', v ?? 'inter')}>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`}>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -162,7 +163,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
type="button"
|
type="button"
|
||||||
title={value}
|
title={value}
|
||||||
onClick={() => set('text_position', value)}
|
onClick={() => set('text_position', value)}
|
||||||
className="h-8 rounded text-base transition-colors"
|
className={`h-8 rounded text-base transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : '#21262d',
|
background: isSelected ? '#00d4ff22' : '#21262d',
|
||||||
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
||||||
@@ -187,7 +188,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
key={value}
|
key={value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => set('label_position', value)}
|
onClick={() => set('label_position', value)}
|
||||||
className="flex items-center justify-center h-8 rounded text-xs transition-colors"
|
className={`flex items-center justify-center h-8 rounded text-xs transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : '#21262d',
|
background: isSelected ? '#00d4ff22' : '#21262d',
|
||||||
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
||||||
@@ -248,7 +249,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
key={value}
|
key={value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => set('text_size', value)}
|
onClick={() => set('text_size', value)}
|
||||||
className="flex items-center justify-center h-8 rounded transition-colors"
|
className={`flex items-center justify-center h-8 rounded transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : '#21262d',
|
background: isSelected ? '#00d4ff22' : '#21262d',
|
||||||
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
||||||
@@ -275,7 +276,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
type="button"
|
type="button"
|
||||||
title={label}
|
title={label}
|
||||||
onClick={() => set('border_style', value)}
|
onClick={() => set('border_style', value)}
|
||||||
className="flex flex-col items-center justify-center h-10 rounded text-xs gap-0.5 transition-colors"
|
className={`flex flex-col items-center justify-center h-10 rounded text-xs gap-0.5 transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : '#21262d',
|
background: isSelected ? '#00d4ff22' : '#21262d',
|
||||||
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
||||||
@@ -301,7 +302,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
key={value}
|
key={value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => set('border_width', value)}
|
onClick={() => set('border_width', value)}
|
||||||
className="flex items-center justify-center h-8 rounded text-xs transition-colors"
|
className={`flex items-center justify-center h-8 rounded text-xs transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : '#21262d',
|
background: isSelected ? '#00d4ff22' : '#21262d',
|
||||||
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
border: `1px solid ${isSelected ? '#00d4ff88' : '#30363d'}`,
|
||||||
@@ -319,7 +320,7 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Z-Order (1 = furthest back)</Label>
|
<Label className="text-xs text-muted-foreground">Z-Order (1 = furthest back)</Label>
|
||||||
<Select value={String(form.z_order)} onValueChange={(v: string | null) => set('z_order', v !== null ? Number(v) : 1)}>
|
<Select value={String(form.z_order)} onValueChange={(v: string | null) => set('z_order', v !== null ? Number(v) : 1)}>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']}`}>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -338,17 +339,17 @@ export function GroupRectModal({ open, onClose, onSubmit, onDelete, initial, tit
|
|||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10"
|
className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10 cursor-pointer"
|
||||||
onClick={() => { onDelete(); onClose() }}
|
onClick={() => { onDelete(); onClose() }}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2 ml-auto">
|
<div className="flex gap-2 ml-auto">
|
||||||
<Button type="button" variant="ghost" size="sm" onClick={onClose}>
|
<Button type="button" variant="ghost" size="sm" className={`cursor-pointer ${modalStyles['modal-cancel-hover']}`} onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" size="sm" className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90">
|
<Button type="submit" size="sm" className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90 cursor-pointer">
|
||||||
{title === 'Add Zone' ? 'Add' : 'Save'}
|
{title === 'Add Zone' ? 'Add' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment, createElement, useState } from 'react'
|
import { Fragment, createElement, useState } from 'react'
|
||||||
|
import modalStyles from './modal-interactive.module.css'
|
||||||
import { RotateCcw, ChevronDown } from 'lucide-react'
|
import { RotateCcw, ChevronDown } from 'lucide-react'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -96,7 +97,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Type</Label>
|
<Label className="text-xs text-muted-foreground">Type</Label>
|
||||||
<Select value={form.type} onValueChange={(v) => set('type', v as NodeType)}>
|
<Select value={form.type} onValueChange={(v) => set('type', v as NodeType)}>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8 w-full">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 w-full cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Node type selector">
|
||||||
<SelectValue>{NODE_TYPE_LABELS[(form.type ?? 'server') as NodeType]}</SelectValue>
|
<SelectValue>{NODE_TYPE_LABELS[(form.type ?? 'server') as NodeType]}</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -137,7 +138,8 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIconPickerOpen((o) => !o)}
|
onClick={() => setIconPickerOpen((o) => !o)}
|
||||||
className="flex items-center justify-between gap-2 h-8 px-3 rounded-md bg-[#21262d] border border-[#30363d] text-sm hover:border-[#8b949e] transition-colors w-full"
|
className={`flex items-center justify-between gap-2 h-8 px-3 bg-[#21262d] border border-[#30363d] text-sm transition-colors w-full cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`}
|
||||||
|
aria-label="Icon picker trigger"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 min-w-0">
|
<span className="flex items-center gap-2 min-w-0">
|
||||||
{(() => {
|
{(() => {
|
||||||
@@ -160,7 +162,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={iconSearch}
|
value={iconSearch}
|
||||||
onChange={(e) => setIconSearch(e.target.value)}
|
onChange={(e) => setIconSearch(e.target.value)}
|
||||||
placeholder="Search icons…"
|
placeholder="Search icons…"
|
||||||
className="bg-[#21262d] border-[#30363d] text-xs h-7"
|
className={`bg-[#21262d] border-[#30363d] text-xs h-7 ${modalStyles['modal-radius']}`}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-2 max-h-52 overflow-y-auto">
|
<div className="flex flex-col gap-2 max-h-52 overflow-y-auto">
|
||||||
@@ -182,7 +184,8 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
type="button"
|
type="button"
|
||||||
title={entry.label}
|
title={entry.label}
|
||||||
onClick={() => { set('custom_icon', isSelected ? undefined : entry.key); setIconPickerOpen(false) }}
|
onClick={() => { set('custom_icon', isSelected ? undefined : entry.key); setIconPickerOpen(false) }}
|
||||||
className="flex items-center justify-center w-7 h-7 rounded transition-colors"
|
className={`flex items-center justify-center w-7 h-7 rounded transition-colors cursor-pointer ${modalStyles['modal-interactive']}`}
|
||||||
|
aria-label={`Select icon ${entry.label}`}
|
||||||
style={{
|
style={{
|
||||||
background: isSelected ? '#00d4ff22' : 'transparent',
|
background: isSelected ? '#00d4ff22' : 'transparent',
|
||||||
border: isSelected ? '1px solid #00d4ff88' : '1px solid transparent',
|
border: isSelected ? '1px solid #00d4ff88' : '1px solid transparent',
|
||||||
@@ -210,7 +213,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={form.label ?? ''}
|
value={form.label ?? ''}
|
||||||
onChange={(e) => { set('label', e.target.value); if (labelError) setLabelError(false) }}
|
onChange={(e) => { set('label', e.target.value); if (labelError) setLabelError(false) }}
|
||||||
placeholder="My Server"
|
placeholder="My Server"
|
||||||
className={`bg-[#21262d] text-sm h-8 ${labelError ? 'border-[#f85149] focus-visible:ring-[#f85149]' : 'border-[#30363d]'}`}
|
className={`bg-[#21262d] text-sm h-8 ${labelError ? 'border-[#f85149] focus-visible:ring-[#f85149]' : 'border-[#30363d]'} ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
{labelError && <p className="text-[11px] text-[#f85149]">Label is required</p>}
|
{labelError && <p className="text-[11px] text-[#f85149]">Label is required</p>}
|
||||||
</div>
|
</div>
|
||||||
@@ -222,26 +225,27 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={form.hostname ?? ''}
|
value={form.hostname ?? ''}
|
||||||
onChange={(e) => set('hostname', e.target.value)}
|
onChange={(e) => set('hostname', e.target.value)}
|
||||||
placeholder="server.lan"
|
placeholder="server.lan"
|
||||||
className="bg-[#21262d] border-[#30363d] font-mono text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* IP */}
|
{/* IP */}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">IP Address <span className="text-muted-foreground/50">(comma-separated)</span></Label>
|
<Label className="text-xs text-muted-foreground">IP Address</Label>
|
||||||
<Input
|
<Input
|
||||||
value={form.ip ?? ''}
|
value={form.ip ?? ''}
|
||||||
onChange={(e) => set('ip', e.target.value)}
|
onChange={(e) => set('ip', e.target.value)}
|
||||||
placeholder="192.168.1.x, 2001:db8::1"
|
placeholder="192.168.1.x, 2001:db8::1"
|
||||||
className="bg-[#21262d] border-[#30363d] font-mono text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
|
<span className="text-[10px] text-muted-foreground/50">comma-separated</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Check method */}
|
{/* Check method */}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Check Method</Label>
|
<Label className="text-xs text-muted-foreground">Check Method</Label>
|
||||||
<Select value={form.check_method ?? 'ping'} onValueChange={(v) => set('check_method', v as CheckMethod)}>
|
<Select value={form.check_method ?? 'ping'} onValueChange={(v) => set('check_method', v as CheckMethod)}>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Check method selector">
|
||||||
<SelectValue>{CHECK_METHOD_LABELS[(form.check_method ?? 'ping') as CheckMethod]}</SelectValue>
|
<SelectValue>{CHECK_METHOD_LABELS[(form.check_method ?? 'ping') as CheckMethod]}</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -259,7 +263,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={form.check_target ?? ''}
|
value={form.check_target ?? ''}
|
||||||
onChange={(e) => set('check_target', e.target.value)}
|
onChange={(e) => set('check_target', e.target.value)}
|
||||||
placeholder="http://..."
|
placeholder="http://..."
|
||||||
className="bg-[#21262d] border-[#30363d] font-mono text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -271,7 +275,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={form.parent_id ?? 'none'}
|
value={form.parent_id ?? 'none'}
|
||||||
onValueChange={(v) => set('parent_id', v === 'none' ? undefined : v)}
|
onValueChange={(v) => set('parent_id', v === 'none' ? undefined : v)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Parent container selector">
|
||||||
<SelectValue placeholder="None (standalone)">
|
<SelectValue placeholder="None (standalone)">
|
||||||
{form.parent_id
|
{form.parent_id
|
||||||
? (filteredParentNodes.find((n) => n.id === form.parent_id)?.label ?? 'None (standalone)')
|
? (filteredParentNodes.find((n) => n.id === form.parent_id)?.label ?? 'None (standalone)')
|
||||||
@@ -300,7 +304,9 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={!!form.container_mode}
|
aria-checked={!!form.container_mode}
|
||||||
onClick={() => set('container_mode', !form.container_mode)}
|
onClick={() => set('container_mode', !form.container_mode)}
|
||||||
className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full transition-colors focus:outline-none"
|
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full transition-colors focus:outline-none ${modalStyles['modal-interactive']}`}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Toggle container mode"
|
||||||
style={{ background: form.container_mode ? '#ff6e00' : '#30363d' }}
|
style={{ background: form.container_mode ? '#ff6e00' : '#30363d' }}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -333,9 +339,11 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
return (
|
return (
|
||||||
<div key={key} className="flex flex-col gap-1 items-center">
|
<div key={key} className="flex flex-col gap-1 items-center">
|
||||||
<label
|
<label
|
||||||
className="relative w-full h-7 rounded-md border cursor-pointer overflow-hidden transition-all"
|
className={`relative w-full h-7 rounded-md border cursor-pointer overflow-hidden transition-all ${modalStyles['modal-interactive']}`}
|
||||||
style={{ borderColor: isCustom ? currentValue : '#30363d' }}
|
style={{ borderColor: isCustom ? currentValue : '#30363d' }}
|
||||||
title={`${key.charAt(0).toUpperCase() + key.slice(1)}: ${currentValue}`}
|
title={`${key.charAt(0).toUpperCase() + key.slice(1)}: ${currentValue}`}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`Color picker for ${key}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@@ -363,7 +371,7 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={String(form.bottom_handles ?? 1)}
|
value={String(form.bottom_handles ?? 1)}
|
||||||
onValueChange={(v) => set('bottom_handles', parseInt(v ?? '1', 10))}
|
onValueChange={(v) => set('bottom_handles', parseInt(v ?? '1', 10))}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-[#21262d] border-[#30363d] text-sm h-8">
|
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Bottom connection points selector">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||||
@@ -383,22 +391,37 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
value={form.notes ?? ''}
|
value={form.notes ?? ''}
|
||||||
onChange={(e) => set('notes', e.target.value)}
|
onChange={(e) => set('notes', e.target.value)}
|
||||||
placeholder="Optional notes"
|
placeholder="Optional notes"
|
||||||
className="bg-[#21262d] border-[#30363d] text-sm h-8"
|
className={`bg-[#21262d] border-[#30363d] text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-1">
|
<div className="flex justify-between gap-2 pt-1">
|
||||||
<Button type="button" variant="ghost" size="sm" onClick={onClose}>
|
{/* Show delete button only for edit mode (not add) */}
|
||||||
Cancel
|
{title !== 'Add Node' ? (
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
type="button"
|
||||||
type="submit"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90"
|
className="text-[#f85149] hover:text-[#f85149] hover:bg-[#f85149]/10 cursor-pointer"
|
||||||
>
|
onClick={() => { if (window.confirm('Delete this node?')) onSubmit({ ...form, _delete: true }); onClose(); }}
|
||||||
{title === 'Add Node' ? 'Add' : 'Save'}
|
style={{ minWidth: 64 }}
|
||||||
</Button>
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
) : <span />}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button type="button" variant="ghost" size="sm" className={`cursor-pointer ${modalStyles['modal-cancel-hover']}`} onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
className="bg-[#00d4ff] text-[#0d1117] hover:bg-[#00d4ff]/90 cursor-pointer"
|
||||||
|
>
|
||||||
|
{title === 'Add Node' ? 'Add' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* SidebarItem pointer on hover */
|
||||||
|
.sidebar-pointer:hover {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
/* Consistent border radius for all modal input/select/button elements */
|
||||||
|
.modal-radius {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pointer cursor for close X */
|
||||||
|
.modal-close-pointer:hover {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtle hover background for cancel button */
|
||||||
|
.modal-cancel-hover:hover {
|
||||||
|
background: #21262d !important;
|
||||||
|
}
|
||||||
|
/* Shared hover/focus border effect for interactive modal elements */
|
||||||
|
.modal-interactive {
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.modal-interactive:hover,
|
||||||
|
.modal-interactive:focus {
|
||||||
|
border-color: #8b949e !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
@@ -200,7 +200,7 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
|||||||
<aside className="w-72 shrink-0 flex flex-col border-l border-border bg-[#161b22] overflow-y-auto">
|
<aside className="w-72 shrink-0 flex flex-col border-l border-border bg-[#161b22] overflow-y-auto">
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
||||||
<span className="font-semibold text-sm text-foreground truncate">{data.label}</span>
|
<span className="font-semibold text-sm text-foreground truncate">{data.label}</span>
|
||||||
<button aria-label="Close panel" onClick={() => setSelectedNode(null)} className="text-muted-foreground hover:text-foreground transition-colors">
|
<button aria-label="Close panel" onClick={() => setSelectedNode(null)} className="text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +243,7 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
|||||||
<span className="text-xs text-muted-foreground">Properties{properties.length > 0 ? ` (${properties.length})` : ''}</span>
|
<span className="text-xs text-muted-foreground">Properties{properties.length > 0 ? ` (${properties.length})` : ''}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setAddingProp((v) => !v); setEditingPropIndex(null) }}
|
onClick={() => { setAddingProp((v) => !v); setEditingPropIndex(null) }}
|
||||||
className="flex items-center gap-1 text-[10px] text-[#00d4ff] hover:text-[#00d4ff]/80 transition-colors"
|
className="flex items-center gap-1 text-[10px] text-[#00d4ff] hover:text-[#00d4ff]/80 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus size={10} /> Add
|
<Plus size={10} /> Add
|
||||||
</button>
|
</button>
|
||||||
@@ -289,7 +289,7 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
|||||||
<div className="px-4 py-3 border-t border-border">
|
<div className="px-4 py-3 border-t border-border">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-xs text-muted-foreground">Services{services.length > 0 ? ` (${services.length})` : ''}</span>
|
<span className="text-xs text-muted-foreground">Services{services.length > 0 ? ` (${services.length})` : ''}</span>
|
||||||
<button onClick={() => { setAddingForNode((v) => v === node.id ? null : node.id); setEditingFor(null) }} className="flex items-center gap-1 text-[10px] text-[#00d4ff] hover:text-[#00d4ff]/80 transition-colors">
|
<button onClick={() => { setAddingForNode((v) => v === node.id ? null : node.id); setEditingFor(null) }} className="flex items-center gap-1 text-[10px] text-[#00d4ff] hover:text-[#00d4ff]/80 transition-colors cursor-pointer">
|
||||||
<Plus size={10} /> Add
|
<Plus size={10} /> Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,10 +316,10 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-auto flex gap-2 px-4 py-3 border-t border-border">
|
<div className="mt-auto flex gap-2 px-4 py-3 border-t border-border">
|
||||||
<Button size="sm" variant="secondary" className="flex-1 gap-1.5" onClick={() => onEdit(node.id)}>
|
<Button size="sm" variant="secondary" className="flex-1 gap-1.5 cursor-pointer" onClick={() => onEdit(node.id)}>
|
||||||
<Edit size={14} /> Edit
|
<Edit size={14} /> Edit
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="destructive" className="gap-1.5" aria-label="Delete node" onClick={handleDelete}>
|
<Button size="sm" variant="destructive" className="gap-1.5 cursor-pointer" aria-label="Delete node" onClick={handleDelete}>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useAuthStore } from '@/stores/authStore'
|
|||||||
import { scanApi, settingsApi } from '@/api/client'
|
import { scanApi, settingsApi } from '@/api/client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { useLatestRelease } from '@/hooks/useLatestRelease'
|
import { useLatestRelease } from '@/hooks/useLatestRelease'
|
||||||
|
|
||||||
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
||||||
|
|
||||||
const STANDALONE = import.meta.env.VITE_STANDALONE === 'true'
|
const STANDALONE = import.meta.env.VITE_STANDALONE === 'true'
|
||||||
@@ -593,7 +594,7 @@ function ScanHistoryPanel() {
|
|||||||
<div className="text-[#8b949e] text-[10px] font-mono truncate">{r.ranges.join(', ')}</div>
|
<div className="text-[#8b949e] text-[10px] font-mono truncate">{r.ranges.join(', ')}</div>
|
||||||
)}
|
)}
|
||||||
{r.error && (
|
{r.error && (
|
||||||
<div className="text-[#f85149] text-[10px] mt-1 leading-tight break-words whitespace-pre-wrap">
|
<div className="text-[#f85149] text-[10px] mt-1 leading-tight wrap-break-word whitespace-pre-wrap">
|
||||||
{r.error}
|
{r.error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -749,7 +750,7 @@ function SidebarItem({ icon: Icon, label, collapsed, active, badge, accent, onCl
|
|||||||
const btn = (
|
const btn = (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`relative flex items-center gap-2 w-full px-2 py-1.5 rounded-md text-sm transition-colors ${
|
className={`relative flex items-center gap-2 w-full px-2 py-1.5 rounded-md text-sm transition-colors cursor-pointer ${
|
||||||
active
|
active
|
||||||
? 'bg-[#00d4ff]/10 text-[#00d4ff]'
|
? 'bg-[#00d4ff]/10 text-[#00d4ff]'
|
||||||
: accent
|
: accent
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function Toolbar({ onSave, onAutoLayout, onExport, onChangeStyle, onUndo,
|
|||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
<Button
|
<Button
|
||||||
size="sm" variant="ghost"
|
size="sm" variant="ghost"
|
||||||
className="gap-1.5 text-muted-foreground hover:text-foreground disabled:opacity-30"
|
className="gap-1.5 text-muted-foreground hover:text-foreground disabled:opacity-30 cursor-pointer hover:bg-[#21262d]"
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={past.length === 0}
|
disabled={past.length === 0}
|
||||||
title="Undo (Ctrl+Z)"
|
title="Undo (Ctrl+Z)"
|
||||||
@@ -48,7 +48,7 @@ export function Toolbar({ onSave, onAutoLayout, onExport, onChangeStyle, onUndo,
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm" variant="ghost"
|
size="sm" variant="ghost"
|
||||||
className="gap-1.5 text-muted-foreground hover:text-foreground disabled:opacity-30"
|
className="gap-1.5 text-muted-foreground hover:text-foreground disabled:opacity-30 cursor-pointer hover:bg-[#21262d]"
|
||||||
onClick={onRedo}
|
onClick={onRedo}
|
||||||
disabled={future.length === 0}
|
disabled={future.length === 0}
|
||||||
title="Redo (Ctrl+Y)"
|
title="Redo (Ctrl+Y)"
|
||||||
@@ -56,13 +56,13 @@ export function Toolbar({ onSave, onAutoLayout, onExport, onChangeStyle, onUndo,
|
|||||||
<Redo2 size={14} />
|
<Redo2 size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="w-px h-4 bg-border mx-1" />
|
<div className="w-px h-4 bg-border mx-1" />
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onAutoLayout}>
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onAutoLayout}>
|
||||||
<LayoutDashboard size={14} /> Auto Layout
|
<LayoutDashboard size={14} /> Auto Layout
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onChangeStyle}>
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onChangeStyle}>
|
||||||
<Palette size={14} /> Style
|
<Palette size={14} /> Style
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={() => fileInputRef.current?.click()} title="Import from YAML">
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={() => fileInputRef.current?.click()} title="Import from YAML">
|
||||||
<Upload size={14} /> Import
|
<Upload size={14} /> Import
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
@@ -72,21 +72,21 @@ export function Toolbar({ onSave, onAutoLayout, onExport, onChangeStyle, onUndo,
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onExportYaml} title="Export canvas as YAML">
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onExportYaml} title="Export canvas as YAML">
|
||||||
<Download size={14} /> Export
|
<Download size={14} /> Export
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onExport} title="Download canvas as PNG">
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onExport} title="Download canvas as PNG">
|
||||||
<FileDown size={14} /> PNG
|
<FileDown size={14} /> PNG
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onExportMd} title="Copy inventory as Markdown table">
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onExportMd} title="Copy inventory as Markdown table">
|
||||||
<Table2 size={14} /> MD
|
<Table2 size={14} /> MD
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground" onClick={onShortcuts} title="Keyboard shortcuts (?)">
|
<Button size="sm" variant="ghost" className="gap-1.5 text-muted-foreground hover:text-foreground cursor-pointer hover:bg-[#21262d]" onClick={onShortcuts} title="Keyboard shortcuts (?)">
|
||||||
<HelpCircle size={14} />
|
<HelpCircle size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="gap-1.5 relative"
|
className="gap-1.5 relative cursor-pointer border border-transparent hover:border-white"
|
||||||
style={{
|
style={{
|
||||||
background: hasUnsavedChanges ? '#00d4ff' : undefined,
|
background: hasUnsavedChanges ? '#00d4ff' : undefined,
|
||||||
color: hasUnsavedChanges ? '#0d1117' : undefined,
|
color: hasUnsavedChanges ? '#0d1117' : undefined,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import modalStyles from '../modals/modal-interactive.module.css'
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
||||||
|
|
||||||
@@ -63,13 +64,12 @@ function DialogContent({
|
|||||||
render={
|
render={
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="absolute top-2 right-2"
|
className={"absolute top-2 right-2 " + modalStyles['modal-close-pointer']}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XIcon
|
<XIcon />
|
||||||
/>
|
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user