vojo/src/app/features/call-status/callIcons.tsx

139 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// These exports are folds `IconSrc` callbacks — `(filled?: boolean) =>
// JSX.Element` — that the folds `<Icon>` component invokes as `src(filled)`
// to populate the inner SVG content. They are NOT React components (they
// are never mounted via JSX as `<CallMicIcon />`), so the
// `react/function-component-definition` rule's "use a function
// declaration" guidance is misapplied here.
/* eslint-disable react/function-component-definition */
// Custom call-control icon set, drawn to match the design-system style
// captured in `docs/design/new-direct-messages-design/project/shared.jsx`
// (lines 4-19): 24×24 viewBox, stroke-only (no fill), 1.6px stroke weight
// for handset/mic/video glyphs, round line joins/caps, currentColor — so
// the same icons read consistently across the green/red/neutral variant
// flips of the IncomingCallStrip / CallControl buttons.
//
// Folds' `Icon` component owns the outer `<svg>` (viewBox=0 0 24 24,
// fill=none, sizing class via the `size` prop). An `IconSrc` therefore
// returns only the inner shapes — same convention as the stock
// `Icons.*` table (see folds/dist/index.js Icons block).
//
// `filled` is intentionally ignored: this is an outline icon family,
// the muted/active distinction is carried by the diagonal slash plus
// the button's variant color, not by an alternate filled shape.
import React from 'react';
import { IconSrc } from 'folds';
const STROKE = {
stroke: 'currentColor',
strokeWidth: 1.6,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
fill: 'none',
};
// Slightly heavier than the body strokes so the «muted» indicator
// reads as a deliberate cancellation rather than ornamentation.
const SLASH = {
...STROKE,
strokeWidth: 2,
};
export const CallMicIcon: IconSrc = () => (
<>
<rect x="9" y="3" width="6" height="12" rx="3" {...STROKE} />
<path d="M5 11a7 7 0 0 0 14 0" {...STROKE} />
<path d="M12 18v3" {...STROKE} />
</>
);
export const CallMicMuteIcon: IconSrc = () => (
<>
<rect x="9" y="3" width="6" height="12" rx="3" {...STROKE} />
<path d="M5 11a7 7 0 0 0 14 0" {...STROKE} />
<path d="M12 18v3" {...STROKE} />
<path d="M3 3l18 18" {...SLASH} />
</>
);
// Loudspeaker «on» — cone + two sound waves. Paired with the variant
// flip (Success when active) so «громкая связь» reads at a glance.
export const CallSpeakerIcon: IconSrc = () => (
<>
<path d="M4 9h3l5-4v14l-5-4H4z" {...STROKE} />
<path d="M16 9a4 4 0 0 1 0 6" {...STROKE} />
<path d="M18.5 6.5a8 8 0 0 1 0 11" {...STROKE} />
</>
);
// Speaker «off» (earpiece) — the same cone with the sound waves dropped,
// not a slash: audio still plays, just through the earpiece. The neutral
// Surface variant carries the «inactive» cue.
export const CallSpeakerMuteIcon: IconSrc = () => <path d="M4 9h3l5-4v14l-5-4H4z" {...STROKE} />;
export const CallVideoIcon: IconSrc = () => (
<>
<rect x="3" y="6" width="13" height="12" rx="2" {...STROKE} />
<path d="M16 10l5-3v10l-5-3" {...STROKE} />
</>
);
export const CallVideoMuteIcon: IconSrc = () => (
<>
<rect x="3" y="6" width="13" height="12" rx="2" {...STROKE} />
<path d="M16 10l5-3v10l-5-3" {...STROKE} />
<path d="M3 3l18 18" {...SLASH} />
</>
);
export const CallScreenShareIcon: IconSrc = () => (
<>
<rect x="3" y="4" width="18" height="13" rx="2" {...STROKE} />
<path d="M9 21h6M12 17v4" {...STROKE} />
<path d="M8.5 11l3.5-3.5L15.5 11" {...STROKE} />
<path d="M12 7.5V14" {...STROKE} />
</>
);
// Screenshare-off carries the same slash convention as the other muted
// glyphs so the on/off state has both a shape cue and a variant cue —
// matters for high-contrast / monochrome modes where the
// Success-vs-Surface variant flip alone is too subtle.
export const CallScreenShareMuteIcon: IconSrc = () => (
<>
<rect x="3" y="4" width="18" height="13" rx="2" {...STROKE} />
<path d="M9 21h6M12 17v4" {...STROKE} />
<path d="M8.5 11l3.5-3.5L15.5 11" {...STROKE} />
<path d="M12 7.5V14" {...STROKE} />
<path d="M3 3l18 18" {...SLASH} />
</>
);
// Classic «pick up» handset, used on the IncomingCallStrip Answer
// button. Kept upright (no rotation) so it reads as «receive call».
export const CallPhoneIcon: IconSrc = () => (
<path
d="M5 4h4l2 5-3 2a12 12 0 0 0 6 6l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 6a2 2 0 0 1 2-2z"
{...STROKE}
/>
);
// Same handset rotated 135° — universal «hang up / decline» glyph
// (handset tilted off the cradle). Reused for Decline (IncomingCallStrip)
// and Hangup (CallControl).
//
// The source handset fills a ~18×18 box whose centre sits at (12, 13), not
// (12, 12). Rotating it 135° about (12, 12) therefore (a) drifts the glyph
// off-centre and (b) pushes the rotated box (~25px diagonal) past the 24×24
// viewBox, so the corners clip. Fix: recentre the box on the viewBox centre,
// rotate, then scale to 0.82 so the rotated glyph fits with stroke margin.
// (SVG applies the transform list right-to-left.)
export const CallPhoneDownIcon: IconSrc = () => (
<g transform="translate(12 12) rotate(135) scale(0.82) translate(-12 -13)">
<path
d="M5 4h4l2 5-3 2a12 12 0 0 0 6 6l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 6a2 2 0 0 1 2-2z"
{...STROKE}
/>
</g>
);