@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.
@@ -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
- 'z-50 max-w-xs',
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