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
5 changes: 3 additions & 2 deletions playroom/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import {
Select,
IconSettingsRegular,
Icon,
Overlay,
useTheme,
useScreenSize,
Expand Down Expand Up @@ -390,7 +390,8 @@ export const PreviewTools = ({
size={40}
border
>
<IconSettingsRegular
<Icon
name="settings-regular"
className={styles.floatingButtonIcon}
size={24}
color={skinVars.colors.neutralHigh}
Expand Down
4 changes: 2 additions & 2 deletions src/__acceptance_tests__/__ssr_pages__/card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {MediaCard, DataCard, Inline, Image, ButtonPrimary, ButtonLink, IconAcademicLight} from '../../..';
import {MediaCard, DataCard, Inline, Image, ButtonPrimary, ButtonLink, IconLightningRegular} from '../../..';

const CardsTest = (): JSX.Element => (
<Inline space={16}>
Expand All @@ -21,7 +21,7 @@ const CardsTest = (): JSX.Element => (
title="title"
subtitle="subtitle"
description="description"
asset={<IconAcademicLight />}
asset={<IconLightningRegular />}
button={
<ButtonPrimary small href="https://google.com">
Action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import {
Avatar,
Badge,
IconShoppingCartRegular,
IconUserAccountRegular,
MainNavigationBar,
NavigationBarAction,
NavigationBarActionGroup,
Expand All @@ -20,7 +20,7 @@ const NavigationBarTest = (): JSX.Element => (
<NavigationBarActionGroup>
<NavigationBarAction onPress={() => {}} aria-label="shopping cart with 2 items">
<Badge value={2}>
<IconShoppingCartRegular color="currentColor" />
<IconUserAccountRegular />
</Badge>
</NavigationBarAction>
<NavigationBarAction onPress={() => {}} aria-label="Open profile">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import {
Avatar,
Badge,
IconShoppingCartRegular,
IconUserAccountRegular,
MainNavigationBar,
NavigationBarAction,
NavigationBarActionGroup,
Expand All @@ -19,7 +19,7 @@ const NavigationBarTest = (): JSX.Element => (
<NavigationBarActionGroup>
<NavigationBarAction onPress={() => {}} aria-label="shopping cart with 2 items">
<Badge value={2}>
<IconShoppingCartRegular color="currentColor" />
<IconUserAccountRegular />
</Badge>
</NavigationBarAction>
<NavigationBarAction onPress={() => {}} aria-label="Open profile">
Expand Down
6 changes: 5 additions & 1 deletion src/__private_stories__/card-action-icon-button-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import {
skinVars,
Circle,
Tag,
IconShopRegular,
CardActionIconButton,
Text2,
Stack,
IconStarFilled,
IconStarRegular,
Icon,
} from '..';

import type {IconPropsWithoutName} from '../icon';

export default {
title: 'Private/Deprecated Card Stories/Utils/CardActionIconButton',
};

const IconShopRegular = (props: IconPropsWithoutName) => <Icon {...props} name="shop-regular" />;

const MyCustomCardActionComponent = () => {
const [pressCount, setPressCount] = React.useState(0);

Expand Down
7 changes: 3 additions & 4 deletions src/__private_stories__/sheet-presets-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import {
Box,
ButtonPrimary,
Circle,
IconCheckRegular,
IconCocktailRegular,
IconLightningRegular,
IconMobileDeviceRegular,
IconTrashCanRegular,
SheetRoot,
skinVars,
Stack,
Text3,
Icon,
} from '..';
import RadioListSheet from '../sheet-radio-list';
import InfoSheet from '../sheet-info';
Expand Down Expand Up @@ -265,8 +264,8 @@ export const Info: StoryComponent<InfoSheetArgs> = ({
: {
type: iconType,
Icon: {
regular: IconCocktailRegular,
small: IconCheckRegular,
regular: () => <Icon name="cocktail-regular" />,
small: () => <Icon name="check-regular" />,
Comment on lines +267 to +268

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InfoSheet expects icon.Icon to be a React.ComponentType<IconProps>, so it can pass size, color, etc. The inline components () => <Icon name="..." /> ignore those props, which will cause incorrect icon sizing/colors.

Replace these with prop-forwarding adapters (e.g., (props) => <Icon {...props} name="cocktail-regular" />).

Suggested change
regular: () => <Icon name="cocktail-regular" />,
small: () => <Icon name="check-regular" />,
regular: (props) => <Icon {...props} name="cocktail-regular" />,
small: (props) => <Icon {...props} name="check-regular" />,

Copilot uses AI. Check for mistakes.
}[iconType],
},
}))}
Expand Down
15 changes: 10 additions & 5 deletions src/__private_stories__/skin-components-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import {
Avatar,
NavigationBreadcrumbs,
ButtonPrimary,
IconPhotoCameraRegular,
ButtonSecondary,
ButtonDanger,
ButtonLink,
Callout,
IconBoxLight,
DataCard,
IconLightningRegular,
ButtonLinkDanger,
Expand Down Expand Up @@ -53,14 +51,15 @@ import {
Meter,
Timeline,
TimelineItem,
IconShopRegular,
CoverCard,
IconExportRegular,
FileUpload,
Icon,
} from '..';
import avatarImg from '../__stories__/images/avatar.jpg';
import usingVrImg from '../__stories__/images/using-vr.jpg';

import type {IconPropsWithoutName} from '../icon';

export default {
title: 'Private/Components in different skins',
argTypes: {
Expand All @@ -86,6 +85,12 @@ const ComponentsGroup = ({children}: {children: React.ReactNode}): JSX.Element =
);
};

const IconPhotoCameraRegular = (props: IconPropsWithoutName) => (
<Icon {...props} name="photo-camera-regular" />
);
const IconBoxLight = (props: IconPropsWithoutName) => <Icon {...props} name="box-light" />;
const IconShopRegular = (props: IconPropsWithoutName) => <Icon {...props} name="shop-regular" />;

export const Default: StoryComponent<Args> = ({variantOutside}) => {
// Only show inverse header when the rest of the screen is default
const brandHeader = variantOutside === 'default';
Expand Down Expand Up @@ -356,7 +361,7 @@ export const Default: StoryComponent<Args> = ({variantOutside}) => {
<FileUpload
name="file"
withDropZone
asset={<IconExportRegular color="currentColor" />}
asset={<Icon name="export-regular" color="currentColor" />}
title="Drag or upload your file"
description="File can be up to 50Mb"
renderButton={(props) => <ButtonPrimary {...props}>Choose file</ButtonPrimary>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Badge,
ButtonPrimary,
FixedToTop,
IconShoppingCartRegular,
Icon,
MainNavigationBar,
NavigationBarAction,
NavigationBarActionGroup,
Expand Down Expand Up @@ -54,7 +54,7 @@ export const Default: StoryComponent<Args> = ({largeContent, input}) => {
aria-label="shopping cart with 2 items"
>
<Badge value={2}>
<IconShoppingCartRegular color="currentColor" />
<Icon name="shopping-cart-regular" color="currentColor" />
</Badge>
</NavigationBarAction>
<NavigationBarAction onPress={() => {}} aria-label="Open profile">
Expand Down
19 changes: 6 additions & 13 deletions src/__private_stories__/tooltip-scenarios-story.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import * as React from 'react';
import {
Stack,
Text3,
Tooltip,
Placeholder,
IconShopRegular,
Title3,
Grid,
GridItem,
SnapCard,
Circle,
IconAcademicRegular,
} from '..';
import {Stack, Text3, Tooltip, Placeholder, Title3, Grid, GridItem, SnapCard, Circle, Icon} from '..';
import {vars} from '../skins/skin-contract.css';

import type {IconPropsWithoutName} from '../icon';

export default {
title: 'Private/Tooltip',
};

const IconShopRegular = (props: IconPropsWithoutName) => <Icon {...props} name="shop-regular" />;
const IconAcademicRegular = (props: IconPropsWithoutName) => <Icon {...props} name="academic-regular" />;

export const InsideFixedContainer: StoryComponent = () => {
return (
<Stack space={16}>
Expand Down
50 changes: 40 additions & 10 deletions src/__screenshot_tests__/icons-screenshot-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,44 @@ const getCases = () => {
return cases;
};

test.each(getCases())('Icons catalog for %s (%s)', async (skin, type) => {
const page = await openStoryPage({
id: 'icons-catalog--catalog',
device: 'DESKTOP',
skin: skin as (typeof SKINS)[number],
args: {light: type === 'light', regular: type === 'regular', filled: type === 'filled', size: 32},
});
test.each(getCases())(
'Icons catalog for %s (%s)',
async (skin, type) => {
const page = await openStoryPage({
id: 'icons-catalog--catalog',
device: 'DESKTOP',
skin: skin as (typeof SKINS)[number],
args: {light: type === 'light', regular: type === 'regular', filled: type === 'filled', size: 32},
});

const icons = await page.screenshot({fullPage: true});
expect(icons).toMatchImageSnapshot();
});
// Mock CDN icon requests to return immediately with a simple SVG
await page.setRequestInterception(true);
page.on('request', (interceptedRequest) => {
if (
interceptedRequest.url().includes('mistica-icons') &&
interceptedRequest.url().endsWith('.svg')
) {
interceptedRequest
.respond({
status: 200,
contentType: 'image/svg+xml',
body: '<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor"/></svg>',
})
.catch(() => {
// Ignore errors if request was already handled
});
} else {
interceptedRequest.continue().catch(() => {
// Ignore errors if request was already handled
});
}
});

// Reload the page to ensure ALL icon requests go through the interceptor
await page.reload({waitUntil: 'networkidle2'});

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the new Icon component only fetches SVGs when the element is in the viewport, taking a single fullPage screenshot right after load will likely capture many unloaded (blank) icons below the fold.

To keep this test meaningful and deterministic, consider programmatically scrolling through the page (or disabling viewport-based lazy loading in acceptance-test mode) before taking the screenshot, so all icons have a chance to fetch/render.

Suggested change
// Scroll through the page so that all lazy-loaded icons enter the viewport
await page.evaluate(() => {
window.scrollTo(0, 0);
});
// Incrementally scroll down by one viewport height at a time until the bottom
// of the document is reached, giving icons time to load at each step.
// This keeps the screenshot deterministic even if icons load only when visible.
// eslint-disable-next-line no-constant-condition
while (true) {
const shouldContinue = await page.evaluate(() => {
const currentScroll = window.scrollY || window.pageYOffset;
const viewportHeight = window.innerHeight;
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
// If we're already at (or very near) the bottom, stop scrolling.
if (currentScroll + viewportHeight >= scrollHeight - 5) {
return false;
}
window.scrollBy(0, viewportHeight);
return true;
});
// Give the browser a moment to render newly visible icons
await page.waitForTimeout(250);
if (!shouldContinue) {
break;
}
}
// Return to the top of the page before taking the full-page screenshot
await page.evaluate(() => {
window.scrollTo(0, 0);
});

Copilot uses AI. Check for mistakes.
const icons = await page.screenshot({fullPage: true});
expect(icons).toMatchImageSnapshot();
},
120000
);
4 changes: 2 additions & 2 deletions src/__stories__/accordion-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
BoxedAccordion,
BoxedAccordionItem,
Circle,
IconThumbUpFilled,
IconMobileDeviceRegular,
Icon,
Image,
Placeholder,
ResponsiveLayout,
Expand Down Expand Up @@ -88,7 +88,7 @@ const Template: StoryComponent<BoxedArgs & {type?: 'boxed'}> = ({
/>
<ItemComponent
{...getAccordionItemContentProps()}
asset={<IconThumbUpFilled size={24} />}
asset={<Icon name="thumb-up-filled" size={24} />}
dataAttributes={{testid: 'accordion-item-2'}}
/>
<ItemComponent
Expand Down
11 changes: 9 additions & 2 deletions src/__stories__/avatar-story.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {Avatar, IconBrainRegular, IconFireRegular, IconStarFilled, ResponsiveLayout, Box} from '..';
import {Avatar, IconStarFilled, ResponsiveLayout, Box, Icon} from '..';
import avatarImg from './images/avatar.jpg';

import type {Variant} from '../theme-variant-context';
Expand Down Expand Up @@ -43,6 +43,9 @@ type Args = {
border: boolean;
};

const IconFireRegular = () => <Icon name="fire-regular" />;
const IconBrainRegular = () => <Icon name="brain-regular" />;
Comment on lines +46 to +47

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avatar’s Icon prop is invoked with IconProps (size/color). The wrappers defined here ignore those props, so the icon inside the avatar will render with default size/color instead of matching the avatar.

Define these adapters as (props) => <Icon {...props} name="..." /> (or use IconPropsWithoutName).

Suggested change
const IconFireRegular = () => <Icon name="fire-regular" />;
const IconBrainRegular = () => <Icon name="brain-regular" />;
const IconFireRegular = (props: React.ComponentProps<typeof Icon>) => (
<Icon {...props} name="fire-regular" />
);
const IconBrainRegular = (props: React.ComponentProps<typeof Icon>) => (
<Icon {...props} name="brain-regular" />
);

Copilot uses AI. Check for mistakes.

export const Default: StoryComponent<Args> = ({
size,
initials,
Expand All @@ -57,7 +60,11 @@ export const Default: StoryComponent<Args> = ({
}) => {
// eslint-disable-next-line no-eval
const badgeValue = badgeOptions.includes(badge) ? eval(badge) : undefined;
const Icon = {IconStarFilled, IconFireRegular, IconBrainRegular}[icon];
const Icon = {
IconStarFilled,
IconFireRegular,
IconBrainRegular,
}[icon];

return (
<ResponsiveLayout variant={variantOutside} fullWidth>
Expand Down
4 changes: 2 additions & 2 deletions src/__stories__/badge-story.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {Badge, IconBellFilled, ResponsiveLayout, Box, Touchable} from '..';
import {Badge, ResponsiveLayout, Box, Touchable, Icon} from '..';

import type {Variant} from '../theme-variant-context';

Expand Down Expand Up @@ -33,7 +33,7 @@ export const Default: StoryComponent<Args> = ({variantOutside, value}) => {
onPress={() => {}}
aria-label="Read notifications"
>
<IconBellFilled />
<Icon name="bell-filled" />
</Touchable>
</Badge>
</Box>
Expand Down
4 changes: 3 additions & 1 deletion src/__stories__/button-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
TextField,
Stack,
Text2,
IconPhotoCameraRegular,
Icon,
ResponsiveLayout,
ButtonLinkDanger,
} from '..';
Expand Down Expand Up @@ -91,6 +91,8 @@ const ButtonBackgroundContainer = ({variant, children}: Props) => (
</div>
);

const IconPhotoCameraRegular = () => <Icon name="photo-camera-regular" />;

export const primaryButton: StoryComponent<Args> = ({
variantOutside,
text,
Expand Down
4 changes: 2 additions & 2 deletions src/__stories__/callout-story.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {Callout, ButtonPrimary, ButtonLink, IconBoxLight, ResponsiveLayout, Box, ButtonSecondary} from '..';
import {Callout, ButtonPrimary, ButtonLink, ResponsiveLayout, Box, ButtonSecondary, Icon} from '..';

import type {Variant} from '../theme-variant-context';

Expand Down Expand Up @@ -64,7 +64,7 @@ export const Default: StoryComponent<Args> = ({
<Box paddingY={24}>
<Callout
variant={variant}
asset={asset ? <IconBoxLight /> : undefined}
asset={asset ? <Icon name="box-light" /> : undefined}
onClose={closable ? () => {} : undefined}
title={title}
description={description}
Expand Down
4 changes: 2 additions & 2 deletions src/__stories__/checkbox-story.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {Checkbox, Text3, Inline, IconCheckRegular, IconCloseRegular, ResponsiveLayout, Box} from '..';
import {Checkbox, Text3, Inline, IconCloseRegular, ResponsiveLayout, Box, Icon} from '..';

import type {Variant} from '../theme-variant-context';

Expand Down Expand Up @@ -72,7 +72,7 @@ export const CustomRender: StoryComponent<Args> = ({disabled, variantOutside}) =
<div style={{opacity: disabled ? 0.5 : undefined}}>
<Inline alignItems="center" space={16}>
{checked ? (
<IconCheckRegular size={18} />
<Icon name="check-regular" size={18} />
) : (
<IconCloseRegular size={18} />
)}
Expand Down
Loading
Loading