@discourser/design-system 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QC44JPCA.cjs → chunk-ABC7N32K.cjs} +316 -10
- package/dist/chunk-ABC7N32K.cjs.map +1 -0
- package/dist/{chunk-F7LHARS4.js → chunk-GD6Q2FUE.js} +446 -6
- package/dist/chunk-GD6Q2FUE.js.map +1 -0
- package/dist/{chunk-M7J7WKJY.js → chunk-SBKRSXSZ.js} +317 -11
- package/dist/chunk-SBKRSXSZ.js.map +1 -0
- package/dist/{chunk-QP4EJI3G.cjs → chunk-UNWXE6UB.cjs} +450 -2
- package/dist/chunk-UNWXE6UB.cjs.map +1 -0
- package/dist/components/Breadcrumb.d.ts +9 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Checkbox.d.ts +6 -6
- package/dist/components/Icons/ClockIcon.d.ts +6 -0
- package/dist/components/Icons/ClockIcon.d.ts.map +1 -0
- package/dist/components/Icons/GripDotsVerticalIcon.d.ts +6 -0
- package/dist/components/Icons/GripDotsVerticalIcon.d.ts.map +1 -0
- package/dist/components/Icons/index.d.ts +3 -0
- package/dist/components/Icons/index.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/AddScenarioDialog.d.ts +16 -0
- package/dist/components/ScenarioQueue/AddScenarioDialog.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/ScenarioCard.d.ts +10 -0
- package/dist/components/ScenarioQueue/ScenarioCard.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/ScenarioCardDraggable.d.ts +15 -0
- package/dist/components/ScenarioQueue/ScenarioCardDraggable.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/ScenarioQueue.d.ts +3 -0
- package/dist/components/ScenarioQueue/ScenarioQueue.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/index.d.ts +6 -0
- package/dist/components/ScenarioQueue/index.d.ts.map +1 -0
- package/dist/components/ScenarioQueue/types.d.ts +56 -0
- package/dist/components/ScenarioQueue/types.d.ts.map +1 -0
- package/dist/components/index.cjs +65 -33
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/index.cjs +69 -37
- package/dist/index.js +2 -2
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.d.ts.map +1 -1
- package/dist/preset/index.js +1 -1
- package/dist/preset/recipes/avatar.d.ts.map +1 -1
- package/dist/preset/recipes/breadcrumb.d.ts +2 -0
- package/dist/preset/recipes/breadcrumb.d.ts.map +1 -0
- package/dist/preset/recipes/checkbox.d.ts.map +1 -1
- package/dist/preset/recipes/field.d.ts.map +1 -1
- package/dist/preset/recipes/index.d.ts +3 -0
- package/dist/preset/recipes/index.d.ts.map +1 -1
- package/dist/preset/recipes/progress.d.ts.map +1 -1
- package/dist/preset/recipes/radio-group.d.ts.map +1 -1
- package/dist/preset/recipes/scenario-card.d.ts +2 -0
- package/dist/preset/recipes/scenario-card.d.ts.map +1 -0
- package/dist/preset/recipes/scenario-queue.d.ts +2 -0
- package/dist/preset/recipes/scenario-queue.d.ts.map +1 -0
- package/dist/preset/recipes/steps.d.ts.map +1 -1
- package/dist/preset/recipes/toast.d.ts.map +1 -1
- package/dist/preset/recipes/tooltip.d.ts.map +1 -1
- package/dist/preset/semantic-tokens.d.ts +12 -0
- package/dist/preset/semantic-tokens.d.ts.map +1 -1
- package/package.json +10 -1
- package/src/components/Breadcrumb.tsx +34 -0
- package/src/components/Icons/ClockIcon.tsx +40 -0
- package/src/components/Icons/GripDotsVerticalIcon.tsx +26 -0
- package/src/components/Icons/index.ts +2 -0
- package/src/components/ScenarioQueue/AddScenarioDialog.tsx +137 -0
- package/src/components/ScenarioQueue/ScenarioCard.tsx +120 -0
- package/src/components/ScenarioQueue/ScenarioCardDraggable.tsx +41 -0
- package/src/components/ScenarioQueue/ScenarioQueue.test.tsx +398 -0
- package/src/components/ScenarioQueue/ScenarioQueue.tsx +162 -0
- package/src/components/ScenarioQueue/index.ts +11 -0
- package/src/components/ScenarioQueue/types.ts +86 -0
- package/src/components/index.ts +19 -0
- package/src/preset/index.ts +9 -0
- package/src/preset/recipes/avatar.ts +1 -2
- package/src/preset/recipes/breadcrumb.ts +77 -0
- package/src/preset/recipes/checkbox.ts +1 -2
- package/src/preset/recipes/field.ts +1 -2
- package/src/preset/recipes/index.ts +7 -0
- package/src/preset/recipes/progress.ts +1 -2
- package/src/preset/recipes/radio-group.ts +1 -2
- package/src/preset/recipes/scenario-card.ts +151 -0
- package/src/preset/recipes/scenario-queue.ts +99 -0
- package/src/preset/recipes/steps.ts +1 -2
- package/src/preset/recipes/toast.ts +1 -2
- package/src/preset/recipes/tooltip.ts +1 -2
- package/src/preset/semantic-tokens.ts +4 -0
- package/src/test/setup.ts +12 -0
- package/dist/chunk-F7LHARS4.js.map +0 -1
- package/dist/chunk-M7J7WKJY.js.map +0 -1
- package/dist/chunk-QC44JPCA.cjs.map +0 -1
- package/dist/chunk-QP4EJI3G.cjs.map +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { ark } from '@ark-ui/react/factory'
|
|
3
|
+
import type { ComponentProps } from 'react'
|
|
4
|
+
import { createStyleContext } from 'styled-system/jsx'
|
|
5
|
+
import { breadcrumb } from 'styled-system/recipes'
|
|
6
|
+
|
|
7
|
+
const ChevronRightIcon = () => (
|
|
8
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
9
|
+
<path d="m9 18 6-6-6-6"></path>
|
|
10
|
+
</svg>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const { withProvider, withContext } = createStyleContext(breadcrumb)
|
|
14
|
+
|
|
15
|
+
export type RootProps = ComponentProps<typeof Root>
|
|
16
|
+
|
|
17
|
+
export const Root = withProvider(ark.nav, 'root', { defaultProps: { 'aria-label': 'breadcrumb' } })
|
|
18
|
+
export const List = withContext(ark.ol, 'list')
|
|
19
|
+
export const Item = withContext(ark.li, 'item')
|
|
20
|
+
export const Link = withContext(ark.a, 'link')
|
|
21
|
+
export const Ellipsis = withContext(ark.li, 'ellipsis', {
|
|
22
|
+
defaultProps: {
|
|
23
|
+
role: 'presentation',
|
|
24
|
+
'aria-hidden': true,
|
|
25
|
+
children: '...',
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const Separator = withContext(ark.li, 'separator', {
|
|
30
|
+
defaultProps: {
|
|
31
|
+
'aria-hidden': true,
|
|
32
|
+
children: <ChevronRightIcon />,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ark } from '@ark-ui/react/factory'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { styled } from 'styled-system/jsx'
|
|
4
|
+
|
|
5
|
+
const StyledSvg = styled(ark.svg)
|
|
6
|
+
|
|
7
|
+
export type ClockIconProps = ComponentProps<typeof StyledSvg>
|
|
8
|
+
|
|
9
|
+
export const ClockIcon = (props: ClockIconProps) => (
|
|
10
|
+
<StyledSvg
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
fill="none"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
width="1em"
|
|
15
|
+
height="1em"
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
strokeWidth="1.5"
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
/>
|
|
25
|
+
<path
|
|
26
|
+
d="M12 6V12"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
strokeWidth="1.5"
|
|
29
|
+
strokeLinecap="round"
|
|
30
|
+
strokeLinejoin="round"
|
|
31
|
+
/>
|
|
32
|
+
<path
|
|
33
|
+
d="M16.24 16.24L12 12"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
strokeWidth="1.5"
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
strokeLinejoin="round"
|
|
38
|
+
/>
|
|
39
|
+
</StyledSvg>
|
|
40
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ark } from '@ark-ui/react/factory'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { styled } from 'styled-system/jsx'
|
|
4
|
+
|
|
5
|
+
const StyledSvg = styled(ark.svg)
|
|
6
|
+
|
|
7
|
+
export type GripDotsVerticalIconProps = ComponentProps<typeof StyledSvg>
|
|
8
|
+
|
|
9
|
+
export const GripDotsVerticalIcon = (props: GripDotsVerticalIconProps) => (
|
|
10
|
+
<StyledSvg
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
fill="none"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
width="1em"
|
|
15
|
+
height="1em"
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
d="M9 6H9.01M15 6H15.01M15 12H15.01M9 12H9.01M9 18H9.01M15 18H15.01M10 6C10 6.55228 9.55228 7 9 7C8.44772 7 8 6.55228 8 6C8 5.44772 8.44772 5 9 5C9.55228 5 10 5.44772 10 6ZM16 6C16 6.55228 15.5523 7 15 7C14.4477 7 14 6.55228 14 6C14 5.44772 14.4477 5 15 5C15.5523 5 16 5.44772 16 6ZM10 12C10 12.5523 9.55228 13 9 13C8.44772 13 8 12.5523 8 12C8 11.4477 8.44772 11 9 11C9.55228 11 10 11.4477 10 12ZM16 12C16 12.5523 15.5523 13 15 13C14.4477 13 14 12.5523 14 12C14 11.4477 14.4477 11 15 11C15.5523 11 16 11.4477 16 12ZM10 18C10 18.5523 9.55228 19 9 19C8.44772 19 8 18.5523 8 18C8 17.4477 8.44772 17 9 17C9.55228 17 10 17.4477 10 18ZM16 18C16 18.5523 15.5523 19 15 19C14.4477 19 14 18.5523 14 18C14 17.4477 14.4477 17 15 17C15.5523 17 16 17.4477 16 18Z"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
strokeWidth="2"
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
/>
|
|
25
|
+
</StyledSvg>
|
|
26
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { css } from 'styled-system/css'
|
|
4
|
+
import * as Dialog from '../Dialog'
|
|
5
|
+
|
|
6
|
+
interface AddScenarioDialogProps {
|
|
7
|
+
open: boolean
|
|
8
|
+
onClose: () => void
|
|
9
|
+
/** Render prop slot — consumer injects their scenario collection here. */
|
|
10
|
+
renderContent?: (props: { onClose: () => void }) => ReactNode
|
|
11
|
+
/** Called when user clicks "Browse More Scenarios" in the footer */
|
|
12
|
+
onBrowseMore?: () => void
|
|
13
|
+
/** Called when user clicks "Build Custom Scenario" in the footer */
|
|
14
|
+
onBuildCustom?: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const dialogContentClass = css({
|
|
18
|
+
width: 'full',
|
|
19
|
+
maxWidth: '560px',
|
|
20
|
+
maxHeight: '80vh',
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const dialogBodyClass = css({
|
|
26
|
+
flex: '1',
|
|
27
|
+
overflowY: 'auto',
|
|
28
|
+
py: '0',
|
|
29
|
+
px: '6',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const footerLinkClass = css({
|
|
33
|
+
display: 'inline-flex',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
gap: '1',
|
|
36
|
+
fontSize: 'sm',
|
|
37
|
+
color: 'primary.solid.bg',
|
|
38
|
+
fontWeight: 'medium',
|
|
39
|
+
textDecoration: 'none',
|
|
40
|
+
cursor: 'pointer',
|
|
41
|
+
bg: 'transparent',
|
|
42
|
+
border: 'none',
|
|
43
|
+
p: '0',
|
|
44
|
+
_hover: { textDecoration: 'underline' },
|
|
45
|
+
_focusVisible: { focusVisibleRing: 'outside' },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
export function AddScenarioDialog({
|
|
49
|
+
open,
|
|
50
|
+
onClose,
|
|
51
|
+
renderContent,
|
|
52
|
+
onBrowseMore,
|
|
53
|
+
onBuildCustom,
|
|
54
|
+
}: AddScenarioDialogProps) {
|
|
55
|
+
return (
|
|
56
|
+
<Dialog.Root open={open} onOpenChange={({ open: isOpen }) => !isOpen && onClose()}>
|
|
57
|
+
<Dialog.Backdrop />
|
|
58
|
+
<Dialog.Positioner>
|
|
59
|
+
<Dialog.Content className={dialogContentClass}>
|
|
60
|
+
<Dialog.Header>
|
|
61
|
+
<Dialog.Title>Add Scenario</Dialog.Title>
|
|
62
|
+
<Dialog.CloseTrigger asChild>
|
|
63
|
+
<button
|
|
64
|
+
aria-label="Close"
|
|
65
|
+
className={css({
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
borderRadius: 'l1',
|
|
70
|
+
w: '6',
|
|
71
|
+
h: '6',
|
|
72
|
+
color: 'fg.muted',
|
|
73
|
+
bg: 'transparent',
|
|
74
|
+
border: 'none',
|
|
75
|
+
cursor: 'pointer',
|
|
76
|
+
fontSize: 'lg',
|
|
77
|
+
_hover: { bg: 'neutral.subtle.bg', color: 'fg.default' },
|
|
78
|
+
})}
|
|
79
|
+
>
|
|
80
|
+
✕
|
|
81
|
+
</button>
|
|
82
|
+
</Dialog.CloseTrigger>
|
|
83
|
+
</Dialog.Header>
|
|
84
|
+
|
|
85
|
+
{/* Scrollable body — consumer owns this content */}
|
|
86
|
+
<Dialog.Body className={dialogBodyClass}>
|
|
87
|
+
{renderContent ? (
|
|
88
|
+
renderContent({ onClose })
|
|
89
|
+
) : (
|
|
90
|
+
// Storybook placeholder
|
|
91
|
+
<div
|
|
92
|
+
className={css({
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
justifyContent: 'center',
|
|
96
|
+
minH: '48',
|
|
97
|
+
borderRadius: 'l2',
|
|
98
|
+
bg: 'neutral.subtle.bg',
|
|
99
|
+
color: 'fg.muted',
|
|
100
|
+
fontSize: 'sm',
|
|
101
|
+
borderWidth: '1px',
|
|
102
|
+
borderStyle: 'dashed',
|
|
103
|
+
borderColor: 'border.default',
|
|
104
|
+
})}
|
|
105
|
+
>
|
|
106
|
+
Scenario collection renders here
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</Dialog.Body>
|
|
110
|
+
|
|
111
|
+
<Dialog.Footer>
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
className={footerLinkClass}
|
|
115
|
+
onClick={() => {
|
|
116
|
+
onBrowseMore?.()
|
|
117
|
+
onClose()
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
Browse More Scenarios →
|
|
121
|
+
</button>
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
className={footerLinkClass}
|
|
125
|
+
onClick={() => {
|
|
126
|
+
onBuildCustom?.()
|
|
127
|
+
onClose()
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
Build Custom Scenario →
|
|
131
|
+
</button>
|
|
132
|
+
</Dialog.Footer>
|
|
133
|
+
</Dialog.Content>
|
|
134
|
+
</Dialog.Positioner>
|
|
135
|
+
</Dialog.Root>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { type ComponentProps, forwardRef } from 'react'
|
|
3
|
+
import { Box, Circle, HStack, VStack } from 'styled-system/jsx'
|
|
4
|
+
import { scenarioCard } from 'styled-system/recipes'
|
|
5
|
+
import * as Card from '../Card'
|
|
6
|
+
import { ClockIcon } from '../Icons/ClockIcon'
|
|
7
|
+
import { GripDotsVerticalIcon } from '../Icons/GripDotsVerticalIcon'
|
|
8
|
+
import * as Switch from '../Switch'
|
|
9
|
+
import {
|
|
10
|
+
type ScenarioCardProps,
|
|
11
|
+
difficultyLabel,
|
|
12
|
+
} from './types'
|
|
13
|
+
|
|
14
|
+
export interface ScenarioCardElementProps
|
|
15
|
+
extends ScenarioCardProps,
|
|
16
|
+
Omit<ComponentProps<'div'>, 'children'> {
|
|
17
|
+
/** Callback ref for the root element (used by ScenarioCardDraggable) */
|
|
18
|
+
rootRef?: (el: HTMLDivElement | null) => void
|
|
19
|
+
/** Callback ref for the drag handle (used by ScenarioCardDraggable) */
|
|
20
|
+
handleRef?: (el: HTMLDivElement | null) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ScenarioCard = forwardRef<HTMLDivElement, ScenarioCardElementProps>(
|
|
24
|
+
function ScenarioCard(
|
|
25
|
+
{
|
|
26
|
+
scenario,
|
|
27
|
+
position,
|
|
28
|
+
isActive,
|
|
29
|
+
showRequeueSwitch = false,
|
|
30
|
+
onRequeue,
|
|
31
|
+
isRepeat = false,
|
|
32
|
+
isDragging = false,
|
|
33
|
+
rootRef,
|
|
34
|
+
handleRef,
|
|
35
|
+
className: _className,
|
|
36
|
+
...rest
|
|
37
|
+
},
|
|
38
|
+
_ref,
|
|
39
|
+
) {
|
|
40
|
+
const styles = scenarioCard({ isActive, isDragging })
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Card.Root
|
|
44
|
+
ref={rootRef}
|
|
45
|
+
className={styles.root}
|
|
46
|
+
variant="elevated"
|
|
47
|
+
css={{
|
|
48
|
+
boxShadow: '0px 2px 8px 0px rgba(167, 139, 250, 0.15)',
|
|
49
|
+
// neutral.surface.bg resolves to white in light mode; Figma uses neutral/99 (#FDFCF5)
|
|
50
|
+
bg: 'neutral.1',
|
|
51
|
+
}}
|
|
52
|
+
{...rest}
|
|
53
|
+
>
|
|
54
|
+
<VStack gap="2.5" pl="5" pr="3" py="2.5" alignItems="flex-start">
|
|
55
|
+
{/* Top row: drag handle (left) + position badge (right) — queue tab only */}
|
|
56
|
+
{!showRequeueSwitch && (
|
|
57
|
+
<HStack justify="space-between" alignItems="flex-start" w="full" py="2" pr="2.5">
|
|
58
|
+
<Box
|
|
59
|
+
ref={handleRef}
|
|
60
|
+
className={styles.dragHandle}
|
|
61
|
+
aria-label="Drag to reorder"
|
|
62
|
+
>
|
|
63
|
+
<GripDotsVerticalIcon w="9" h="10" aria-hidden="true" />
|
|
64
|
+
</Box>
|
|
65
|
+
<Circle
|
|
66
|
+
size={isActive ? "12" : "11"}
|
|
67
|
+
className={styles.positionBadge}
|
|
68
|
+
aria-label={`Position ${position}`}
|
|
69
|
+
>
|
|
70
|
+
{position}
|
|
71
|
+
</Circle>
|
|
72
|
+
</HStack>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{/* Title */}
|
|
76
|
+
<Box px="1" py="1.5" w="full">
|
|
77
|
+
<p className={styles.title}>{scenario.title}</p>
|
|
78
|
+
</Box>
|
|
79
|
+
|
|
80
|
+
{/* Badge column: difficulty stacked above duration, matching Figma flex-col layout */}
|
|
81
|
+
<VStack gap="3" alignItems="flex-start" py="2.5">
|
|
82
|
+
<Box
|
|
83
|
+
className={styles.difficultyBadge}
|
|
84
|
+
data-difficulty={scenario.difficulty}
|
|
85
|
+
>
|
|
86
|
+
{difficultyLabel[scenario.difficulty]}
|
|
87
|
+
</Box>
|
|
88
|
+
|
|
89
|
+
<Box className={styles.durationBadge} data-difficulty={scenario.difficulty}>
|
|
90
|
+
<ClockIcon w="3" h="3" aria-hidden="true" />
|
|
91
|
+
{scenario.duration}
|
|
92
|
+
</Box>
|
|
93
|
+
|
|
94
|
+
{isRepeat && (
|
|
95
|
+
<Box className={styles.durationBadge} data-difficulty={scenario.difficulty}>Repeat</Box>
|
|
96
|
+
)}
|
|
97
|
+
</VStack>
|
|
98
|
+
|
|
99
|
+
{/* Re-queue switch row (completed tab only) */}
|
|
100
|
+
{showRequeueSwitch && (
|
|
101
|
+
<HStack className={styles.switchRow} justify="space-between">
|
|
102
|
+
<span className={styles.switchLabel}>Re-queue</span>
|
|
103
|
+
<Switch.Root
|
|
104
|
+
size="sm"
|
|
105
|
+
defaultChecked
|
|
106
|
+
onCheckedChange={(details) => {
|
|
107
|
+
if (!details.checked) onRequeue?.(scenario.id)
|
|
108
|
+
}}
|
|
109
|
+
aria-label={`Re-queue ${scenario.title}`}
|
|
110
|
+
>
|
|
111
|
+
<Switch.HiddenInput />
|
|
112
|
+
<Switch.Control css={{ _checked: { bg: 'secondary.6' } }} />
|
|
113
|
+
</Switch.Root>
|
|
114
|
+
</HStack>
|
|
115
|
+
)}
|
|
116
|
+
</VStack>
|
|
117
|
+
</Card.Root>
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useSortable } from '@dnd-kit/react/sortable'
|
|
3
|
+
import { ScenarioCard } from './ScenarioCard'
|
|
4
|
+
import type { Scenario } from './types'
|
|
5
|
+
|
|
6
|
+
interface ScenarioCardDraggableProps {
|
|
7
|
+
scenario: Scenario
|
|
8
|
+
/** 0-based index in the current list (required by useSortable) */
|
|
9
|
+
index: number
|
|
10
|
+
/** 1-based display position shown in the position badge */
|
|
11
|
+
position: number
|
|
12
|
+
/** Whether this is the active (first) card */
|
|
13
|
+
isActive: boolean
|
|
14
|
+
/** Whether this card was previously completed and re-queued */
|
|
15
|
+
isRepeat?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ScenarioCardDraggable({
|
|
19
|
+
scenario,
|
|
20
|
+
index,
|
|
21
|
+
position,
|
|
22
|
+
isActive,
|
|
23
|
+
isRepeat,
|
|
24
|
+
}: ScenarioCardDraggableProps) {
|
|
25
|
+
const { ref, handleRef, isDragging } = useSortable({
|
|
26
|
+
id: scenario.id,
|
|
27
|
+
index,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ScenarioCard
|
|
32
|
+
scenario={scenario}
|
|
33
|
+
position={position}
|
|
34
|
+
isActive={isActive}
|
|
35
|
+
isDragging={isDragging}
|
|
36
|
+
isRepeat={isRepeat}
|
|
37
|
+
rootRef={ref}
|
|
38
|
+
handleRef={handleRef}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|