snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
181
react/features/time-timer/components/web/Disk.tsx
Normal file
181
react/features/time-timer/components/web/Disk.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
|
||||
/**
|
||||
* SVG disk renderer used by {@code TimeTimerPill}. Draws a ring with an
|
||||
* optional filled wedge. During overrun lap 2+ (once the disk has filled
|
||||
* once), an additional CSS conic-gradient overlay conveys the ongoing
|
||||
* sweep — a continuous gradient from the base red at the 12 o'clock origin
|
||||
* to a darker red at the current leading edge.
|
||||
*/
|
||||
|
||||
const CX = 50;
|
||||
const CY = 50;
|
||||
const RADIUS = 46;
|
||||
|
||||
/**
|
||||
* Converts a polar coordinate (clock angle in degrees, 0 = 12 o'clock) to
|
||||
* cartesian coordinates on the disk.
|
||||
*
|
||||
* @param {number} angleDeg - Angle in degrees, clockwise from the top.
|
||||
* @param {number} r - Radius.
|
||||
* @returns {{ x: number, y: number }}
|
||||
*/
|
||||
function polarToCartesian(angleDeg: number, r: number) {
|
||||
const rad = ((angleDeg - 90) * Math.PI) / 180;
|
||||
|
||||
return {
|
||||
x: CX + (r * Math.cos(rad)),
|
||||
y: CY + (r * Math.sin(rad))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an SVG path for a filled pie wedge covering the given fraction of
|
||||
* the disk, drawn clockwise from the top.
|
||||
*
|
||||
* @param {number} fraction - Fraction of the disk to fill (0..1).
|
||||
* @returns {string}
|
||||
*/
|
||||
function wedgePath(fraction: number): string {
|
||||
const clamped = Math.max(0, Math.min(1, fraction));
|
||||
|
||||
if (clamped <= 0) {
|
||||
return '';
|
||||
}
|
||||
if (clamped >= 1) {
|
||||
const top = polarToCartesian(0, RADIUS);
|
||||
const bottom = polarToCartesian(180, RADIUS);
|
||||
|
||||
return [
|
||||
`M ${top.x} ${top.y}`,
|
||||
`A ${RADIUS} ${RADIUS} 0 0 1 ${bottom.x} ${bottom.y}`,
|
||||
`A ${RADIUS} ${RADIUS} 0 0 1 ${top.x} ${top.y}`,
|
||||
'Z'
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
const endAngle = clamped * 360;
|
||||
const start = polarToCartesian(0, RADIUS);
|
||||
const end = polarToCartesian(endAngle, RADIUS);
|
||||
const largeArc = endAngle > 180 ? 1 : 0;
|
||||
|
||||
return [
|
||||
`M ${CX} ${CY}`,
|
||||
`L ${start.x} ${start.y}`,
|
||||
`A ${RADIUS} ${RADIUS} 0 ${largeArc} 1 ${end.x} ${end.y}`,
|
||||
'Z'
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
color: string;
|
||||
fraction: number;
|
||||
|
||||
/**
|
||||
* Angular position (degrees, clockwise from the top / 12 o'clock) of
|
||||
* the leading edge of the overrun sweep during lap 2 and onward. When
|
||||
* provided, the disk renders a conic-gradient overlay from the base
|
||||
* red at the origin to a darker red at this angle, over the (fully
|
||||
* filled) base disk.
|
||||
*/
|
||||
overrunArcEndDeg?: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<{ size: number; }>()((_theme, { size }) => {
|
||||
return {
|
||||
root: {
|
||||
flex: '0 0 auto',
|
||||
height: size,
|
||||
position: 'relative',
|
||||
width: size
|
||||
},
|
||||
|
||||
svg: {
|
||||
display: 'block',
|
||||
height: size,
|
||||
width: size
|
||||
},
|
||||
|
||||
// Circular element overlaid exactly on top of the solid SVG disk. Its
|
||||
// conic-gradient background is applied inline (see below) rather than
|
||||
// here: the leading-edge angle changes every second during overrun, so
|
||||
// baking it into a tss-react style would generate a fresh CSS class on
|
||||
// every tick. The static positioning stays here.
|
||||
overrunOverlay: {
|
||||
borderRadius: '50%',
|
||||
inset: 0,
|
||||
position: 'absolute'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds the lap-2+ overrun sweep — a conic gradient running clockwise from
|
||||
* the 12 o'clock origin: base red → darker red at the current leading edge,
|
||||
* then back to base red for the remainder (already filled from lap 1).
|
||||
*
|
||||
* @param {number} deg - Angular position of the leading edge, in degrees.
|
||||
* @returns {string}
|
||||
*/
|
||||
function overrunGradient(deg: number): string {
|
||||
const base = BaseTheme.palette.timeTimerExpiredDisk;
|
||||
const edge = BaseTheme.palette.timeTimerExpiredOverrunEdge;
|
||||
|
||||
return `conic-gradient(from 0deg, ${base} 0deg, ${edge} ${deg}deg, ${base} ${deg}deg, ${base} 360deg)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time-timer disk as a ring with an optional filled wedge, plus
|
||||
* a conic-gradient overrun sweep during lap 2+.
|
||||
*
|
||||
* @param {IProps} props - Component props.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
const Disk = ({ color, fraction, overrunArcEndDeg, size }: IProps) => {
|
||||
const { classes } = useStyles({ size });
|
||||
const showOverrun = typeof overrunArcEndDeg === 'number';
|
||||
|
||||
return (
|
||||
<div className = { classes.root }>
|
||||
<svg
|
||||
className = { classes.svg }
|
||||
viewBox = '0 0 100 100'>
|
||||
{/*
|
||||
* Per Figma: a faint 5% white fill behind the wedge so the
|
||||
* unfilled portion of the disk reads as a subtle lighter
|
||||
* shape against the navy chip background. Drawn FIRST so the
|
||||
* wedge paints over it.
|
||||
*/}
|
||||
<circle
|
||||
cx = { CX }
|
||||
cy = { CY }
|
||||
fill = '#FFFFFF'
|
||||
fillOpacity = '0.05'
|
||||
r = { RADIUS } />
|
||||
<circle
|
||||
cx = { CX }
|
||||
cy = { CY }
|
||||
fill = 'none'
|
||||
r = { RADIUS }
|
||||
stroke = { color }
|
||||
strokeWidth = '6' />
|
||||
{fraction > 0 && (
|
||||
<path
|
||||
d = { wedgePath(fraction) }
|
||||
fill = { color } />
|
||||
)}
|
||||
</svg>
|
||||
{showOverrun && (
|
||||
<div
|
||||
className = { classes.overrunOverlay }
|
||||
style = {{ background: overrunGradient(overrunArcEndDeg ?? 0) }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Disk;
|
||||
Reference in New Issue
Block a user