@licklist/design 0.78.5-dev.69 → 0.78.5-dev.70

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 (123) hide show
  1. package/dist/styles/themes/bookedit/_fonts.scss +2 -0
  2. package/dist/v2/components/ActionMenu/ActionMenu.d.ts.map +1 -1
  3. package/dist/v2/components/ActionMenu/ActionMenu.js +5 -3
  4. package/dist/v2/components/AvatarUpload/AvatarUpload.d.ts +12 -0
  5. package/dist/v2/components/AvatarUpload/AvatarUpload.d.ts.map +1 -0
  6. package/dist/v2/components/AvatarUpload/index.d.ts +2 -0
  7. package/dist/v2/components/AvatarUpload/index.d.ts.map +1 -0
  8. package/dist/v2/components/Button/Button.d.ts +1 -1
  9. package/dist/v2/components/Button/Button.d.ts.map +1 -1
  10. package/dist/v2/components/Button/Button.scss.js +1 -1
  11. package/dist/v2/components/DataTable/DataTable.d.ts +41 -0
  12. package/dist/v2/components/DataTable/DataTable.d.ts.map +1 -0
  13. package/dist/v2/components/DataTable/index.d.ts +3 -0
  14. package/dist/v2/components/DataTable/index.d.ts.map +1 -0
  15. package/dist/v2/components/EmptyState/EmptyState.d.ts +14 -0
  16. package/dist/v2/components/EmptyState/EmptyState.d.ts.map +1 -0
  17. package/dist/v2/components/EmptyState/index.d.ts +3 -0
  18. package/dist/v2/components/EmptyState/index.d.ts.map +1 -0
  19. package/dist/v2/components/FormField/FormField.scss.js +1 -1
  20. package/dist/v2/components/InfoGrid/InfoGrid.d.ts +13 -0
  21. package/dist/v2/components/InfoGrid/InfoGrid.d.ts.map +1 -0
  22. package/dist/v2/components/InfoGrid/index.d.ts +2 -0
  23. package/dist/v2/components/InfoGrid/index.d.ts.map +1 -0
  24. package/dist/v2/components/NewTable/NewTable.scss.js +1 -1
  25. package/dist/v2/components/RadioCard/RadioCard.d.ts +17 -0
  26. package/dist/v2/components/RadioCard/RadioCard.d.ts.map +1 -0
  27. package/dist/v2/components/RadioCard/index.d.ts +2 -0
  28. package/dist/v2/components/RadioCard/index.d.ts.map +1 -0
  29. package/dist/v2/components/StatusBadge/StatusBadge.d.ts +8 -0
  30. package/dist/v2/components/StatusBadge/StatusBadge.d.ts.map +1 -0
  31. package/dist/v2/components/StatusBadge/index.d.ts +3 -0
  32. package/dist/v2/components/StatusBadge/index.d.ts.map +1 -0
  33. package/dist/v2/components/StepIndicator/StepIndicator.d.ts +9 -0
  34. package/dist/v2/components/StepIndicator/StepIndicator.d.ts.map +1 -0
  35. package/dist/v2/components/StepIndicator/index.d.ts +2 -0
  36. package/dist/v2/components/StepIndicator/index.d.ts.map +1 -0
  37. package/dist/v2/components/TableControls/TableControls.d.ts +28 -0
  38. package/dist/v2/components/TableControls/TableControls.d.ts.map +1 -0
  39. package/dist/v2/components/TableControls/index.d.ts +3 -0
  40. package/dist/v2/components/TableControls/index.d.ts.map +1 -0
  41. package/dist/v2/components/Tabs/Tabs.d.ts +15 -0
  42. package/dist/v2/components/Tabs/Tabs.d.ts.map +1 -0
  43. package/dist/v2/components/Tabs/index.d.ts +3 -0
  44. package/dist/v2/components/Tabs/index.d.ts.map +1 -0
  45. package/dist/v2/icons/index.d.ts +42 -0
  46. package/dist/v2/icons/index.d.ts.map +1 -1
  47. package/dist/v2/index.d.ts +18 -0
  48. package/dist/v2/index.d.ts.map +1 -1
  49. package/dist/v2/pages/CreateUser/CreateUserPage.d.ts +110 -0
  50. package/dist/v2/pages/CreateUser/CreateUserPage.d.ts.map +1 -0
  51. package/dist/v2/pages/CreateUser/index.d.ts +3 -0
  52. package/dist/v2/pages/CreateUser/index.d.ts.map +1 -0
  53. package/dist/v2/pages/RoleSelection/RoleSelectionPage.d.ts +26 -0
  54. package/dist/v2/pages/RoleSelection/RoleSelectionPage.d.ts.map +1 -0
  55. package/dist/v2/pages/RoleSelection/index.d.ts +3 -0
  56. package/dist/v2/pages/RoleSelection/index.d.ts.map +1 -0
  57. package/dist/v2/pages/UserDetails/UserDetailsPage.d.ts +37 -0
  58. package/dist/v2/pages/UserDetails/UserDetailsPage.d.ts.map +1 -0
  59. package/dist/v2/pages/UserDetails/index.d.ts +3 -0
  60. package/dist/v2/pages/UserDetails/index.d.ts.map +1 -0
  61. package/dist/v2/pages/auth/CreatePassword/CreatePasswordPage.d.ts.map +1 -1
  62. package/dist/v2/pages/auth/Login/LoginPage.d.ts.map +1 -1
  63. package/dist/v2/pages/auth/ResetPassword/ResetPasswordPage.d.ts.map +1 -1
  64. package/dist/v2/styles/components/Button.scss +27 -0
  65. package/package.json +2 -2
  66. package/src/styles/themes/bookedit/_fonts.scss +2 -0
  67. package/src/v2/components/ActionMenu/ActionMenu.tsx +4 -2
  68. package/src/v2/components/AvatarUpload/AvatarUpload.scss +68 -0
  69. package/src/v2/components/AvatarUpload/AvatarUpload.stories.tsx +83 -0
  70. package/src/v2/components/AvatarUpload/AvatarUpload.tsx +69 -0
  71. package/src/v2/components/AvatarUpload/index.ts +1 -0
  72. package/src/v2/components/Button/Button.tsx +1 -0
  73. package/src/v2/components/DataTable/DataTable.scss +181 -0
  74. package/src/v2/components/DataTable/DataTable.tsx +256 -0
  75. package/src/v2/components/DataTable/index.ts +7 -0
  76. package/src/v2/components/EmptyState/EmptyState.scss +39 -0
  77. package/src/v2/components/EmptyState/EmptyState.stories.tsx +45 -0
  78. package/src/v2/components/EmptyState/EmptyState.tsx +37 -0
  79. package/src/v2/components/EmptyState/index.ts +2 -0
  80. package/src/v2/components/FormField/FormField.scss +12 -0
  81. package/src/v2/components/InfoGrid/InfoGrid.scss +51 -0
  82. package/src/v2/components/InfoGrid/InfoGrid.stories.tsx +76 -0
  83. package/src/v2/components/InfoGrid/InfoGrid.tsx +28 -0
  84. package/src/v2/components/InfoGrid/index.ts +1 -0
  85. package/src/v2/components/NewTable/NewTable.scss +4 -4
  86. package/src/v2/components/RadioCard/RadioCard.scss +76 -0
  87. package/src/v2/components/RadioCard/RadioCard.stories.tsx +115 -0
  88. package/src/v2/components/RadioCard/RadioCard.tsx +68 -0
  89. package/src/v2/components/RadioCard/index.ts +1 -0
  90. package/src/v2/components/StatusBadge/StatusBadge.scss +53 -0
  91. package/src/v2/components/StatusBadge/StatusBadge.tsx +31 -0
  92. package/src/v2/components/StatusBadge/index.ts +2 -0
  93. package/src/v2/components/StepIndicator/StepIndicator.scss +62 -0
  94. package/src/v2/components/StepIndicator/StepIndicator.stories.tsx +37 -0
  95. package/src/v2/components/StepIndicator/StepIndicator.tsx +41 -0
  96. package/src/v2/components/StepIndicator/index.ts +1 -0
  97. package/src/v2/components/TableControls/TableControls.scss +63 -0
  98. package/src/v2/components/TableControls/TableControls.tsx +110 -0
  99. package/src/v2/components/TableControls/index.ts +7 -0
  100. package/src/v2/components/Tabs/Tabs.scss +36 -0
  101. package/src/v2/components/Tabs/Tabs.stories.tsx +75 -0
  102. package/src/v2/components/Tabs/Tabs.tsx +52 -0
  103. package/src/v2/components/Tabs/index.ts +2 -0
  104. package/src/v2/icons/index.tsx +219 -0
  105. package/src/v2/index.ts +98 -0
  106. package/src/v2/pages/CreateUser/CreateUserPage.scss +760 -0
  107. package/src/v2/pages/CreateUser/CreateUserPage.stories.tsx +157 -0
  108. package/src/v2/pages/CreateUser/CreateUserPage.tsx +1062 -0
  109. package/src/v2/pages/CreateUser/index.ts +13 -0
  110. package/src/v2/pages/RoleSelection/RoleSelectionPage.scss +193 -0
  111. package/src/v2/pages/RoleSelection/RoleSelectionPage.stories.tsx +112 -0
  112. package/src/v2/pages/RoleSelection/RoleSelectionPage.tsx +127 -0
  113. package/src/v2/pages/RoleSelection/index.ts +2 -0
  114. package/src/v2/pages/UserDetails/UserDetailsPage.scss +236 -0
  115. package/src/v2/pages/UserDetails/UserDetailsPage.stories.tsx +84 -0
  116. package/src/v2/pages/UserDetails/UserDetailsPage.tsx +210 -0
  117. package/src/v2/pages/UserDetails/index.ts +2 -0
  118. package/src/v2/pages/auth/AuthLayout/AuthLayout.scss +8 -6
  119. package/src/v2/pages/auth/CreatePassword/CreatePasswordPage.tsx +1 -3
  120. package/src/v2/pages/auth/Login/LoginPage.tsx +1 -3
  121. package/src/v2/pages/auth/ResetPassword/ResetPasswordPage.scss +2 -0
  122. package/src/v2/pages/auth/ResetPassword/ResetPasswordPage.tsx +1 -2
  123. package/src/v2/styles/components/Button.scss +27 -0
