Skip to content

Commit 39a59f2

Browse files
committed
feat(platform): consolidate credential access tables and integration rows
1 parent 67d6a34 commit 39a59f2

45 files changed

Lines changed: 1262 additions & 442 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/platform/AGENTS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ Use `Stack` for compact people or integration artwork: show at most three items,
3131

3232
Use `ActivityRow` for a first-line glyph, avatar or artwork stack, title/detail and centered trailing metadata. Its trailing metadata is either text or people, never both. A navigating row ends at its chevron and has no sibling action or ellipsis. Put record actions in its detail; a passive row with independent controls uses ActivityBody and has no whole-row hover or click target. Use the chart, calendar, composer or attachment owner when those parts have a different interaction. See the [component ownership map](docs/component-ownership.md) before adding another family.
3333

34-
Inputs and textareas share the neutral field frame, resting border and 8px text inset. Hover changes only the frame border; the fill remains unchanged. Focus alone preserves resting paint. Required placeholders describe the expected input without replacing the label. `viewOnly` supplies native read-only behavior and the shared copy action in the sidebar’s 24px trailing slot; copy feedback remains visible for four seconds. Read-only fields show a not-allowed cursor without dimming, while their copy action remains interactive. TextArea defaultSize selects regular (76px), expanded (112px) or large (240px); skill instruction editors use large. Invite recipient fields share the regular textarea height and insets. Quiet `SearchField` is the explicit focus exception: its subtle underline starts at the typing edge, excluding the leading icon and trailing controls, and appears on hover or focus while the background remains unchanged. Counts inherit adjacent label metrics, use ordinary inline spacing and align on the text baseline. Disabled dropdowns show a not-allowed cursor.
34+
Inputs and textareas share the neutral field frame, resting border and 8px text inset. Hover changes only the frame border; the fill remains unchanged. Focus alone preserves resting paint. Required placeholders describe the expected input without replacing the label. `viewOnly` supplies native read-only behavior and an always-visible shared copy action in the sidebar’s 24px trailing slot. Textareas place the same action at the top-right. Copy feedback remains visible for four seconds before returning to the copy action. Read-only fields show a not-allowed cursor without dimming, while their copy action remains interactive. TextArea defaultSize selects regular (76px), expanded (112px) or large (240px); skill instruction editors use large. Invite recipient fields share the regular textarea height and insets. Quiet `SearchField` is the explicit focus exception: its subtle underline starts at the typing edge, excluding the leading icon and trailing controls, and appears on hover or focus while the background remains unchanged. Counts inherit adjacent label metrics, use ordinary inline spacing and align on the text baseline. Disabled dropdowns show a not-allowed cursor.
3535

3636
Toolbar choices use `ChoiceMenu`, the same compact quiet button and menu as Home's activity period. A toolbar action uses the existing quiet compact Button or IconButton. Page-level actions live in the control bar beneath the title and description; an action for a particular record belongs with that record. `Select` and `SelectField` provide field chrome for forms and settings; do not place a full-width field picker in a control bar or invent a toolbar variant to disguise it. Reuse the existing owner for the interaction and compare its actual trigger with Home before accepting it.
3737

3838
Measure the actual scroll viewport with `ScrollArea`, or attach `useScrollEdges` directly to a native or Base UI scroll owner. Use the shared 16px mask only on edges with more content, updating after scroll, resize, input, reset or filtering. Platform and Settings navigation hide the native scrollbar and keep their edge fades, keyboard, wheel and touch scrolling. Other scroll areas keep their existing scrollbar behavior. Insets establish resting layout; they cannot substitute for an edge fade. Home list widgets and notification panels use `--ui-widget-max-size` as a ceiling, not a forced height. The Home activity chart grows with its content and has no nested scroll viewport. Keep the dashboard free to scroll and let each overflowing list or schedule scroll independently beneath its stationary heading. Shared ScrollArea viewports and the Home calendar suppress rubber-band overscroll at their boundaries. Plain tooltips preview at most three lines; retain the complete accessible text. A structured people popover owns its content and uses the dropdown’s popup padding and row insets.
3939

4040
Form dialogs use `Modal` or `MultiStepModal`. Pass a title, content, action labels and handlers. A confirm handler that returns a promise holds the dialog open as pending, closes it on success, and keeps the draft with an inline message on failure; nothing dismisses it while pending. Returning `false` keeps it open at the dialog's next stage, and `onBack` turns the quiet footer action into a step back. The shared borderless surface positions its title at the logical start and supplies one quiet Cancel/Back. Command search and previews use the same shadow and dismissal layer. Form controls cannot construct or style a form header, footer or Cancel button. Opening a dialog or advancing its step focuses the first available field. Confirmation dialogs focus Cancel, and read-only previews focus Close. A nested dialog handles Escape one layer at a time. While a form is open, the global search shortcut leaves its draft and keyboard focus in place. Closing returns focus to the invoking control. Textareas scroll or grow within their owner and never show a manual resize handle. Recipient fields use the textarea’s height, draft typography and inset. Committed addresses use compact caption text and an always-visible neutral ×, like a panel tab. The remove button sits inside the tag beside its label; long addresses yield space to the button. Comma or Enter commits a draft, pasting a list commits its addresses, and Backspace from an empty draft removes the last tag.
4141

42+
ContentTabs uses Base UI tab behavior with the same ControlRow lane, spacing and paint as DestinationTabs. DataTable composes those tabs, a caller-owned toolbar, native table semantics and shared checkboxes for controlled row/select-all operations. Passive table rows have no whole-row hover or click target; their controls remain independent. Consumers own filtering, sorting and mutation policy.
43+
4244
MessageBubble owns the normal user-message surface and selectable integration ideas. Idea messages sit centered over full-width artwork; selecting one appends to the Home draft without submitting or discarding existing text or attachments.
4345

4446
Use `useClipboard` / `CopyButton` for successful copy feedback and `useFeedbackExpiry` for temporary status. Copied indicators and transient notices dwell for four seconds; repeated events restart the timer. Pause an actionable notice during hover or focus and keep its action available in the stack afterward. Icon color changes use `--ui-motion-color` (120ms, zero under reduced motion). Do not put literal timers or CSS durations in product components.

‎apps/sim/platform/app/(shell)/integrations/_components/connected-credential/connected-credential.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { ActionMenu, ActivityRow, Modal } from '@/platform/design-system/compone
77
import { ActivityBody } from '@/platform/design-system/components/activity-row/activity-body'
88
import { CountedLabel } from '@/platform/design-system/components/overflow-text/counted-label'
99
import type { IntegrationAccount } from '@/platform/lib/data/integrations-types'
10+
import { credentialDescription } from '@/platform/lib/integrations/credential-display'
1011

1112
interface CredentialRow extends IntegrationAccount {
12-
statusLabel: string
1313
/** Opens this account's permissions when the integration lists several. */
1414
href?: string
1515
}
@@ -74,14 +74,14 @@ export function ConnectedCredential({
7474
href={account.href}
7575
leading={<IntegrationMark providerId={providerId} icon={icon} />}
7676
title={account.label}
77-
detail={account.statusLabel}
77+
detail={credentialDescription(account)}
7878
/>
7979
) : (
8080
<li key={account.id} className={styles.row}>
8181
<ActivityBody leading={<IntegrationMark providerId={providerId} icon={icon} />}>
8282
<span data-type='T059'>{account.label}</span>
8383
<span className={styles.muted} data-type='T050'>
84-
{account.statusLabel}
84+
{credentialDescription(account)}
8585
</span>
8686
</ActivityBody>
8787
{action}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { ReactNode } from 'react'
2+
import styles from '@/platform/app/(shell)/integrations/_components/credential-access/credential-access.module.css'
3+
import { IntegrationControls } from '@/platform/app/(shell)/integrations/_components/integration-controls/integration-controls'
4+
import { ActionMenu, ChoiceMenu, SearchField } from '@/platform/design-system/components'
5+
import type { CredentialAccessView } from '@/platform/lib/integrations/credential-navigation'
6+
7+
interface CredentialAccessControlsProps {
8+
view: CredentialAccessView
9+
onSearch: (value: string) => void
10+
onRoleChange: (value: CredentialAccessView['accessRole']) => void
11+
onOrderChange: (value: CredentialAccessView['accessOrder']) => void
12+
children?: ReactNode
13+
}
14+
15+
export function CredentialAccessControls({
16+
view,
17+
onSearch,
18+
onRoleChange,
19+
onOrderChange,
20+
children,
21+
}: CredentialAccessControlsProps) {
22+
return (
23+
<div className={styles.tools}>
24+
<IntegrationControls
25+
leading={
26+
<SearchField
27+
variant='quiet'
28+
label='Search people'
29+
placeholder='Search people…'
30+
value={view.accessQ}
31+
onValueChange={onSearch}
32+
/>
33+
}
34+
>
35+
<ChoiceMenu
36+
label='Role filter'
37+
value={view.accessRole}
38+
options={[
39+
{ value: 'all', label: 'All roles' },
40+
{ value: 'admin', label: 'Admin' },
41+
{ value: 'member', label: 'Member' },
42+
{ value: 'organization-admin', label: 'Organization admin' },
43+
{ value: 'workspace-admin', label: 'Workspace admin' },
44+
]}
45+
onValueChange={(value) => {
46+
if (
47+
value === 'all' ||
48+
value === 'admin' ||
49+
value === 'member' ||
50+
value === 'organization-admin' ||
51+
value === 'workspace-admin'
52+
)
53+
onRoleChange(value)
54+
}}
55+
/>
56+
<ActionMenu
57+
label='Sort people'
58+
icon='I050'
59+
actions={[
60+
{
61+
id: 'asc',
62+
label: 'Name A–Z',
63+
checked: view.accessOrder === 'asc',
64+
onSelect: () => onOrderChange('asc'),
65+
},
66+
{
67+
id: 'desc',
68+
label: 'Name Z–A',
69+
checked: view.accessOrder === 'desc',
70+
onSelect: () => onOrderChange('desc'),
71+
},
72+
]}
73+
/>
74+
</IntegrationControls>
75+
{children}
76+
</div>
77+
)
78+
}
Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
1-
.section {
1+
.section,
2+
.tools {
23
display: grid;
34
min-inline-size: 0;
4-
gap: var(--ui-widget-content-gap);
5+
gap: var(--ui-section-gap);
56
}
6-
.heading,
7-
.actions,
8-
.row {
9-
display: flex;
10-
align-items: center;
11-
gap: var(--ui-row-gap);
12-
}
13-
.heading,
14-
.row {
15-
justify-content: space-between;
16-
}
17-
.heading {
18-
margin-inline-end: calc(-1 * var(--ui-row-inset));
7+
.section {
8+
gap: var(--spacing-24);
199
}
20-
.rows {
10+
.invite {
2111
display: grid;
22-
gap: var(--ui-list-gap);
23-
margin-inline: calc(-1 * var(--ui-row-inset));
12+
grid-template-columns: minmax(0, 1fr) minmax(var(--spacing-128), auto) auto;
13+
align-items: end;
14+
gap: var(--ui-row-gap);
2415
}
25-
.row {
26-
padding: var(--ui-row-block) var(--ui-row-inset);
16+
.invitePeople {
2717
min-inline-size: 0;
2818
}
29-
.actions {
30-
flex: none;
19+
.bulk {
20+
display: flex;
21+
flex-wrap: wrap;
22+
align-items: center;
3123
gap: var(--ui-action-gap);
32-
margin-inline-end: calc(-1 * var(--ui-action-overhang));
24+
}
25+
.bulk > span {
26+
margin-inline-end: var(--ui-row-gap);
3327
}
3428
.detail {
3529
color: var(--ui-secondary);
3630
overflow-wrap: anywhere;
3731
}
32+
.role {
33+
display: inline-flex;
34+
max-inline-size: 100%;
35+
min-inline-size: 0;
36+
}
37+
@media (max-width: 40rem) {
38+
.invite {
39+
grid-template-columns: minmax(0, 1fr) auto;
40+
}
41+
.invitePeople {
42+
grid-column: 1 / -1;
43+
}
44+
}

0 commit comments

Comments
 (0)