@talelk/newui-organisms 0.0.0-stage → 2.0.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.
Files changed (64) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +12 -2
  3. package/package.json +38 -3
  4. package/src/exports.test.ts +27 -0
  5. package/src/index.ts +1 -0
  6. package/src/organisms/AppSidebar/AppSidebar.test.tsx +136 -0
  7. package/src/organisms/AppSidebar/AppSidebar.tsx +259 -0
  8. package/src/organisms/AppSidebar/index.ts +1 -0
  9. package/src/organisms/AuthCard/AuthCard.a11y.test.tsx +36 -0
  10. package/src/organisms/AuthCard/AuthCard.test.tsx +56 -0
  11. package/src/organisms/AuthCard/AuthCard.tsx +89 -0
  12. package/src/organisms/AuthCard/index.ts +1 -0
  13. package/src/organisms/CommandPalette/CommandPalette.test.tsx +196 -0
  14. package/src/organisms/CommandPalette/CommandPalette.tsx +468 -0
  15. package/src/organisms/CommandPalette/index.ts +1 -0
  16. package/src/organisms/DataState/DataState.test.tsx +45 -0
  17. package/src/organisms/DataState/DataState.tsx +111 -0
  18. package/src/organisms/DataState/index.ts +6 -0
  19. package/src/organisms/DataTable/DataTable.test.tsx +161 -0
  20. package/src/organisms/DataTable/DataTable.tsx +612 -0
  21. package/src/organisms/DataTable/index.ts +1 -0
  22. package/src/organisms/DetailHeader/DetailHeader.test.tsx +55 -0
  23. package/src/organisms/DetailHeader/DetailHeader.tsx +95 -0
  24. package/src/organisms/DetailHeader/index.ts +1 -0
  25. package/src/organisms/FilterBar/FilterBar.test.tsx +152 -0
  26. package/src/organisms/FilterBar/FilterBar.tsx +251 -0
  27. package/src/organisms/FilterBar/index.ts +1 -0
  28. package/src/organisms/KanbanBoard/KanbanBoard.test.tsx +255 -0
  29. package/src/organisms/KanbanBoard/KanbanBoard.tsx +605 -0
  30. package/src/organisms/KanbanBoard/drag.test.ts +448 -0
  31. package/src/organisms/KanbanBoard/drag.ts +407 -0
  32. package/src/organisms/KanbanBoard/index.ts +17 -0
  33. package/src/organisms/MobileNav/MobileNav.browser.test.tsx +39 -0
  34. package/src/organisms/MobileNav/MobileNav.test.tsx +108 -0
  35. package/src/organisms/MobileNav/MobileNav.tsx +120 -0
  36. package/src/organisms/MobileNav/index.ts +6 -0
  37. package/src/organisms/PageToolbar/PageToolbar.a11y.test.tsx +35 -0
  38. package/src/organisms/PageToolbar/PageToolbar.test.tsx +42 -0
  39. package/src/organisms/PageToolbar/PageToolbar.tsx +126 -0
  40. package/src/organisms/PageToolbar/index.ts +1 -0
  41. package/src/organisms/Section/Section.test.tsx +41 -0
  42. package/src/organisms/Section/Section.tsx +108 -0
  43. package/src/organisms/Section/index.ts +1 -0
  44. package/src/organisms/SelectionBar/SelectionBar.test.tsx +35 -0
  45. package/src/organisms/SelectionBar/SelectionBar.tsx +84 -0
  46. package/src/organisms/SelectionBar/index.ts +5 -0
  47. package/src/organisms/SettingsDialog/SettingsDialog.a11y.test.tsx +58 -0
  48. package/src/organisms/SettingsDialog/SettingsDialog.browser.test.tsx +43 -0
  49. package/src/organisms/SettingsDialog/SettingsDialog.ssr.test.tsx +27 -0
  50. package/src/organisms/SettingsDialog/SettingsDialog.test.tsx +202 -0
  51. package/src/organisms/SettingsDialog/SettingsDialog.tsx +548 -0
  52. package/src/organisms/SettingsDialog/index.ts +20 -0
  53. package/src/organisms/Sidebar/Sidebar.browser.test.tsx +52 -0
  54. package/src/organisms/Sidebar/Sidebar.test.tsx +308 -0
  55. package/src/organisms/Sidebar/Sidebar.tsx +732 -0
  56. package/src/organisms/Sidebar/index.ts +22 -0
  57. package/src/organisms/StatGrid/StatGrid.a11y.test.tsx +40 -0
  58. package/src/organisms/StatGrid/StatGrid.test.tsx +95 -0
  59. package/src/organisms/StatGrid/StatGrid.tsx +151 -0
  60. package/src/organisms/StatGrid/index.ts +1 -0
  61. package/src/organisms/VirtualList/VirtualList.test.tsx +80 -0
  62. package/src/organisms/VirtualList/VirtualList.tsx +113 -0
  63. package/src/organisms/VirtualList/index.ts +1 -0
  64. package/src/organisms/index.ts +16 -0
