Skip to content

Commit 2ef1dbd

Browse files
HugoGresseclaude
andauthored
Polish self-edit admin button + theme the Cap widget (#261)
Two small UI tweaks asked for after the last self-edit pass: 1) Pending-edits button is no longer perpetually orange. When the pending queue is empty the button drops to variant="outlined" + color="primary" so the speaker self-edit accordion does not look alarmed by default. As soon as pendingCount > 0 it switches to variant="contained" + color="warning" so admins notice they have work to review. 2) Cap captcha widget now follows the host MUI theme. The upstream defaults are tuned for light surfaces and produce a near-white box on the dark theme, which clashes hard with Paper. CapWidget now reads useTheme() and writes the colour CSS custom properties (background, border, text, checkbox, spinner) on its container div. CSS variables cascade to the widget child, so the widget picks the overrides up without us reaching into its internals. Sizing / radius / padding stay on the widget defaults — only the colour tokens are themed. Light mode keeps the exact upstream values to avoid regressing existing deployments. Dark mode maps to MUI palette tokens (background.paper, text.primary, divider, action.hover, action.disabledBackground) so the widget tracks any theme customisation a deploy might apply. Frontend tsc clean; vite build succeeds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 21d6884 commit 2ef1dbd

2 files changed

Lines changed: 58 additions & 4 deletions

File tree

src/events/page/speakers/components/SpeakerSelfEditSettings.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,14 @@ export const SpeakerSelfEditSettings = ({ event }: SpeakerSelfEditSettingsProps)
117117
// dialog from the summary does NOT toggle the accordion.
118118
const renderPendingEditsButton = (placement: 'summary' | 'details') => {
119119
if (!event.speakerSelfEdit?.enabled) return null
120+
// Only highlight in warning orange when there is actually a queue
121+
// to review. Empty queue stays on the default theme colour so the
122+
// accordion summary does not look perpetually alarmed.
123+
const hasPending = pendingCount > 0
120124
return (
121125
<Button
122-
variant="contained"
123-
color="warning"
126+
variant={hasPending ? 'contained' : 'outlined'}
127+
color={hasPending ? 'warning' : 'primary'}
124128
size="small"
125129
onClick={(e) => {
126130
e.stopPropagation()

src/public/speakerEdit/CapWidget.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as React from 'react'
2-
import { useEffect, useRef } from 'react'
2+
import { useEffect, useMemo, useRef } from 'react'
33
// Side-effect import: registers the <cap-widget> custom element on
44
// `window.customElements`. The widget is now bundled from the
55
// `@cap.js/widget` npm package (pinned in package.json) so the script
66
// is served from our own deploy instead of a third-party CDN — closes
77
// the supply-chain vector that the previous jsdelivr <script> opened.
88
import '@cap.js/widget'
9+
import { useTheme } from '@mui/material/styles'
910
import { CAP_API_ENDPOINT } from '../../env'
1011

1112
declare global {
@@ -24,8 +25,54 @@ export type CapWidgetProps = {
2425
onReset?: () => void
2526
}
2627

28+
// CSS custom properties exposed by @cap.js/widget. The defaults shipped
29+
// by the widget are tuned for light backgrounds; on a dark MUI theme
30+
// they produce a near-white box that stands out aggressively against
31+
// the surrounding surface. Override the colour vars (and ONLY the
32+
// colour vars — sizing/radius stays on the widget defaults) based on
33+
// the active MUI palette so the widget blends with the host page.
34+
type CapColourVars = {
35+
'--cap-background': string
36+
'--cap-border-color': string
37+
'--cap-color': string
38+
'--cap-checkbox-background': string
39+
'--cap-checkbox-border': string
40+
'--cap-spinner-color': string
41+
'--cap-spinner-background-color': string
42+
}
43+
2744
export const CapWidget = ({ onSolve, onReset }: CapWidgetProps) => {
2845
const containerRef = useRef<HTMLDivElement | null>(null)
46+
const theme = useTheme()
47+
const isDark = theme.palette.mode === 'dark'
48+
49+
// Compute the CSS-var overrides once per theme flip rather than on
50+
// every render. Light values mirror the upstream widget defaults so
51+
// existing deployments keep their look; dark values pull from the
52+
// MUI palette so the widget tracks any user theme customisation.
53+
const colourVars = useMemo<CapColourVars>(
54+
() =>
55+
isDark
56+
? {
57+
'--cap-background': theme.palette.background.paper,
58+
'--cap-border-color': theme.palette.divider,
59+
'--cap-color': theme.palette.text.primary,
60+
'--cap-checkbox-background': theme.palette.action.hover,
61+
'--cap-checkbox-border': `1px solid ${theme.palette.divider}`,
62+
'--cap-spinner-color': theme.palette.text.primary,
63+
'--cap-spinner-background-color': theme.palette.action.disabledBackground,
64+
}
65+
: {
66+
'--cap-background': '#fdfdfd',
67+
'--cap-border-color': '#dddddd8f',
68+
'--cap-color': '#212121',
69+
'--cap-checkbox-background': '#fafafa91',
70+
'--cap-checkbox-border': '1px solid #aaaaaad1',
71+
'--cap-spinner-color': '#000',
72+
'--cap-spinner-background-color': '#eee',
73+
},
74+
[isDark, theme.palette]
75+
)
2976

3077
useEffect(() => {
3178
if (!containerRef.current) return
@@ -49,5 +96,8 @@ export const CapWidget = ({ onSolve, onReset }: CapWidgetProps) => {
4996
}
5097
}, [])
5198

52-
return <div ref={containerRef} />
99+
// CSS custom properties cascade from this container down to the
100+
// shadow-DOM-less <cap-widget> child, so the widget picks the
101+
// overrides up automatically without us reaching into its internals.
102+
return <div ref={containerRef} style={colourVars as React.CSSProperties} />
53103
}

0 commit comments

Comments
 (0)