@proteos/ui 0.60.0 → 0.75.0
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/index.cjs +416 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -13
- package/dist/index.d.ts +308 -13
- package/dist/index.js +414 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/availability-calendar/availability-calendar.stories.tsx +78 -0
- package/src/components/availability-calendar/availability-calendar.test.tsx +89 -0
- package/src/components/availability-calendar/availability-calendar.tsx +182 -0
- package/src/components/availability-calendar/index.ts +5 -0
- package/src/components/conversation-brief-card/conversation-brief-preview.tsx +176 -47
- package/src/components/conversation-brief-card/conversation-brief-review.test.tsx +58 -1
- package/src/components/data-table/data-table.test.tsx +24 -0
- package/src/components/data-table/data-table.tsx +9 -0
- package/src/components/stepper/index.ts +8 -0
- package/src/components/stepper/stepper.stories.tsx +84 -0
- package/src/components/stepper/stepper.test.tsx +79 -0
- package/src/components/stepper/stepper.tsx +127 -0
- package/src/components/time-slot-list/index.ts +2 -0
- package/src/components/time-slot-list/time-slot-list.stories.tsx +100 -0
- package/src/components/time-slot-list/time-slot-list.test.tsx +120 -0
- package/src/components/time-slot-list/time-slot-list.tsx +154 -0
- package/src/components/tooltip/tooltip.tsx +4 -1
- package/src/index.ts +3 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ToggleButton as AriaToggleButton,
|
|
4
|
+
ToggleButtonGroup as AriaToggleButtonGroup,
|
|
5
|
+
type ToggleButtonGroupProps as AriaToggleButtonGroupProps,
|
|
6
|
+
type Key,
|
|
7
|
+
} from 'react-aria-components'
|
|
8
|
+
import { tv, type VariantProps } from 'tailwind-variants'
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../lib/cn'
|
|
11
|
+
import { Button } from '../button/button'
|
|
12
|
+
|
|
13
|
+
/* =========================================================================
|
|
14
|
+
TimeSlotList — a column of exclusive picks: free times on a day, shifts,
|
|
15
|
+
delivery windows. Each item is a full-width outlined button; the chosen
|
|
16
|
+
one carries an ink ring and may grow an inline confirm button beside it
|
|
17
|
+
(the "pick and go" register). Wraps RAC ToggleButtonGroup in single
|
|
18
|
+
selection so the group is one radiogroup with arrow-key movement and one
|
|
19
|
+
tab stop — and so a confirm button can sit beside an item without nesting
|
|
20
|
+
interactive content inside an option.
|
|
21
|
+
|
|
22
|
+
Labels are plain strings: formatting the time (12h / 24h, zone) is the
|
|
23
|
+
consumer's job; the list only lays the picks out.
|
|
24
|
+
========================================================================= */
|
|
25
|
+
|
|
26
|
+
export const timeSlotListStyles = tv({
|
|
27
|
+
slots: {
|
|
28
|
+
group: 'flex flex-col gap-2',
|
|
29
|
+
row: 'flex gap-2 min-w-0',
|
|
30
|
+
item: [
|
|
31
|
+
'flex-1 min-h-9 px-3 inline-flex items-center justify-center cursor-pointer outline-none',
|
|
32
|
+
'font-sans text-[13.5px] font-medium text-ink rounded-md',
|
|
33
|
+
'bg-surface border border-rule-strong',
|
|
34
|
+
'transition-[background-color,color,border-color,box-shadow] duration-150',
|
|
35
|
+
'hovered:bg-surface-2 hovered:border-ink-3',
|
|
36
|
+
'pressed:bg-surface-3',
|
|
37
|
+
'selected:border-ink selected:bg-surface selected:shadow-[inset_0_0_0_1px_var(--color-ink)]',
|
|
38
|
+
'selected:hovered:bg-surface',
|
|
39
|
+
'data-[focus-visible]:shadow-[var(--shadow-focus)]',
|
|
40
|
+
'selected:data-[focus-visible]:shadow-[var(--shadow-focus),inset_0_0_0_1px_var(--color-ink)]',
|
|
41
|
+
// Taken while you looked: dashed and quiet.
|
|
42
|
+
'data-[disabled]:border-dashed data-[disabled]:border-rule data-[disabled]:text-ink-4',
|
|
43
|
+
'data-[disabled]:cursor-default data-[disabled]:bg-transparent',
|
|
44
|
+
],
|
|
45
|
+
confirm: 'flex-1 min-h-9',
|
|
46
|
+
empty: [
|
|
47
|
+
'rounded-md border border-rule-2 bg-surface-2 px-4 py-5 text-center',
|
|
48
|
+
'font-sans text-[13px] text-ink-3',
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
variants: {
|
|
52
|
+
columns: {
|
|
53
|
+
1: { group: 'grid grid-cols-1' },
|
|
54
|
+
2: { group: 'grid grid-cols-2' },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
defaultVariants: { columns: 1 },
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export type TimeSlotListVariants = VariantProps<typeof timeSlotListStyles>
|
|
61
|
+
|
|
62
|
+
export type TimeSlotListItem<T extends Key = Key> = {
|
|
63
|
+
id: T
|
|
64
|
+
label: ReactNode
|
|
65
|
+
/** No longer offered (taken, outside the window): rendered dashed, not pickable. */
|
|
66
|
+
isDisabled?: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type TimeSlotListProps<T extends Key = Key> = Omit<
|
|
70
|
+
AriaToggleButtonGroupProps,
|
|
71
|
+
'children' | 'selectionMode' | 'selectedKeys' | 'onSelectionChange' | 'className' | 'style'
|
|
72
|
+
> & {
|
|
73
|
+
items: ReadonlyArray<TimeSlotListItem<T>>
|
|
74
|
+
/** `null` = nothing picked. */
|
|
75
|
+
selectedId: T | null
|
|
76
|
+
onSelect: (id: T) => void
|
|
77
|
+
/** Renders a cta beside the selected item; pressing it calls `onConfirm`. */
|
|
78
|
+
confirmLabel?: string
|
|
79
|
+
onConfirm?: (id: T) => void
|
|
80
|
+
/** Shown instead of the list when `items` is empty. */
|
|
81
|
+
emptyMessage?: ReactNode
|
|
82
|
+
columns?: TimeSlotListVariants['columns']
|
|
83
|
+
/** Focus the selected item (or the first) on mount. */
|
|
84
|
+
autoFocus?: boolean
|
|
85
|
+
/** Freeze every pick and the confirm while a request is in flight. */
|
|
86
|
+
isDisabled?: boolean
|
|
87
|
+
className?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function TimeSlotList<T extends Key>({
|
|
91
|
+
items,
|
|
92
|
+
selectedId,
|
|
93
|
+
onSelect,
|
|
94
|
+
confirmLabel,
|
|
95
|
+
onConfirm,
|
|
96
|
+
emptyMessage = 'Nothing available.',
|
|
97
|
+
columns,
|
|
98
|
+
autoFocus = false,
|
|
99
|
+
isDisabled = false,
|
|
100
|
+
className,
|
|
101
|
+
orientation = 'vertical',
|
|
102
|
+
...props
|
|
103
|
+
}: TimeSlotListProps<T>) {
|
|
104
|
+
const styles = timeSlotListStyles({ columns })
|
|
105
|
+
if (items.length === 0) {
|
|
106
|
+
return (
|
|
107
|
+
<div role="status" className={cn(styles.empty(), className)}>
|
|
108
|
+
{emptyMessage}
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
const focusTarget = selectedId ?? items.find((item) => !item.isDisabled)?.id ?? null
|
|
113
|
+
return (
|
|
114
|
+
<AriaToggleButtonGroup
|
|
115
|
+
{...props}
|
|
116
|
+
orientation={orientation}
|
|
117
|
+
selectionMode="single"
|
|
118
|
+
disallowEmptySelection
|
|
119
|
+
isDisabled={isDisabled}
|
|
120
|
+
selectedKeys={selectedId === null ? new Set() : new Set([selectedId])}
|
|
121
|
+
onSelectionChange={(keys) => {
|
|
122
|
+
const [next] = Array.from(keys)
|
|
123
|
+
if (next !== undefined) onSelect(next as T)
|
|
124
|
+
}}
|
|
125
|
+
className={cn(styles.group(), className)}
|
|
126
|
+
>
|
|
127
|
+
{items.map((item) => {
|
|
128
|
+
const isSelected = item.id === selectedId
|
|
129
|
+
return (
|
|
130
|
+
<div key={String(item.id)} className={styles.row()}>
|
|
131
|
+
<AriaToggleButton
|
|
132
|
+
id={item.id}
|
|
133
|
+
{...(item.isDisabled ? { isDisabled: true } : {})}
|
|
134
|
+
{...(autoFocus && item.id === focusTarget ? { autoFocus: true } : {})}
|
|
135
|
+
className={styles.item()}
|
|
136
|
+
>
|
|
137
|
+
{item.label}
|
|
138
|
+
</AriaToggleButton>
|
|
139
|
+
{isSelected && confirmLabel && (
|
|
140
|
+
<Button
|
|
141
|
+
variant="cta"
|
|
142
|
+
className={styles.confirm()}
|
|
143
|
+
isDisabled={isDisabled}
|
|
144
|
+
onPress={() => onConfirm?.(item.id)}
|
|
145
|
+
>
|
|
146
|
+
{confirmLabel}
|
|
147
|
+
</Button>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
})}
|
|
152
|
+
</AriaToggleButtonGroup>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -42,7 +42,10 @@ export function Tooltip({ className, offset = 6, ...props }: TooltipProps) {
|
|
|
42
42
|
offset={offset}
|
|
43
43
|
className={(renderProps) =>
|
|
44
44
|
cn(
|
|
45
|
-
|
|
45
|
+
// Above the floating chat / softphone windows (1100 / 1200) and their
|
|
46
|
+
// full-screen workspace, like Select and Toast — a page inside the
|
|
47
|
+
// window keeps its tooltips.
|
|
48
|
+
'z-[1300] max-w-xs',
|
|
46
49
|
'rounded-md border border-rule bg-surface shadow-[var(--shadow-raised)]',
|
|
47
50
|
'px-2.5 py-1.5',
|
|
48
51
|
'font-mono text-[11.5px] text-ink-2',
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './components/attachment-chip'
|
|
2
2
|
export * from './components/attribute-type-icon'
|
|
3
|
+
export * from './components/availability-calendar'
|
|
3
4
|
export * from './components/avatar'
|
|
4
5
|
export * from './components/avatar-group'
|
|
5
6
|
export * from './components/badge'
|
|
@@ -47,6 +48,7 @@ export * from './components/segmented-control'
|
|
|
47
48
|
export * from './components/select'
|
|
48
49
|
export * from './components/separator'
|
|
49
50
|
export * from './components/skeleton'
|
|
51
|
+
export * from './components/stepper'
|
|
50
52
|
export * from './components/success-animation'
|
|
51
53
|
export * from './components/switch'
|
|
52
54
|
export * from './components/table'
|
|
@@ -55,6 +57,7 @@ export * from './components/tag-input'
|
|
|
55
57
|
export * from './components/text-shimmer'
|
|
56
58
|
export * from './components/textarea'
|
|
57
59
|
export * from './components/time-field'
|
|
60
|
+
export * from './components/time-slot-list'
|
|
58
61
|
export * from './components/toast'
|
|
59
62
|
export * from './components/tooltip'
|
|
60
63
|
|