@@ -0,0 +1,181 @@
1
+ @import '../../styles/index.scss';
2
+
3
+ .data-table {
4
+ display: flex;
5
+ flex-direction: column;
6
+ width: 100%;
7
+ overflow: visible;
8
+
9
+ // ── Header row ─────────────────────────────────────────────────────────────
10
+ &__header {
11
+ display: flex;
12
+ height: 48px;
13
+ align-items: center;
14
+ align-self: stretch;
15
+ background: var(--surface-secondary, #f8f8fa);
16
+ padding: 0 16px;
17
+ border-bottom: 1px solid var(--border-primary, #e8e9ef);
18
+ gap: 16px;
19
+ min-width: 0;
20
+ }
21
+
22
+ &__th {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 4px;
26
+ font-size: 13px;
27
+ font-weight: 500;
28
+ line-height: 16px;
29
+ color: var(--label-secondary, #626a90);
30
+ white-space: nowrap;
31
+ flex-shrink: 0;
32
+
33
+ &--flex1 {
34
+ flex: 1;
35
+ min-width: 0;
36
+ }
37
+
38
+ &--sortable {
39
+ cursor: pointer;
40
+ transition: color 0.15s;
41
+ user-select: none;
42
+
43
+ &:hover {
44
+ color: var(--label-primary, #121e52);
45
+ }
46
+ }
47
+
48
+ &--action {
49
+ width: 40px;
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ &--checkbox {
54
+ width: 40px;
55
+ flex-shrink: 0;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ }
60
+ }
61
+
62
+ &__sort-icon {
63
+ display: flex;
64
+ align-items: center;
65
+ flex-shrink: 0;
66
+
67
+ svg {
68
+ width: 14px;
69
+ height: 14px;
70
+ }
71
+
72
+ &--hidden {
73
+ opacity: 0;
74
+ }
75
+ }
76
+
77
+ // ── Body ───────────────────────────────────────────────────────────────────
78
+ &__body {
79
+ display: flex;
80
+ flex-direction: column;
81
+ position: relative;
82
+ overflow: visible;
83
+ }
84
+
85
+ &__row {
86
+ display: flex;
87
+ align-items: center;
88
+ align-self: stretch;
89
+ border-bottom: 1px solid var(--border-primary, #e8e9ef);
90
+ transition: background-color 0.15s;
91
+
92
+ &:last-child {
93
+ border-bottom: none;
94
+ }
95
+
96
+ &--clickable {
97
+ cursor: pointer;
98
+
99
+ &:hover {
100
+ background: var(--surface-primary-hover, #f8f8fa);
101
+ }
102
+ }
103
+
104
+ // When ActionMenu is open, ensure dropdown isn't clipped
105
+ &.action-menu-open {
106
+ overflow: visible;
107
+ position: relative;
108
+ z-index: 20;
109
+
110
+ .data-table__row-cells {
111
+ overflow: visible;
112
+ }
113
+ }
114
+ }
115
+
116
+ &__row-cells {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 16px;
120
+ flex: 1;
121
+ min-width: 0;
122
+ overflow: hidden;
123
+ padding: 16px;
124
+ }
125
+
126
+ &__td {
127
+ font-size: 13px;
128
+ font-weight: 400;
129
+ line-height: 16px;
130
+ color: var(--label-primary, #121e52);
131
+ flex-shrink: 0;
132
+
133
+ &--flex1 {
134
+ flex: 1;
135
+ min-width: 0;
136
+ }
137
+
138
+ &--action {
139
+ width: 40px;
140
+ flex-shrink: 0;
141
+ display: flex;
142
+ justify-content: flex-end;
143
+ }
144
+
145
+ &--checkbox {
146
+ width: 40px;
147
+ flex-shrink: 0;
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ }
152
+ }
153
+
154
+ // ── Checkbox ──────────────────────────────────────────────────────────────────
155
+ &__checkbox {
156
+ width: 16px;
157
+ height: 16px;
158
+ flex-shrink: 0;
159
+ cursor: pointer;
160
+ accent-color: var(--label-action, #6200ee);
161
+ border-radius: 3px;
162
+ }
163
+
164
+ // ── Selected row ──────────────────────────────────────────────────────────────
165
+ &__row--selected {
166
+ background: var(--surface-action-soft, #f3e8ff);
167
+
168
+ &:hover {
169
+ background: var(--surface-action-soft, #f3e8ff);
170
+ }
171
+ }
172
+
173
+ // ── Empty state ─────────────────────────────────────────────────────────────
174
+ &__empty {
175
+ padding: 48px 16px;
176
+ text-align: center;
177
+ font-size: 14px;
178
+ font-weight: 500;
179
+ color: var(--label-secondary, #626a90);
180
+ }
181
+ }
@@ -0,0 +1,256 @@
1
+ import React, { useRef, useEffect } from 'react'
2
+ import { ActionMenu } from '../ActionMenu'
3
+ import { ArrowUpIcon, ArrowDownIcon } from '../../icons'
4
+ import './DataTable.scss'
5
+
6
+ // ─── Types ────────────────────────────────────────────────────────────────────
7
+
8
+ export type DataTableSortDirection = 'asc' | 'desc'
9
+
10
+ export interface DataTableColumn<T> {
11
+ label: string
12
+ accessor?: keyof T | ((item: T) => React.ReactNode)
13
+ render?: (item: T) => React.ReactNode
14
+ /** Fixed width, e.g. '140px', '100px'. Omit for flex-1 (first column default). */
15
+ width?: string
16
+ /** Applied to both header cell and body cell */
17
+ className?: string
18
+ /** Applied to header cell only */
19
+ labelClassName?: string
20
+ sortKey?: string
21
+ sortable?: boolean
22
+ }
23
+
24
+ export interface DataTableRowAction<T> {
25
+ label: string
26
+ onClick: (item: T) => void
27
+ variant?: 'default' | 'danger'
28
+ }
29
+
30
+ export interface DataTableProps<T> {
31
+ columns: DataTableColumn<T>[]
32
+ data: T[]
33
+ keyExtractor: (item: T) => string
34
+ onRowClick?: (item: T) => void
35
+ rowActions?: (item: T) => DataTableRowAction<T>[]
36
+ emptyState?: React.ReactNode
37
+ className?: string
38
+ sortKey?: string
39
+ sortDirection?: DataTableSortDirection
40
+ onSort?: (key: string) => void
41
+ /** Enable row checkboxes for multi-select */
42
+ selectable?: boolean
43
+ /** Currently selected row keys */
44
+ selectedKeys?: string[]
45
+ /** Called when selection changes */
46
+ onSelectionChange?: (keys: string[]) => void
47
+ }
48
+
49
+ // ─── Header checkbox (handles indeterminate state) ────────────────────────────
50
+
51
+ interface HeaderCheckboxProps {
52
+ checked: boolean
53
+ indeterminate: boolean
54
+ onChange: () => void
55
+ }
56
+
57
+ function HeaderCheckbox({ checked, indeterminate, onChange }: HeaderCheckboxProps) {
58
+ const ref = useRef<HTMLInputElement>(null)
59
+ useEffect(() => {
60
+ if (ref.current) ref.current.indeterminate = indeterminate
61
+ }, [indeterminate])
62
+ return (
63
+ <input
64
+ type="checkbox"
65
+ ref={ref}
66
+ checked={checked}
67
+ onChange={onChange}
68
+ className="data-table__checkbox"
69
+ onClick={(e) => e.stopPropagation()}
70
+ />
71
+ )
72
+ }
73
+
74
+ // ─── Component ────────────────────────────────────────────────────────────────
75
+
76
+ export function DataTable<T>({
77
+ columns,
78
+ data,
79
+ keyExtractor,
80
+ onRowClick,
81
+ rowActions,
82
+ emptyState,
83
+ className = '',
84
+ sortKey,
85
+ sortDirection,
86
+ onSort,
87
+ selectable = false,
88
+ selectedKeys = [],
89
+ onSelectionChange,
90
+ }: DataTableProps<T>) {
91
+ const allKeys = data.map(keyExtractor)
92
+ const allSelected = data.length > 0 && allKeys.every((k) => selectedKeys.includes(k))
93
+ const someSelected = selectedKeys.length > 0 && !allSelected
94
+
95
+ const toggleAll = () => {
96
+ if (!onSelectionChange) return
97
+ if (allSelected) {
98
+ onSelectionChange(selectedKeys.filter((k) => !allKeys.includes(k)))
99
+ } else {
100
+ const newKeys = [...selectedKeys]
101
+ allKeys.forEach((k) => { if (!newKeys.includes(k)) newKeys.push(k) })
102
+ onSelectionChange(newKeys)
103
+ }
104
+ }
105
+
106
+ const toggleRow = (key: string) => {
107
+ if (!onSelectionChange) return
108
+ if (selectedKeys.includes(key)) {
109
+ onSelectionChange(selectedKeys.filter((k) => k !== key))
110
+ } else {
111
+ onSelectionChange([...selectedKeys, key])
112
+ }
113
+ }
114
+
115
+ return (
116
+ <div className={`data-table ${className}`}>
117
+ {/* ── Header row ── */}
118
+ <div className="data-table__header">
119
+ {selectable && (
120
+ <div className="data-table__th data-table__th--checkbox">
121
+ <HeaderCheckbox
122
+ checked={allSelected}
123
+ indeterminate={someSelected}
124
+ onChange={toggleAll}
125
+ />
126
+ </div>
127
+ )}
128
+
129
+ {columns.map((col, i) => {
130
+ const isSorted = col.sortKey && sortKey === col.sortKey
131
+ const canSort = col.sortable && col.sortKey && onSort
132
+
133
+ return (
134
+ <div
135
+ key={i}
136
+ className={[
137
+ 'data-table__th',
138
+ !col.width && i === 0 ? 'data-table__th--flex1' : '',
139
+ col.labelClassName ?? '',
140
+ col.className ?? '',
141
+ canSort ? 'data-table__th--sortable' : '',
142
+ ]
143
+ .filter(Boolean)
144
+ .join(' ')}
145
+ style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
146
+ onClick={() => canSort && onSort!(col.sortKey!)}
147
+ >
148
+ {col.label}
149
+ {canSort && (
150
+ <span
151
+ className={`data-table__sort-icon ${!isSorted ? 'data-table__sort-icon--hidden' : ''}`}
152
+ >
153
+ {isSorted && sortDirection === 'desc' ? (
154
+ <ArrowDownIcon />
155
+ ) : (
156
+ <ArrowUpIcon />
157
+ )}
158
+ </span>
159
+ )}
160
+ </div>
161
+ )
162
+ })}
163
+ {rowActions && <div className="data-table__th data-table__th--action" />}
164
+ </div>
165
+
166
+ {/* ── Body ── */}
167
+ <div className="data-table__body">
168
+ {data.length === 0 ? (
169
+ <div className="data-table__empty">
170
+ {emptyState ?? 'No data found.'}
171
+ </div>
172
+ ) : (
173
+ data.map((item) => {
174
+ const key = keyExtractor(item)
175
+ const actions = rowActions?.(item)
176
+ const isSelected = selectedKeys.includes(key)
177
+
178
+ return (
179
+ <div
180
+ key={key}
181
+ className={[
182
+ 'data-table__row',
183
+ onRowClick ? 'data-table__row--clickable' : '',
184
+ isSelected ? 'data-table__row--selected' : '',
185
+ ]
186
+ .filter(Boolean)
187
+ .join(' ')}
188
+ onClick={() => onRowClick?.(item)}
189
+ >
190
+ {selectable && (
191
+ <div
192
+ className="data-table__td data-table__td--checkbox"
193
+ onClick={(e) => { e.stopPropagation(); toggleRow(key) }}
194
+ >
195
+ <input
196
+ type="checkbox"
197
+ className="data-table__checkbox"
198
+ checked={isSelected}
199
+ onChange={() => toggleRow(key)}
200
+ onClick={(e) => e.stopPropagation()}
201
+ />
202
+ </div>
203
+ )}
204
+
205
+ <div className="data-table__row-cells">
206
+ {columns.map((col, colIndex) => {
207
+ let content: React.ReactNode
208
+ if (col.render) {
209
+ content = col.render(item)
210
+ } else if (col.accessor) {
211
+ content =
212
+ typeof col.accessor === 'function'
213
+ ? col.accessor(item)
214
+ : String((item as Record<string, unknown>)[col.accessor as string] ?? '')
215
+ }
216
+
217
+ return (
218
+ <div
219
+ key={colIndex}
220
+ className={[
221
+ 'data-table__td',
222
+ !col.width && colIndex === 0 ? 'data-table__td--flex1' : '',
223
+ col.className ?? '',
224
+ ]
225
+ .filter(Boolean)
226
+ .join(' ')}
227
+ style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
228
+ >
229
+ {content}
230
+ </div>
231
+ )
232
+ })}
233
+
234
+ {actions && actions.length > 0 && (
235
+ <div
236
+ className="data-table__td data-table__td--action"
237
+ onClick={(e) => e.stopPropagation()}
238
+ >
239
+ <ActionMenu
240
+ items={actions.map((a) => ({
241
+ label: a.label,
242
+ onClick: () => a.onClick(item),
243
+ variant: a.variant,
244
+ }))}
245
+ />
246
+ </div>
247
+ )}
248
+ </div>
249
+ </div>
250
+ )
251
+ })
252
+ )}
253
+ </div>
254
+ </div>
255
+ )
256
+ }
@@ -0,0 +1,7 @@
1
+ export { DataTable } from './DataTable'
2
+ export type {
3
+ DataTableColumn,
4
+ DataTableRowAction,
5
+ DataTableProps,
6
+ DataTableSortDirection,
7
+ } from './DataTable'
@@ -0,0 +1,39 @@
1
+ @import '../../styles/index.scss';
2
+
3
+ .empty-state {
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ justify-content: center;
8
+ gap: 8px;
9
+ padding: 48px 24px;
10
+ text-align: center;
11
+
12
+ &__icon {
13
+ color: var(--label-tertiary, #9399b3);
14
+ margin-bottom: 8px;
15
+ width: 48px;
16
+ height: 48px;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ }
21
+
22
+ &__title {
23
+ font-size: 16px;
24
+ font-weight: 600;
25
+ color: var(--label-primary, #121e52);
26
+ margin: 0;
27
+ }
28
+
29
+ &__description {
30
+ font-size: 14px;
31
+ color: var(--label-secondary, #626a90);
32
+ max-width: 360px;
33
+ margin: 0;
34
+ }
35
+
36
+ &__action {
37
+ margin-top: 8px;
38
+ }
39
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react'
3
+ import { EmptyState } from './EmptyState'
4
+ import { PaymentsEmptyIcon } from '../../icons'
5
+
6
+ const meta: Meta<typeof EmptyState> = {
7
+ title: 'v2/Components/EmptyState',
8
+ component: EmptyState,
9
+ parameters: { layout: 'centered' },
10
+ }
11
+
12
+ export default meta
13
+
14
+ type Story = StoryObj<typeof EmptyState>
15
+
16
+ export const WithIcon: Story = {
17
+ args: {
18
+ icon: <PaymentsEmptyIcon />,
19
+ title: 'No payments yet',
20
+ description: 'Once payments are made they will appear here.',
21
+ },
22
+ }
23
+
24
+ export const TitleOnly: Story = {
25
+ args: {
26
+ title: 'Nothing to show',
27
+ },
28
+ }
29
+
30
+ export const WithAction: Story = {
31
+ args: {
32
+ icon: <PaymentsEmptyIcon />,
33
+ title: 'No results found',
34
+ description: 'Try adjusting your filters or create a new entry to get started.',
35
+ action: {
36
+ label: 'Create new',
37
+ onClick: () => {
38
+ setTimeout(() => {
39
+ // eslint-disable-next-line no-console
40
+ console.log('Create new clicked')
41
+ }, 300)
42
+ },
43
+ },
44
+ },
45
+ }
@@ -0,0 +1,37 @@
1
+ import React from 'react'
2
+ import './EmptyState.scss'
3
+ import { Button } from '../Button'
4
+
5
+ export interface EmptyStateProps {
6
+ icon?: React.ReactNode
7
+ title: string
8
+ description?: string
9
+ action?: {
10
+ label: string
11
+ onClick(): void
12
+ }
13
+ className?: string
14
+ }
15
+
16
+ export const EmptyState: React.FC<EmptyStateProps> = ({
17
+ icon,
18
+ title,
19
+ description,
20
+ action,
21
+ className = '',
22
+ }) => (
23
+ <div className={`empty-state ${className}`}>
24
+ {icon && <div className="empty-state__icon">{icon}</div>}
25
+ <p className="empty-state__title">{title}</p>
26
+ {description && (
27
+ <p className="empty-state__description">{description}</p>
28
+ )}
29
+ {action && (
30
+ <div className="empty-state__action">
31
+ <Button variant="primary" onClick={action.onClick}>
32
+ {action.label}
33
+ </Button>
34
+ </div>
35
+ )}
36
+ </div>
37
+ )
@@ -0,0 +1,2 @@
1
+ export { EmptyState } from './EmptyState'
2
+ export type { EmptyStateProps } from './EmptyState'
@@ -52,6 +52,17 @@
52
52
  background-color: var(--surface-secondary, #f8f8fa);
53
53
  }
54
54
 
55
+ // Reset browser native :valid styling (prevents Bootstrap/browser green state on blur)
56
+ &:valid {
57
+ border-color: var(--border-primary, #e8e9ef);
58
+ background-image: none;
59
+ }
60
+
61
+ &:valid:focus {
62
+ border-color: var(--border-selected, #6200EE);
63
+ background-image: none;
64
+ }
65
+
55
66
  &--error {
56
67
  border-color: var(--borders-status-border-error, #ef4444);
57
68
 
@@ -91,6 +102,7 @@
91
102
  }
92
103
 
93
104
  &__error-text {
105
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
94
106
  font-size: var(--text-xs-size, 11px);
95
107
  line-height: var(--text-xs-line, 14px);
96
108
  color: var(--label-status-error, #ef4444);
@@ -0,0 +1,51 @@
1
+ @import '../../styles/index.scss';
2
+
3
+ .info-grid {
4
+ display: grid;
5
+ gap: 16px;
6
+
7
+ &--cols-2 {
8
+ grid-template-columns: repeat(2, 1fr);
9
+ }
10
+
11
+ &--cols-3 {
12
+ grid-template-columns: repeat(3, 1fr);
13
+ }
14
+
15
+ &--cols-4 {
16
+ grid-template-columns: repeat(4, 1fr);
17
+ }
18
+
19
+ &__item {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 4px;
23
+ padding: 16px;
24
+ background: var(--surface-secondary, #f8f8fa);
25
+ border-radius: 8px;
26
+ border: 1px solid var(--border-primary, #e8e9ef);
27
+ }
28
+
29
+ &__label {
30
+ font-size: 11px;
31
+ font-weight: 600;
32
+ text-transform: uppercase;
33
+ letter-spacing: 0.05em;
34
+ color: var(--label-secondary, #626a90);
35
+ }
36
+
37
+ &__value {
38
+ font-size: 14px;
39
+ font-weight: 500;
40
+ color: var(--label-primary, #121e52);
41
+ }
42
+ }
43
+
44
+ @media (max-width: 640px) {
45
+ .info-grid {
46
+ &--cols-3,
47
+ &--cols-4 {
48
+ grid-template-columns: repeat(2, 1fr);
49
+ }
50
+ }
51
+ }