Support BigBlueButton icon font glyphs across the component library
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 45/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- react, typescript
Direzione di ricerca
Start by examining the existing icon usage in the component library, particularly in src/components/Button/styles.ts:218 and the components listed in the issue (BBBModal, BBBHint, BBBAccordion, BBBSelect, BBBSearch). Understand how the BBB client's icon font works by looking at public/stylesheets/bbb-icons.css and the Icon component in the client. The first step is to create the new BBBIcon component in src/components/Icon/, then generate the icon names from bbb-icons.css. Finally, update each component to accept the new icon prop types using a helper like renderIcon.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Is your feature request related to a problem? Please describe.
Every icon slot in this library is typed as React.ReactNode, and the built-in defaults are drawn from three unrelated sources — react-icons/md, @mui/icons-material, and hand-written inline SVG:
| Component | Built-in icon | Source |
|---|---|---|
BBButton |
helperIcon default, feedbackContent default |
MdSettings, MdCheckCircle |
BBBNavigation |
fallback icon |
MdExpandCircleDown |
BBBHint |
icon default, close button |
MdInfo, MdClose |
BBBModal |
close button | MdClose |
BBBAccordion |
expand chevron | MdExpandMore |
BBBSelect |
dropdown arrow | MdExpandMore |
BBBCheckbox |
round variant | RadioButtonUnchecked / RadioButtonChecked |
BBBSearch |
search glyph, clear button | hand-written inline <svg> |
The BigBlueButton client draws its own UI with a different icon set — the bbb-icons webfont. A plugin built with this library therefore renders Material Design glyphs next to BBB glyphs, in the same toolbar, side by side. There is currently no supported way to ask a component for a BBB glyph.
The library has in fact already grown an undocumented accommodation for it. src/components/Button/styles.ts:218 contains:
export const IconWrapper = styled.div`
...
> i {
font-size: 2rem;
}
`;
A bare > i element selector only makes sense for icon-font <i> elements — the icon-font path exists in this codebase today, it is just accidental rather than designed.
On top of that, several icons are hardcoded with no prop to override them at all (BBBModal close, BBBAccordion expand, BBBSelect dropdown arrow, BBBSearch search/clear), so even the workaround above cannot reach them.
How BigBlueButton does it today (analysis)
Checked against bigbluebutton/bigbluebutton, bigbluebutton-html5.
The font and its stylesheet are global, and live outside React.
public/fonts/BbbIcons/bbb-icons.woff2andbbb-icons.woffpublic/stylesheets/bbb-icons.css— 437 lines, 135.icon-bbb-*rulesclient/main.htmlwires both up before the app boots:
<!-- client/main.html:164 -->
<link rel="preload" href="fonts/BbbIcons/bbb-icons.woff2?v=VERSION" as="font" type="font/woff2" crossorigin="anonymous"/>
<!-- client/main.html:174 -->
<link rel="stylesheet" href="stylesheets/bbb-icons.css" crossorigin="anonymous"/>
<!-- client/main.html:182 -->
<style>
@font-face {
font-family: 'bbb-icons';
src: url('fonts/BbbIcons/bbb-icons.woff2?v=VERSION') format('woff2'),
url('fonts/BbbIcons/bbb-icons.woff?v=VERSION') format('woff');
font-weight: normal;
font-style: normal;
}
</style>
The stylesheet does the styling; the React component only picks a class.
/* public/stylesheets/bbb-icons.css */
[class^="icon-bbb-"], [class*=" icon-bbb-"] {
font-family: 'bbb-icons' !important;
display: inline-block;
font-style: normal;
font-weight: 400;
line-height: 1;
width: 1em;
text-align: center;
vertical-align: middle;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-bbb-thumbs_up:before { content: "\e92d"; }
Two consequences worth keeping: the glyph is sized by font-size (it is 1em wide) and colored by color, exactly like text — so it composes with the flex wrappers this library already uses; and [class^="icon-bbb-"] matches any class with that prefix, which is how plugins extend the set (see below).
The client's own Icon component is a thin class-name builder (imports/ui/components/common/icon/icon-ts/component.tsx):
const Icon: React.FC<IconProps> = ({
ariaHidden, ariaLabel, className = '',
prependIconName = 'icon-bbb-', iconName = '', rotate = false,
}) => {
const isAriaHidden = ariaHidden ?? !ariaLabel;
return (
<Styled.Icon
aria-hidden={isAriaHidden}
aria-label={isAriaHidden ? undefined : ariaLabel}
role={isAriaHidden ? undefined : 'img'}
className={cx(className, [prependIconName, iconName].join(''))}
$rotate={rotate}
/>
);
};
Styled.Icon is styled.i with only a $rotate transform. The older component.jsx variant adds a color prop. Note the accessibility default: hidden unless an ariaLabel is given, in which case it becomes role="img".
prependIconName is a real extension point, not decoration. bigbluebutton-plugin-audioplayer/src/main.css ships its own glyphs under the same family and prefix:
@font-face {
font-family: 'bbb-icons';
src: url('../assets/audio-player.woff') format('woff');
}
.icon-bbb-audio-player:before { content: "\e820"; }
Any design here must keep a name outside the known set renderable.
The plugin SDK already standardised an icon-name value type. bigbluebutton-html-plugin-sdk/src/extensible-areas/common/icon/types.ts:
export interface PluginIconName { iconName: string; }
export interface PluginIconSvgContent { svgContent: React.SVGProps<SVGSVGElement>; }
export type PluginIconType = string | PluginIconName | PluginIconSvgContent;
and the client normalises it in several places, e.g. imports/ui/components/common/menu/component.jsx:23 and imports/ui/components/common/separator/component.tsx:15-17:
if (typeof icon === 'string') return <Icon iconName={icon} ... />;
if (icon && typeof icon === 'object' && 'iconName' in icon) return <Icon iconName={icon.iconName} ... />;
if (icon && typeof icon === 'object' && 'svgContent' in icon) return <Wrapper>{icon.svgContent}</Wrapper>;
Plugin authors already hold PluginIconType values. If this library speaks the same shape, those values can be forwarded to a BBButton unchanged.
Duplication is a known cost in the monorepo. bbb-learning-dashboard keeps its own copy at src/fonts/BbbIcons/bbb-icons.woff. Adding a third copy that ships inside the client bundle would be a regression, which drives the font delivery decision below.
Describe the solution you'd like
Six parts. Parts 1–3 are the core and are non-breaking; parts 4–6 are the ergonomics and coverage work.
1. A new BBBIcon component
New folder src/components/Icon/, exported as BBBIcon, mirroring the client's contract so that a glyph rendered by this library is indistinguishable from one rendered by BBB itself.
It ships no CSS and loads no font. It only emits the class name and relies on the rules already present in the host document.
export interface IconProps {
/** Name of the glyph in the BBB icon font, without the `icon-bbb-` prefix. */
name: BBBIconName | (string & {});
/** Class prefix prepended to `name`; override it to reach glyphs a plugin added to the font. @default 'icon-bbb-' */
prefix?: string;
/** Glyph size, applied as `font-size`; the glyph is 1em wide and square. @default '1em' */
size?: string;
/** Glyph color, applied as `color`; inherits from the parent when omitted. */
color?: string;
/** Rotates the glyph 180°, mirrored under `dir="rtl"`. @default false */
rotate?: boolean;
/** Accessible name; when set, the icon becomes `role="img"` instead of being hidden. */
ariaLabel?: string;
/** Forces the icon to be hidden from assistive technology; defaults to `true` unless `ariaLabel` is set. */
ariaHidden?: boolean;
/** Extra class names merged with the generated icon class. */
className?: string;
/** Value for the `data-test` attribute. */
dataTest?: string;
}
name is typed as BBBIconName | (string & {}) rather than a closed union: autocomplete lists the 135 known names, while a plugin-provided glyph still type-checks.
2. Generated, typed icon names
scripts/generate-icon-names.ts parses bbb-icons.css from a pinned BBB version and emits src/components/Icon/icon-names.ts:
export const BBB_ICON_NAMES = ['about', 'add', 'alert', /* …135 total */] as const;
export type BBBIconName = typeof BBB_ICON_NAMES[number];
Regenerating is a deliberate, reviewable commit, so the pinned BBB version is visible in the diff and the union never drifts silently.
3. Font assets only outside the BBB client
The constraint: inside the BBB client the library must add nothing — no @font-face, no re-declaration of bbb-icons.css. Redeclaring @font-face for the family bbb-icons would fight the client's own declaration, and shipping the .woff2 in the bundle would be a third copy of a font already served.
Proposal:
-
The same generator vendors
bbb-icons.css+bbb-icons.woff2as a dev-only asset, imported by.storybook/preview.ts. It is not reachable from any component's module graph, so it cannot end up in a consumer's bundle. -
For apps outside BBB that still want the glyphs, a separate opt-in entry point with the side effect:
// only for apps that are NOT the BBB client import '@bigbluebutton/bbb-ui-components-react/icon-font';Added to
package.jsonexportsand towebpack.config.babel.jslike any other entry, and listed insideEffects. -
No component ever imports it. Inside BBB, nobody calls it and nothing is added.
The BBBIcon README documents this explicitly: in a BBB plugin, import nothing — the font is already there.
4. Accept icon names on the existing icon props
A shared internal helper normalises the three accepted shapes, matching what the client already does for PluginIconType:
// src/components/Icon/render-icon.tsx
export type BBBIconProp = React.ReactNode | BBBIconName | { iconName: string };
export const renderIcon = (icon: BBBIconProp): React.ReactNode => {
if (typeof icon === 'string') return <BBBIcon name={icon} />;
if (icon && typeof icon === 'object' && 'iconName' in icon) return <BBBIcon name={icon.iconName} />;
return icon;
};
Every existing icon prop widens from React.ReactNode to BBBIconProp and is passed through renderIcon at render time. <MdSend /> keeps working unchanged.
Breaking-change flag.
React.ReactNodealready includesstring, so todayicon="save"renders the literal textsave. Treating a bare string as an icon name changes runtime behaviour for anyone who relied on that. It is an unlikely usage — none of these props are documented as accepting text — but it is a real change. Options, to be settled in review: (a) ship the bare-string form and land it on the next major; (b) accept only the{ iconName }object form, fully backwards compatible but more verbose and only partiallyPluginIconType-compatible; (c) ship (b) now and (a) in the next major. Recommendation: (a), since the whole point is letting plugin authors forwardPluginIconTypevalues verbatim.
5. Expose the icons that are currently hardcoded
These have no override today. Each gets a BBBIconProp prop whose default is the current Material icon, so behaviour is unchanged when the prop is omitted.
| Component | New prop | Replaces | Current default |
|---|---|---|---|
BBBModal |
closeIcon |
component.tsx:70 |
<MdClose size="1.5rem" /> |
BBBHint |
closeIcon |
component.tsx:58 |
<MdClose fontSize="1rem" /> |
BBBAccordion |
expandIcon |
component.tsx:39 |
<MdExpandMore /> |
BBBSelect |
dropdownIcon |
component.tsx:38 |
MdExpandMore (MUI IconComponent) |
BBBSearch |
searchIcon, clearIcon |
component.tsx:87-103, :129-145 |
hand-written inline <svg> |
BBBSelect.dropdownIcon needs care: MUI's IconComponent takes a component type, not an element, so the prop has to be adapted rather than forwarded.
BBBCheckbox's RadioButtonUnchecked / RadioButtonChecked are left alone — they are the round variant's control rendering, not a decorative icon slot.
6. Replace the ad-hoc > i rule with a documented sizing contract
src/components/Button/styles.ts:218 currently hardcodes > i { font-size: 2rem; }. With BBBIcon owning size, that rule should become an explicit default that a caller can override, and the same sizing question answered consistently for BBBNavigation, BBBHint, BBBSlider and BBBInput, whose icon wrappers are each sized differently today.
Worth documenting once, in the BBBIcon README: a font glyph inherits font-size and color, so size/color behave like the identically named props of react-icons — the two icon sources stay interchangeable inside the same wrapper.
Describe alternatives you've considered
Do nothing; consumers keep passing <i className="icon-bbb-…" />. Rejected: no type safety, no a11y defaults, no sizing contract, the > i hack stays undocumented, and it cannot reach the hardcoded icons at all.
Convert the font to individual React SVG components (à la react-icons), generated from the font. Best tree-shaking and no global CSS dependency at all. Rejected: the font is not the source of truth for the vectors, the generated output would drift from the client's rendering, and it duplicates in bytes exactly what the client already loads — for the primary consumer (a plugin inside BBB) this is strictly worse.
Always bundle the font and the @font-face in the library. Self-contained and the simplest story for Storybook. Rejected per the constraint above: it duplicates ~30KB already served by the client, declares a competing @font-face for the family bbb-icons, and risks rendering a different font version than the surrounding UI.
Re-export the client's Icon component from the SDK instead. The SDK does not export it, and the library must stay usable outside a plugin context. Keeping a local BBBIcon whose contract matches the client's is the cheaper coupling.
Affected component(s)
BBBIcon (new), BBButton, BBBNavigation, BBBHint, BBBSelect, BBBInput, BBBSlider, BBBModal, BBBAccordion, BBBSearch.
Prop changes, per component
| Component | Prop | Change |
|---|---|---|
BBBIcon |
— | New component |
BBButton |
icon (circle/squared/stacked) |
React.ReactNode → BBBIconProp |
BBButton |
iconStart, iconEnd (default layout) |
React.ReactNode → BBBIconProp |
BBButton |
helperIcon (stacked) |
React.ReactNode → BBBIconProp |
BBBNavigation |
icon |
React.ReactNode → BBBIconProp |
BBBHint |
icon |
React.ReactNode → BBBIconProp |
BBBHint |
closeIcon |
New, defaults to <MdClose fontSize="1rem" /> |
BBBSelect |
icon |
React.ReactNode → BBBIconProp |
BBBSelect |
dropdownIcon |
New, defaults to MdExpandMore |
BBBInput |
buttonIcon |
React.ReactNode → BBBIconProp |
BBBSlider |
iconStart, iconEnd |
React.ReactNode → BBBIconProp |
BBBModal |
closeIcon |
New, defaults to <MdClose size="1.5rem" /> |
BBBAccordion |
expandIcon |
New, defaults to <MdExpandMore /> |
BBBSearch |
searchIcon, clearIcon |
New, default to the current inline SVGs |
Not changed: BBButton.feedbackContent, BBBInput.beforeButton / afterButton / sentFeedbackContent, BBBAccordion.buttonHeader, BBBModal.footerContent — these are general content slots, not icon slots, and already accept a <BBBIcon /> element as-is. BBBCheckbox and BBBToggle are unaffected.
Proposed API / Usage Example
import { BBBIcon, BBButton, BBBHint, BBBModal } from '@bigbluebutton/bbb-ui-components-react';
// 1. Standalone, inside any content slot
<BBBIcon name="thumbs_up" />
<BBBIcon name="presentation" size="1.5rem" ariaLabel="Presentation" />
<BBBIcon name="right_arrow" rotate />
// 2. By name, on an existing icon prop (replaces the raw <i> workaround)
<BBButton layout="circle" size="sm" icon="thumbs_up" onClick={upvote} ariaLabel="Upvote" />
// 3. Forwarding a PluginIconType value straight from the SDK, unchanged
<BBButton layout="squared" icon={pluginItem.icon} onClick={run} ariaLabel={pluginItem.label} />
// 4. Replacing an icon that is hardcoded today
<BBBModal isOpen onRequestClose={close} title="Settings" closeIcon={<BBBIcon name="close" />}>
...
</BBBModal>
// 5. Material icons keep working, unchanged
<BBBHint label="Recording started" icon={<MdInfo fontSize="1rem" />} />
// 6. Glyph absent from the core font, added by the plugin's own font file;
// type-checks thanks to the `string & {}` arm of `name`
<BBBIcon name="audio-player" />
Outside the BBB client only (Storybook, standalone app, tests):
import '@bigbluebutton/bbb-ui-components-react/icon-font';
Additional context
Suggested delivery order. Parts 1–3 are self-contained and unblock consumers immediately (icon={<BBBIcon name="…" />} works on every existing prop without any other change). Parts 4–6 can follow as separate PRs; part 4 carries the breaking-change decision and should be settled before it lands.
Open questions for review
- Bare string vs
{ iconName }only — see the flag under part 4. - Where is the pinned
bbb-icons.cssread from at generation time: a committed vendored copy, or fetched from a tag ofbigbluebutton/bigbluebutton? - Should the generator run in CI to detect drift against a BBB release, or stay fully manual?
BBBSelect.dropdownIconhas to bridge MUI'sIconComponent(component type) and this library's element-based icon props — confirm the adapter shape.- Does
BBBIconneed thesvgContentarm ofPluginIconTypetoo, so a plugin can forward anyPluginIconTypevalue, not just the name-based ones?
Repository conventions. The new component follows CLAUDE.md: folder src/components/Icon/ with component.tsx, styles.ts, index.ts, types.ts, constants.ts, component.stories.tsx, README.md, assets/example.png; plus entries in src/components/index.ts, the root README.md, webpack.config.babel.js and package.json exports. The Storybook story is the natural place to render the full 135-glyph grid as living documentation of the available names.
Sources consulted
bigbluebutton/bigbluebutton-html5:public/stylesheets/bbb-icons.css,public/fonts/BbbIcons/,client/main.html:164-190,imports/ui/components/common/icon/,imports/ui/components/common/menu/component.jsx:23-44,imports/ui/components/common/separator/component.tsx:15-17bigbluebutton-html-plugin-sdk:src/extensible-areas/common/icon/types.tsbigbluebutton-plugin-audioplayer:src/main.css:1-20- This repository:
src/components/Button/styles.ts:218, and the icon props of every component listed above
- Lingua principale
- TypeScript
- Stelle
- 2
- Fork
- 5
- Merge medio
- 2g 5h
- PR unite (30g)
- 9
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di bigbluebutton/bbb-ui-components-react
-
BBButton: subtle variant with neutral color has an opaque white background, so it renders white on white in dark modeForse già presa @nisatsama l’ha presa 1 giorno fa. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
bigbluebutton/bbb-ui-components-react#100 · 1 commento ·
-
Add support for dark themeApertaenhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 30/100
-
Add standard component for notifications (BBBToast)Forse di nuovo libera @Arthurk12 l’ha presa 32 giorni fa e non c’è nessuna pull request aperta. Apertaenhancement
bigbluebutton/bbb-ui-components-react#72 · 1 assegnatario ·
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
Tutte le issue di bigbluebutton/bbb-ui-components-react
Issue simili
-
feat(subscription): add Manage Subscription (Stripe portal) to the Subscription tab plan cardAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
[Feature] 通知栏合并重复消息并显示次数Apertaarea:ui enhancement issue-form:feature platform:cross-platform review: high
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
1lck/Lithe-IDEA#1092 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
developmentseed/deck.gl-raster#693 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
Marker-Inc-Korea/AutoRAG#1801 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
iii-hq/iii#2278 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno