Skip to content

Commit 427f064

Browse files
authored
fix(dotcom): polish workspace settings dialog scrolling and layout (tldraw#9284)
In order to polish the manage workspace dialog from tldraw#9268, this PR makes the tab-page scrollbar appear only when content actually overflows, sizes the dialog body so it stops jumping around, and tidies a few labels and disabled-state styles. - Scrollbar only appears when a tab page actually overflows. The container stays `overflow: hidden` and a `useScrollbarWhenScrollable` callback ref measures the content with a `ResizeObserver`, adding a `.scrollable` class that flips to `overflow: auto` only when needed. This avoids a permanent scrollbar/gutter on systems that always render scrollbars. - Sized the dialog body with a `max-height`/`min-height` so it doesn't jump around as content changes. - Renamed the sidebar workspace action label from "Settings" to "Manage". - Renamed the "Enable invite link" label to "Enable invites". - Disabled inline buttons now dim via text color instead of opacity. - Bumped the empty-state text contrast (`text-3` → `text-1`). ### Change type - [x] `improvement` ### Test plan 1. Open the manage workspace dialog from the sidebar "Manage" action. 2. With few members, confirm neither tab page shows a scrollbar or gutter. 3. Add members until a tab page overflows and confirm the scrollbar appears only then. 4. Switch between the Members and Settings tabs and confirm the dialog body height stays stable. 5. Confirm disabled inline buttons dim and the "Enable invites" label reads correctly. - [ ] Unit tests - [ ] End to end tests ### Release notes - Polish scrolling and layout in the manage workspace dialog. ### Code changes | Section | LOC change | | ------- | ---------- | | Apps | +40 / -11 |
1 parent 1299eeb commit 427f064

5 files changed

Lines changed: 58 additions & 20 deletions

File tree

apps/dotcom/client/public/tla/locales-compiled/en.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@
245245
"value": "Editor"
246246
}
247247
],
248+
"34e34c43ec": [
249+
{
250+
"type": 0,
251+
"value": "Manage"
252+
}
253+
],
248254
"375396d6e5": [
249255
{
250256
"type": 0,
@@ -621,12 +627,6 @@
621627
"value": "Got it"
622628
}
623629
],
624-
"78faf88f4d": [
625-
{
626-
"type": 0,
627-
"value": "Enable invite link"
628-
}
629-
],
630630
"797799f35e": [
631631
{
632632
"type": 0,
@@ -1013,6 +1013,12 @@
10131013
"value": "Email address"
10141014
}
10151015
],
1016+
"b48becc771": [
1017+
{
1018+
"type": 0,
1019+
"value": "Enable invites"
1020+
}
1021+
],
10161022
"b6d4223e60": [
10171023
{
10181024
"type": 0,

apps/dotcom/client/public/tla/locales/en.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@
113113
"344a7f427f": {
114114
"translation": "Editor"
115115
},
116+
"34e34c43ec": {
117+
"translation": "Manage"
118+
},
116119
"375396d6e5": {
117120
"translation": "Remove member"
118121
},
@@ -296,9 +299,6 @@
296299
"78e9815992": {
297300
"translation": "Got it"
298301
},
299-
"78faf88f4d": {
300-
"translation": "Enable invite link"
301-
},
302302
"797799f35e": {
303303
"translation": "Submit an issue on GitHub"
304304
},
@@ -465,6 +465,9 @@
465465
"b357b524e7": {
466466
"translation": "Email address"
467467
},
468+
"b48becc771": {
469+
"translation": "Enable invites"
470+
},
468471
"b6d4223e60": {
469472
"translation": "Sign in"
470473
},

apps/dotcom/client/src/tla/components/TlaSidebar/components/TlaSidebarWorkspaceActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import styles from '../sidebar.module.css'
1111

1212
const messages = defineMessages({
1313
newFile: { defaultMessage: 'New file' },
14-
workspaceSettings: { defaultMessage: 'Settings' },
14+
workspaceSettings: { defaultMessage: 'Manage' },
1515
})
1616

1717
/**

apps/dotcom/client/src/tla/components/dialogs/WorkspaceSettingsDialog.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MAX_WORKSPACE_NAME_LENGTH, Role, ZErrorCode, can } from '@tldraw/dotcom-shared'
22
import { Tooltip as _Tooltip } from 'radix-ui'
3-
import { useLayoutEffect, useRef, useState } from 'react'
3+
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
44
import { useNavigate } from 'react-router-dom'
55
import {
66
TldrawUiDialogBody,
@@ -45,12 +45,32 @@ interface WorkspaceSettingsDialogProps {
4545
onClose(): void
4646
}
4747

48+
// Returns a callback ref for a scroll container that shows its scrollbar only when the
49+
// content actually overflows. The container stays `overflow: hidden` (see .tabPage) and
50+
// gets the `.scrollable` class — flipping overflow to auto — once measurement shows the
51+
// content is taller than the (max-height-capped) container. A ResizeObserver on the
52+
// container and its content re-checks as members are added/removed or the window resizes.
53+
function useScrollbarWhenScrollable() {
54+
return useCallback((el: HTMLDivElement | null) => {
55+
if (!el) return
56+
const update = () =>
57+
el.classList.toggle(styles.scrollable, el.scrollHeight > el.clientHeight + 1)
58+
update()
59+
const observer = new ResizeObserver(update)
60+
observer.observe(el)
61+
for (const child of el.children) observer.observe(child)
62+
return () => observer.disconnect()
63+
}, [])
64+
}
65+
4866
export function WorkspaceSettingsDialog({ workspaceId, onClose }: WorkspaceSettingsDialogProps) {
4967
const app = useApp()
5068
const { addDialog } = useDialogs()
5169
const [activeTab, setActiveTab] = useState('members')
5270
const [copiedInviteLink, setCopiedInviteLink] = useState(false)
5371

72+
const scrollableRef = useScrollbarWhenScrollable()
73+
5474
const namePlaceholderMsg = useMsg(messages.namePlaceholder)
5575
const ownerMsg = useMsg(messages.owner)
5676
const memberMsg = useMsg(messages.member)
@@ -339,7 +359,7 @@ export function WorkspaceSettingsDialog({ workspaceId, onClose }: WorkspaceSetti
339359
</div>
340360

341361
<TlaMenuTabsPage id="members">
342-
<div className={styles.tabPage}>
362+
<div className={styles.tabPage} ref={scrollableRef}>
343363
<div className={styles.membersList}>
344364
{members.map((member) => {
345365
const isSelf = member.userId === app.getUser().id
@@ -424,13 +444,13 @@ export function WorkspaceSettingsDialog({ workspaceId, onClose }: WorkspaceSetti
424444
</TlaMenuTabsPage>
425445

426446
<TlaMenuTabsPage id="settings">
427-
<div className={styles.tabPage}>
447+
<div className={styles.tabPage} ref={scrollableRef}>
428448
<div className={styles.settingsPage}>
429449
{canManageWorkspace && (
430450
<>
431451
<TlaMenuControl>
432452
<TlaMenuControlLabel htmlFor="workspace-invite-enabled-switch">
433-
<F defaultMessage="Enable invite link" />
453+
<F defaultMessage="Enable invites" />
434454
</TlaMenuControlLabel>
435455
<TlaMenuSwitch
436456
id="workspace-invite-enabled-switch"

apps/dotcom/client/src/tla/components/dialogs/dialogs.module.css

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
flex-direction: column;
1010
gap: var(--tl-space-4);
1111
max-width: 500px;
12+
overflow: auto;
1213
}
1314

1415
/* ----------------- Feedback dialog ---------------- */
@@ -201,14 +202,14 @@
201202
/* ----------------- Group Settings Dialog ---------------- */
202203

203204
.workspaceSettingsBody {
205+
padding-top: 0px;
204206
max-width: 360px;
207+
max-height: max(420px, 80%);
208+
min-height: 420px;
205209
width: calc(100vw - 48px);
206210
}
207211

208212
.section {
209-
/* Sections are separated by spacing alone (no dividers), so the gap needs to be
210-
clearly larger than the ~8px spacing within a section. */
211-
margin-bottom: 20px;
212213
display: flex;
213214
flex-direction: column;
214215
}
@@ -230,7 +231,7 @@
230231
margin: 8px 0 0;
231232
text-align: center;
232233
text-wrap: balance;
233-
color: var(--tla-color-text-3);
234+
color: var(--tla-color-text-1);
234235
}
235236

236237
.membersList {
@@ -271,7 +272,7 @@
271272
also equals that 12px, the strip now spans the body's full width — so drop the tab
272273
nav's bottom rule inset to 0 and let it bleed to the modal edges. */
273274
.workspaceTabs {
274-
margin: 0 -12px;
275+
margin: 20px -12px 0px -12px;
275276
--tabs-line-inset: 0px;
276277
/* Tighten the gap between the Members and Settings tabs (default leaves too much). */
277278
--tabs-tab-gap: -12px;
@@ -290,6 +291,14 @@
290291
padding: 16px 12px 0;
291292
margin: 0 -12px;
292293
max-height: calc(70vh - 280px);
294+
/* Hidden by default so no scrollbar shows when the content fits. The component
295+
measures the content and adds .scrollable to flip overflow to auto only when it
296+
actually overflows — avoids a permanent scrollbar/gutter on systems that always
297+
render scrollbars. */
298+
overflow-y: hidden;
299+
}
300+
301+
.tabPage.scrollable {
293302
overflow-y: auto;
294303
}
295304

@@ -323,7 +332,7 @@
323332
}
324333

325334
.inlineButton:disabled {
326-
opacity: 0.5;
335+
color: var(--tla-color-text-3);
327336
cursor: not-allowed;
328337
}
329338

0 commit comments

Comments
 (0)