@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.
Files changed (88) hide show
  1. package/dist/{chunk-QC44JPCA.cjs → chunk-ABC7N32K.cjs} +316 -10
  2. package/dist/chunk-ABC7N32K.cjs.map +1 -0
  3. package/dist/{chunk-F7LHARS4.js → chunk-GD6Q2FUE.js} +446 -6
  4. package/dist/chunk-GD6Q2FUE.js.map +1 -0
  5. package/dist/{chunk-M7J7WKJY.js → chunk-SBKRSXSZ.js} +317 -11
  6. package/dist/chunk-SBKRSXSZ.js.map +1 -0
  7. package/dist/{chunk-QP4EJI3G.cjs → chunk-UNWXE6UB.cjs} +450 -2
  8. package/dist/chunk-UNWXE6UB.cjs.map +1 -0
  9. package/dist/components/Breadcrumb.d.ts +9 -0
  10. package/dist/components/Breadcrumb.d.ts.map +1 -0
  11. package/dist/components/Checkbox.d.ts +6 -6
  12. package/dist/components/Icons/ClockIcon.d.ts +6 -0
  13. package/dist/components/Icons/ClockIcon.d.ts.map +1 -0
  14. package/dist/components/Icons/GripDotsVerticalIcon.d.ts +6 -0
  15. package/dist/components/Icons/GripDotsVerticalIcon.d.ts.map +1 -0
  16. package/dist/components/Icons/index.d.ts +3 -0
  17. package/dist/components/Icons/index.d.ts.map +1 -0
  18. package/dist/components/ScenarioQueue/AddScenarioDialog.d.ts +16 -0
  19. package/dist/components/ScenarioQueue/AddScenarioDialog.d.ts.map +1 -0
  20. package/dist/components/ScenarioQueue/ScenarioCard.d.ts +10 -0
  21. package/dist/components/ScenarioQueue/ScenarioCard.d.ts.map +1 -0
  22. package/dist/components/ScenarioQueue/ScenarioCardDraggable.d.ts +15 -0
  23. package/dist/components/ScenarioQueue/ScenarioCardDraggable.d.ts.map +1 -0
  24. package/dist/components/ScenarioQueue/ScenarioQueue.d.ts +3 -0
  25. package/dist/components/ScenarioQueue/ScenarioQueue.d.ts.map +1 -0
  26. package/dist/components/ScenarioQueue/index.d.ts +6 -0
  27. package/dist/components/ScenarioQueue/index.d.ts.map +1 -0
  28. package/dist/components/ScenarioQueue/types.d.ts +56 -0
  29. package/dist/components/ScenarioQueue/types.d.ts.map +1 -0
  30. package/dist/components/index.cjs +65 -33
  31. package/dist/components/index.d.ts +4 -0
  32. package/dist/components/index.d.ts.map +1 -1
  33. package/dist/components/index.js +1 -1
  34. package/dist/index.cjs +69 -37
  35. package/dist/index.js +2 -2
  36. package/dist/preset/index.cjs +2 -2
  37. package/dist/preset/index.d.ts.map +1 -1
  38. package/dist/preset/index.js +1 -1
  39. package/dist/preset/recipes/avatar.d.ts.map +1 -1
  40. package/dist/preset/recipes/breadcrumb.d.ts +2 -0
  41. package/dist/preset/recipes/breadcrumb.d.ts.map +1 -0
  42. package/dist/preset/recipes/checkbox.d.ts.map +1 -1
  43. package/dist/preset/recipes/field.d.ts.map +1 -1
  44. package/dist/preset/recipes/index.d.ts +3 -0
  45. package/dist/preset/recipes/index.d.ts.map +1 -1
  46. package/dist/preset/recipes/progress.d.ts.map +1 -1
  47. package/dist/preset/recipes/radio-group.d.ts.map +1 -1
  48. package/dist/preset/recipes/scenario-card.d.ts +2 -0
  49. package/dist/preset/recipes/scenario-card.d.ts.map +1 -0
  50. package/dist/preset/recipes/scenario-queue.d.ts +2 -0
  51. package/dist/preset/recipes/scenario-queue.d.ts.map +1 -0
  52. package/dist/preset/recipes/steps.d.ts.map +1 -1
  53. package/dist/preset/recipes/toast.d.ts.map +1 -1
  54. package/dist/preset/recipes/tooltip.d.ts.map +1 -1
  55. package/dist/preset/semantic-tokens.d.ts +12 -0
  56. package/dist/preset/semantic-tokens.d.ts.map +1 -1
  57. package/package.json +10 -1
  58. package/src/components/Breadcrumb.tsx +34 -0
  59. package/src/components/Icons/ClockIcon.tsx +40 -0
  60. package/src/components/Icons/GripDotsVerticalIcon.tsx +26 -0
  61. package/src/components/Icons/index.ts +2 -0
  62. package/src/components/ScenarioQueue/AddScenarioDialog.tsx +137 -0
  63. package/src/components/ScenarioQueue/ScenarioCard.tsx +120 -0
  64. package/src/components/ScenarioQueue/ScenarioCardDraggable.tsx +41 -0
  65. package/src/components/ScenarioQueue/ScenarioQueue.test.tsx +398 -0
  66. package/src/components/ScenarioQueue/ScenarioQueue.tsx +162 -0
  67. package/src/components/ScenarioQueue/index.ts +11 -0
  68. package/src/components/ScenarioQueue/types.ts +86 -0
  69. package/src/components/index.ts +19 -0
  70. package/src/preset/index.ts +9 -0
  71. package/src/preset/recipes/avatar.ts +1 -2
  72. package/src/preset/recipes/breadcrumb.ts +77 -0
  73. package/src/preset/recipes/checkbox.ts +1 -2
  74. package/src/preset/recipes/field.ts +1 -2
  75. package/src/preset/recipes/index.ts +7 -0
  76. package/src/preset/recipes/progress.ts +1 -2
  77. package/src/preset/recipes/radio-group.ts +1 -2
  78. package/src/preset/recipes/scenario-card.ts +151 -0
  79. package/src/preset/recipes/scenario-queue.ts +99 -0
  80. package/src/preset/recipes/steps.ts +1 -2
  81. package/src/preset/recipes/toast.ts +1 -2
  82. package/src/preset/recipes/tooltip.ts +1 -2
  83. package/src/preset/semantic-tokens.ts +4 -0
  84. package/src/test/setup.ts +12 -0
  85. package/dist/chunk-F7LHARS4.js.map +0 -1
  86. package/dist/chunk-M7J7WKJY.js.map +0 -1
  87. package/dist/chunk-QC44JPCA.cjs.map +0 -1
  88. 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,2 @@
1
+ export { GripDotsVerticalIcon, type GripDotsVerticalIconProps } from './GripDotsVerticalIcon'
2
+ export { ClockIcon, type ClockIconProps } from './ClockIcon'
@@ -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
+ }