@@ -0,0 +1,202 @@
1
+ import { render, screen } from '@solidjs/testing-library'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { createSignal } from 'solid-js'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import { SettingsDialog } from './SettingsDialog'
6
+
7
+ interface ExampleProps {
8
+ open?: boolean
9
+ value?: string
10
+ defaultValue?: string
11
+ onValueChange?: (value: string) => void
12
+ onOpenChange?: (open: boolean) => void
13
+ }
14
+
15
+ function Example(props: ExampleProps) {
16
+ return (
17
+ <SettingsDialog.Root
18
+ open={props.open}
19
+ defaultOpen={props.open === undefined ? true : undefined}
20
+ onOpenChange={props.onOpenChange}
21
+ value={props.value}
22
+ defaultValue={props.defaultValue}
23
+ onValueChange={props.onValueChange}
24
+ >
25
+ <SettingsDialog.Sidebar>
26
+ <SettingsDialog.NavGroup label="User settings">
27
+ <SettingsDialog.NavItem value="profile">
28
+ Profile
29
+ </SettingsDialog.NavItem>
30
+ <SettingsDialog.NavItem value="notifications">
31
+ Notifications
32
+ </SettingsDialog.NavItem>
33
+ </SettingsDialog.NavGroup>
34
+ </SettingsDialog.Sidebar>
35
+ <SettingsDialog.Content>
36
+ <SettingsDialog.Panel value="profile">
37
+ <SettingsDialog.Header title="Profile" />
38
+ <SettingsDialog.Body>Profile body</SettingsDialog.Body>
39
+ </SettingsDialog.Panel>
40
+ <SettingsDialog.Panel value="notifications">
41
+ <SettingsDialog.Header title="Notifications" />
42
+ <SettingsDialog.Body>Notifications body</SettingsDialog.Body>
43
+ </SettingsDialog.Panel>
44
+ </SettingsDialog.Content>
45
+ </SettingsDialog.Root>
46
+ )
47
+ }
48
+
49
+ describe('SettingsDialog', () => {
50
+ it('opens on request and closes on Escape', async () => {
51
+ function Harness() {
52
+ const [open, setOpen] = createSignal(false)
53
+ return (
54
+ <div>
55
+ <button type="button" onClick={() => setOpen(true)}>
56
+ open settings
57
+ </button>
58
+ <SettingsDialog.Root open={open()} onOpenChange={setOpen}>
59
+ <SettingsDialog.Sidebar>
60
+ <SettingsDialog.NavItem value="profile">
61
+ Profile
62
+ </SettingsDialog.NavItem>
63
+ </SettingsDialog.Sidebar>
64
+ <SettingsDialog.Content>
65
+ <SettingsDialog.Panel value="profile">
66
+ Profile body
67
+ </SettingsDialog.Panel>
68
+ </SettingsDialog.Content>
69
+ </SettingsDialog.Root>
70
+ </div>
71
+ )
72
+ }
73
+
74
+ render(() => <Harness />)
75
+ expect(screen.queryByRole('dialog')).toBeNull()
76
+
77
+ await userEvent.click(screen.getByRole('button', { name: 'open settings' }))
78
+ expect(screen.getByRole('dialog')).toBeTruthy()
79
+
80
+ await userEvent.keyboard('{Escape}')
81
+ expect(screen.queryByRole('dialog')).toBeNull()
82
+ })
83
+
84
+ it('reports a close through onOpenChange', async () => {
85
+ const onOpenChange = vi.fn()
86
+ render(() => <Example open onOpenChange={onOpenChange} />)
87
+
88
+ await userEvent.keyboard('{Escape}')
89
+ expect(onOpenChange).toHaveBeenCalledWith(false)
90
+ })
91
+
92
+ it('marks the selected nav item active', () => {
93
+ render(() => <Example defaultValue="profile" />)
94
+
95
+ const profile = screen.getByRole('tab', { name: 'Profile' })
96
+ const notifications = screen.getByRole('tab', { name: 'Notifications' })
97
+
98
+ expect(profile.getAttribute('aria-selected')).toBe('true')
99
+ expect(profile.getAttribute('data-state')).toBe('active')
100
+ expect(notifications.getAttribute('aria-selected')).toBe('false')
101
+ expect(notifications.getAttribute('data-state')).toBe('inactive')
102
+ })
103
+
104
+ it('switches panels when a nav item is clicked', async () => {
105
+ const onValueChange = vi.fn()
106
+ render(() => (
107
+ <Example defaultValue="profile" onValueChange={onValueChange} />
108
+ ))
109
+
110
+ expect(screen.getByRole('tabpanel').textContent).toContain('Profile body')
111
+
112
+ await userEvent.click(screen.getByRole('tab', { name: 'Notifications' }))
113
+
114
+ expect(onValueChange).toHaveBeenCalledWith('notifications')
115
+ expect(screen.getByRole('tabpanel').textContent).toContain(
116
+ 'Notifications body',
117
+ )
118
+ })
119
+
120
+ it('keeps every panel in the DOM, hiding the inactive ones', () => {
121
+ render(() => <Example defaultValue="profile" />)
122
+
123
+ const panels = document.querySelectorAll('[data-slot="settings-panel"]')
124
+ expect(panels).toHaveLength(2)
125
+ expect(panels[0]?.getAttribute('data-state')).toBe('active')
126
+ expect(panels[1]?.getAttribute('data-state')).toBe('inactive')
127
+ })
128
+
129
+ it('honours an explicit active prop over the dialog value', () => {
130
+ render(() => (
131
+ <SettingsDialog.Root defaultOpen defaultValue="beta">
132
+ <SettingsDialog.Sidebar>
133
+ <SettingsDialog.NavItem value="alpha" active>
134
+ Alpha
135
+ </SettingsDialog.NavItem>
136
+ <SettingsDialog.NavItem value="beta">Beta</SettingsDialog.NavItem>
137
+ </SettingsDialog.Sidebar>
138
+ </SettingsDialog.Root>
139
+ ))
140
+
141
+ expect(
142
+ screen.getByRole('tab', { name: 'Alpha' }).getAttribute('data-state'),
143
+ ).toBe('active')
144
+ })
145
+
146
+ it('renders the header title, description and actions', () => {
147
+ render(() => (
148
+ <SettingsDialog.Root defaultOpen defaultValue="profile">
149
+ <SettingsDialog.Content>
150
+ <SettingsDialog.Panel value="profile">
151
+ <SettingsDialog.Header
152
+ title="Profile"
153
+ description="Your details"
154
+ actions={<button type="button">Save</button>}
155
+ />
156
+ <SettingsDialog.Body>Body</SettingsDialog.Body>
157
+ </SettingsDialog.Panel>
158
+ </SettingsDialog.Content>
159
+ </SettingsDialog.Root>
160
+ ))
161
+
162
+ expect(screen.getByText('Profile')).toBeTruthy()
163
+ expect(screen.getByText('Your details')).toBeTruthy()
164
+ expect(screen.getByRole('button', { name: 'Save' })).toBeTruthy()
165
+ })
166
+
167
+ it('associates a row title with the control through labelFor', () => {
168
+ render(() => (
169
+ <SettingsDialog.Root defaultOpen defaultValue="profile">
170
+ <SettingsDialog.Content>
171
+ <SettingsDialog.Panel value="profile">
172
+ <SettingsDialog.Row title="Display name" labelFor="display-name">
173
+ <input id="display-name" type="text" />
174
+ </SettingsDialog.Row>
175
+ </SettingsDialog.Panel>
176
+ </SettingsDialog.Content>
177
+ </SettingsDialog.Root>
178
+ ))
179
+
180
+ const label = screen.getByText('Display name')
181
+ expect(label.tagName).toBe('LABEL')
182
+ expect(label.getAttribute('for')).toBe('display-name')
183
+ })
184
+
185
+ it('detects the control id for the row title when labelFor is omitted', () => {
186
+ render(() => (
187
+ <SettingsDialog.Root defaultOpen defaultValue="profile">
188
+ <SettingsDialog.Content>
189
+ <SettingsDialog.Panel value="profile">
190
+ <SettingsDialog.Row title="Display name">
191
+ <input id="auto-name" type="text" />
192
+ </SettingsDialog.Row>
193
+ </SettingsDialog.Panel>
194
+ </SettingsDialog.Content>
195
+ </SettingsDialog.Root>
196
+ ))
197
+
198
+ const label = screen.getByText('Display name')
199
+ expect(label.tagName).toBe('LABEL')
200
+ expect(label.getAttribute('for')).toBe('auto-name')
201
+ })
202
+ })