@startsimpli/ui 0.4.77 → 0.4.78
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.78",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
45
|
-
"@startsimpli/
|
|
46
|
-
"@startsimpli/
|
|
45
|
+
"@startsimpli/api": "^0.5.48",
|
|
46
|
+
"@startsimpli/auth": "^0.4.31"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"@startsimpli/api": {
|
|
@@ -36,6 +36,8 @@ export interface MembersSectionProps {
|
|
|
36
36
|
onChangeRole?: (userId: string, role: TeamRole) => void | Promise<void>
|
|
37
37
|
onRemove?: (userId: string) => void | Promise<void>
|
|
38
38
|
onResendInvite?: (userId: string) => void | Promise<void>
|
|
39
|
+
/** Suspend/restore a membership without deleting it (soft exit). */
|
|
40
|
+
onSetActive?: (userId: string, isActive: boolean) => void | Promise<void>
|
|
39
41
|
/** When set, the invite dialog is rendered next to the heading. */
|
|
40
42
|
onInvite?: (input: { email: string; teamId: string; role: TeamRole }) => Promise<InvitationLite | void>
|
|
41
43
|
onInvited?: (invitation: InvitationLite | void) => void
|
|
@@ -43,8 +45,9 @@ export interface MembersSectionProps {
|
|
|
43
45
|
availableRoles?: TeamRole[]
|
|
44
46
|
inviteRoles?: TeamRole[]
|
|
45
47
|
defaultInviteRole?: TeamRole
|
|
46
|
-
/** Header copy.
|
|
47
|
-
|
|
48
|
+
/** Header copy. A node so a host can badge/icon the heading without
|
|
49
|
+
* re-assembling the header row. `description` receives the member count. */
|
|
50
|
+
title?: React.ReactNode
|
|
48
51
|
description?: (count: number) => React.ReactNode
|
|
49
52
|
/** Set false when the host already renders a card header. */
|
|
50
53
|
showHeader?: boolean
|
|
@@ -70,6 +73,7 @@ export function MembersSection({
|
|
|
70
73
|
onChangeRole,
|
|
71
74
|
onRemove,
|
|
72
75
|
onResendInvite,
|
|
76
|
+
onSetActive,
|
|
73
77
|
onInvite,
|
|
74
78
|
onInvited,
|
|
75
79
|
availableRoles,
|
|
@@ -123,6 +127,7 @@ export function MembersSection({
|
|
|
123
127
|
onChangeRole={onChangeRole}
|
|
124
128
|
onRemove={onRemove}
|
|
125
129
|
onResendInvite={onResendInvite}
|
|
130
|
+
onSetActive={onSetActive}
|
|
126
131
|
emptyState={emptyState}
|
|
127
132
|
classNames={classNames?.table}
|
|
128
133
|
/>
|
|
@@ -46,6 +46,12 @@ export interface MembersTableProps {
|
|
|
46
46
|
* only alongside `canManage` — an expired invite is otherwise a dead end.
|
|
47
47
|
*/
|
|
48
48
|
onResendInvite?: (userId: string) => void | Promise<void>
|
|
49
|
+
/**
|
|
50
|
+
* Suspend or restore a membership without deleting it — the soft exit next to
|
|
51
|
+
* Remove. Offered only alongside `canManage`. Rows render Deactivate while
|
|
52
|
+
* active and Restore once `isActive === false`.
|
|
53
|
+
*/
|
|
54
|
+
onSetActive?: (userId: string, isActive: boolean) => void | Promise<void>
|
|
49
55
|
/** Custom empty-state slot. Defaults to a generic "No members yet" cell. */
|
|
50
56
|
emptyState?: React.ReactNode
|
|
51
57
|
/**
|
|
@@ -113,6 +119,7 @@ export function MembersTable({
|
|
|
113
119
|
onChangeRole,
|
|
114
120
|
onRemove,
|
|
115
121
|
onResendInvite,
|
|
122
|
+
onSetActive,
|
|
116
123
|
emptyState,
|
|
117
124
|
showStatus,
|
|
118
125
|
showLastActive,
|
|
@@ -244,6 +251,15 @@ export function MembersTable({
|
|
|
244
251
|
Resend invite
|
|
245
252
|
</button>
|
|
246
253
|
)}
|
|
254
|
+
{onSetActive && (
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
className={cls.deactivateButton}
|
|
258
|
+
onClick={() => onSetActive(m.userId, m.isActive === false)}
|
|
259
|
+
>
|
|
260
|
+
{m.isActive === false ? 'Restore' : 'Deactivate'}
|
|
261
|
+
</button>
|
|
262
|
+
)}
|
|
247
263
|
{onRemove && (
|
|
248
264
|
<button
|
|
249
265
|
type="button"
|
|
@@ -91,6 +91,33 @@ describe('MembersTable — invite lifecycle', () => {
|
|
|
91
91
|
expect(screen.queryByRole('button', { name: /resend/i })).not.toBeInTheDocument()
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
+
// The soft exit next to Remove. The table already paints a `deactivated`
|
|
95
|
+
// pill, but nothing could ever produce one: Remove (hard-delete) was the only
|
|
96
|
+
// way out of a foundry, so suspending someone meant destroying their role,
|
|
97
|
+
// attribution and history (startsim-768w.19.2).
|
|
98
|
+
it('offers deactivate on an active row and calls it with (userId, false)', () => {
|
|
99
|
+
const onSetActive = jest.fn()
|
|
100
|
+
render(<MembersTable members={rows} canManage onSetActive={onSetActive} />)
|
|
101
|
+
const buttons = screen.getAllByRole('button', { name: /deactivate/i })
|
|
102
|
+
expect(buttons).toHaveLength(3)
|
|
103
|
+
fireEvent.click(buttons[0])
|
|
104
|
+
expect(onSetActive).toHaveBeenCalledWith('u1', false)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('offers restore — not deactivate — on an already deactivated row', () => {
|
|
108
|
+
const onSetActive = jest.fn()
|
|
109
|
+
const deactivated: MemberRow[] = [{ ...rows[1], isActive: false }]
|
|
110
|
+
render(<MembersTable members={deactivated} canManage onSetActive={onSetActive} />)
|
|
111
|
+
expect(screen.queryByRole('button', { name: /deactivate/i })).not.toBeInTheDocument()
|
|
112
|
+
fireEvent.click(screen.getByRole('button', { name: /restore/i }))
|
|
113
|
+
expect(onSetActive).toHaveBeenCalledWith('u2', true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('shows no deactivate affordance without canManage', () => {
|
|
117
|
+
render(<MembersTable members={rows} onSetActive={jest.fn()} />)
|
|
118
|
+
expect(screen.queryByRole('button', { name: /deactivate/i })).not.toBeInTheDocument()
|
|
119
|
+
})
|
|
120
|
+
|
|
94
121
|
it('stays backwards compatible with rows that carry no lifecycle fields', () => {
|
|
95
122
|
const legacy: MemberRow[] = [
|
|
96
123
|
{ id: 'm1', userId: 'u1', teamId: 't1', role: 'admin', joinedAt: '2026-01-01' },
|
|
@@ -118,6 +145,13 @@ describe('MembersSection — the page-level composer', () => {
|
|
|
118
145
|
expect(onResendInvite).toHaveBeenCalledWith('u2')
|
|
119
146
|
})
|
|
120
147
|
|
|
148
|
+
it('forwards a deactivate from the composer', () => {
|
|
149
|
+
const onSetActive = jest.fn()
|
|
150
|
+
render(<MembersSection members={rows} canManage onSetActive={onSetActive} />)
|
|
151
|
+
fireEvent.click(screen.getAllByRole('button', { name: /deactivate/i })[1])
|
|
152
|
+
expect(onSetActive).toHaveBeenCalledWith('u2', false)
|
|
153
|
+
})
|
|
154
|
+
|
|
121
155
|
it('surfaces an error line when a mutation failed', () => {
|
|
122
156
|
render(
|
|
123
157
|
<MembersSection members={rows} canManage error="Couldn't update that member" />
|
|
@@ -34,6 +34,8 @@ export interface MembersTableClassNames {
|
|
|
34
34
|
invitedBy?: string
|
|
35
35
|
/** Resend-invite action on a pending/expired row. */
|
|
36
36
|
resendButton?: string
|
|
37
|
+
/** Deactivate / Restore action — the soft exit next to the hard Remove. */
|
|
38
|
+
deactivateButton?: string
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export const MEMBERS_TABLE_DEFAULTS: Required<MembersTableClassNames> = {
|
|
@@ -70,4 +72,6 @@ export const MEMBERS_TABLE_DEFAULTS: Required<MembersTableClassNames> = {
|
|
|
70
72
|
invitedBy: 'text-xs text-muted-foreground/80',
|
|
71
73
|
resendButton:
|
|
72
74
|
'rounded-md border border-border px-2 py-1 text-xs text-muted-foreground transition-colors hover:border-primary/40 hover:bg-primary/10 hover:text-primary disabled:opacity-50',
|
|
75
|
+
deactivateButton:
|
|
76
|
+
'rounded-md border border-border px-2 py-1 text-xs text-muted-foreground transition-colors hover:border-foreground/30 hover:bg-muted hover:text-foreground disabled:opacity-50',
|
|
73
77
|
}
|