Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,14 @@ export const SpeakerSelfEditSettings = ({ event }: SpeakerSelfEditSettingsProps)
// dialog from the summary does NOT toggle the accordion.
const renderPendingEditsButton = (placement: 'summary' | 'details') => {
if (!event.speakerSelfEdit?.enabled) return null
// Only highlight in warning orange when there is actually a queue
// to review. Empty queue stays on the default theme colour so the
// accordion summary does not look perpetually alarmed.
const hasPending = pendingCount > 0
return (
<Button
variant="contained"
color="warning"
variant={hasPending ? 'contained' : 'outlined'}
color={hasPending ? 'warning' : 'primary'}
size="small"
onClick={(e) => {
e.stopPropagation()
Expand Down
54 changes: 52 additions & 2 deletions src/public/speakerEdit/CapWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as React from 'react'
import { useEffect, useRef } from 'react'
import { useEffect, useMemo, useRef } from 'react'
// Side-effect import: registers the <cap-widget> custom element on
// `window.customElements`. The widget is now bundled from the
// `@cap.js/widget` npm package (pinned in package.json) so the script
// is served from our own deploy instead of a third-party CDN — closes
// the supply-chain vector that the previous jsdelivr <script> opened.
import '@cap.js/widget'
import { useTheme } from '@mui/material/styles'
import { CAP_API_ENDPOINT } from '../../env'

declare global {
Expand All @@ -24,8 +25,54 @@ export type CapWidgetProps = {
onReset?: () => void
}

// CSS custom properties exposed by @cap.js/widget. The defaults shipped
// by the widget are tuned for light backgrounds; on a dark MUI theme
// they produce a near-white box that stands out aggressively against
// the surrounding surface. Override the colour vars (and ONLY the
// colour vars — sizing/radius stays on the widget defaults) based on
// the active MUI palette so the widget blends with the host page.
type CapColourVars = {
'--cap-background': string
'--cap-border-color': string
'--cap-color': string
'--cap-checkbox-background': string
'--cap-checkbox-border': string
'--cap-spinner-color': string
'--cap-spinner-background-color': string
}

export const CapWidget = ({ onSolve, onReset }: CapWidgetProps) => {
const containerRef = useRef<HTMLDivElement | null>(null)
const theme = useTheme()
const isDark = theme.palette.mode === 'dark'

// Compute the CSS-var overrides once per theme flip rather than on
// every render. Light values mirror the upstream widget defaults so
// existing deployments keep their look; dark values pull from the
// MUI palette so the widget tracks any user theme customisation.
const colourVars = useMemo<CapColourVars>(
() =>
isDark
? {
'--cap-background': theme.palette.background.paper,
'--cap-border-color': theme.palette.divider,
'--cap-color': theme.palette.text.primary,
'--cap-checkbox-background': theme.palette.action.hover,
'--cap-checkbox-border': `1px solid ${theme.palette.divider}`,
'--cap-spinner-color': theme.palette.text.primary,
'--cap-spinner-background-color': theme.palette.action.disabledBackground,
}
: {
'--cap-background': '#fdfdfd',
'--cap-border-color': '#dddddd8f',
'--cap-color': '#212121',
'--cap-checkbox-background': '#fafafa91',
'--cap-checkbox-border': '1px solid #aaaaaad1',
'--cap-spinner-color': '#000',
'--cap-spinner-background-color': '#eee',
},
[isDark, theme.palette]
)

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

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