Skip to content
Merged
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
2 changes: 1 addition & 1 deletion packages/components/src/components/hds/icon/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
aria-hidden="{{if @title 'false' 'true'}}"
aria-labelledby={{this.ariaLabelledby}}
data-test-icon={{@name}}
fill="{{this.color}}"
fill="{{this.fillColor}}"
id={{this.iconId}}
role={{this.role}}
width="{{this.svgSize.width}}"
Expand Down
36 changes: 28 additions & 8 deletions packages/components/src/components/hds/icon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import Component from '@glimmer/component';
import { guidFor } from '@ember/object/internals';
import { assert } from '@ember/debug';
import { iconNames } from '@hashicorp/flight-icons/svg';
import { HdsIconSizeValues } from './types.ts';
import type { HdsIconSizes } from './types';
import { HdsIconSizeValues, HdsIconColorValues } from './types.ts';
import type { HdsIconSizes, HdsIconColors } from './types';
import type { IconName } from '@hashicorp/flight-icons/svg';

export const AVAILABLE_COLORS: string[] = Object.values(HdsIconColorValues);

export interface HdsIconSignature {
Args: {
name: IconName;
color?: string;
color?: HdsIconColors | string | undefined;
size?: HdsIconSizes;
stretched?: boolean;
isInline?: boolean;
Expand All @@ -24,6 +26,9 @@ export interface HdsIconSignature {
}

export default class HdsIcon extends Component<HdsIconSignature> {
iconId = 'icon-' + guidFor(this);
titleId = 'title-' + guidFor(this);

constructor(owner: unknown, args: HdsIconSignature['Args']) {
super(owner, args);

Expand All @@ -40,11 +45,23 @@ export default class HdsIcon extends Component<HdsIconSignature> {
return this.args.isInline ?? false;
}

get color(): string {
return this.args.color ?? 'currentColor';
get predefinedColor(): HdsIconColors | undefined {
const { color } = this.args;

if (color && AVAILABLE_COLORS.includes(color)) {
return color as HdsIconColors;
} else {
return undefined;
}
}

iconId = 'icon-' + guidFor(this);
get fillColor(): string {
if (this.predefinedColor !== undefined) {
return 'currentColor';
} else {
return this.args.color ?? 'currentColor';
}
}

get size(): string {
return this.args.size ?? HdsIconSizeValues.Sixteen;
Expand All @@ -57,8 +74,6 @@ export default class HdsIcon extends Component<HdsIconSignature> {
};
}

titleId = 'title-' + guidFor(this);

get title(): string | null {
return this.args.title ?? null;
}
Expand All @@ -82,6 +97,11 @@ export default class HdsIcon extends Component<HdsIconSignature> {
classes.push('hds-icon--is-inline');
}

// add a (helper) class based on the @color argument (if pre-defined)
if (this.predefinedColor) {
classes.push(`hds-foreground-${this.predefinedColor}`);
}

// add an extra class to control the animation (depends on the icon)
if (['loading', 'running'].includes(name)) {
classes.push(`hds-icon--animation-${name}`);
Expand Down
24 changes: 24 additions & 0 deletions packages/components/src/components/hds/icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,27 @@ export enum HdsIconSizeValues {
}

export type HdsIconSizes = `${HdsIconSizeValues}`;

export enum HdsIconColorValues {
Copy link
Contributor

Choose a reason for hiding this comment

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

(question): wouldn't we want all of these to be HdsColorValues anyway? Is there a need to have them specifically in IconColorValues? It seems reasonable that if these are our ColorValues we'd want them available for everywhere that we use these values. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about it, but I ended up keeping them separate for different reasons:

  1. we may want to add also the "brand" colors for the icon (but not the text, I don't see use cases for that); at the moment thought is not clear how we would do it: have different brand colors, which ones would be the ones that could be used for an icon?
  2. this would require to add a global types.ts file, under packages/components/src/components/hds; not a big deal but maybe something we want to consider a bit more

so my thinking is that we can always do it, it's not something for this specific PR

Primary = 'primary',
Strong = 'strong',
Faint = 'faint',
Disabled = 'disabled',
HighContrast = 'high-contrast',
Action = 'action',
ActionHover = 'action-hover',
ActionActive = 'action-active',
Highlight = 'highlight',
HighlightOnSurface = 'highlight-on-surface',
HighlightHighContrast = 'highlight-high-contrast',
Success = 'success',
SuccessOnSurface = 'success-on-surface',
SuccessHighContrast = 'success-high-contrast',
Warning = 'warning',
WarningOnSurface = 'warning-on-surface',
WarningHighContrast = 'warning-high-contrast',
Critical = 'critical',
CriticalOnSurface = 'critical-on-surface',
CriticalHighContrast = 'critical-high-contrast',
}
export type HdsIconColors = `${HdsIconColorValues}`;
10 changes: 9 additions & 1 deletion showcase/app/routes/components/icon.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import Route from '@ember/routing/route';

export default class IconRoute extends Route {}
import { AVAILABLE_COLORS } from '@hashicorp/design-system-components/components/hds/icon/index';

export default class IconRoute extends Route {
model() {
return {
AVAILABLE_COLORS,
};
}
}
6 changes: 6 additions & 0 deletions showcase/app/styles/showcase-pages/icon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ body.components-icon {
gap: 4px;
align-items: center;
}

.shw-component-icon-sample-color--high-contrast {
width: fit-content;
background: #0c0c0e;
outline: 2px solid #0c0c0e;
}
}
65 changes: 45 additions & 20 deletions showcase/app/templates/components/icon.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,71 @@
</SF.Item>
</Shw::Flex>

<Shw::Divider @level={{2}} />
<Shw::Divider />

<Shw::Text::H2>Color</Shw::Text::H2>

<Shw::Text::H4 @tag="h3">Color inheritance</Shw::Text::H4>
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: you could just do <Shw::Text::H3>Color inheritance</Shw::Text::H4>

Copy link
Contributor

Choose a reason for hiding this comment

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

Missed that the @tag was changed initially. If you want the visual style of the smaller H4 here for some reason then this is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nit: you could just do Shw::Text::H3Color inheritance</Shw::Text::H4>

<Shw::Text::H4 @tag="h3"> means render an <h3> tag with the visual style of a .shw-text-h4

<Shw::Flex @direction="column" as |SF|>
<SF.Item as |SFI|>
<SFI.Label>Unspecified color (<code>currentColor</code>)</SFI.Label>
<Hds::Icon @name="bookmark" />
<SFI.Label>unspecified color (<code>currentColor</code>)</SFI.Label>
<div>
<Hds::Icon @name="lock-fill" @size="24" />
</div>
</SF.Item>
<SF.Item as |SFI|>
<SFI.Label>Custom red color, specified via <code>@color</code> argument</SFI.Label>
<Hds::Icon @name="folder-fill" @color="red" />
<SF.Item as |SGI|>
<SGI.Label>parent with <code>#e12568</code> color</SGI.Label>
<div {{style color="#e12568"}}>
<Hds::Icon @name="lock-fill" @size="24" />
</div>
</SF.Item>
<SF.Item as |SFI|>
<SFI.Label>Unspecified color (<code>currentColor</code>) + parent with custom blue color (to check inheritance)</SFI.Label>
<a {{style color="blue"}} href="#">
<Hds::Icon @name="external-link" />
</a>
</Shw::Flex>

<Shw::Divider @level={{2}} />

<Shw::Text::H4 @tag="h3">Pre-defined colors</Shw::Text::H4>
<Shw::Grid @columns={{5}} as |SG|>
{{#each this.model.AVAILABLE_COLORS as |color|}}
<SG.Item @label={{color}}>
<div class="shw-component-icon-sample-color--{{color}}">
<Hds::Icon @name="lock-fill" @color={{color}} @size="24" />
</div>
</SG.Item>
{{/each}}
</Shw::Grid>

<Shw::Divider @level={{2}} />

<Shw::Text::H4 @tag="h3">Custom colors</Shw::Text::H4>
<Shw::Flex @direction="column" as |SF|>
<SF.Item as |SGI|>
<SGI.Label><code>#e91e63</code></SGI.Label>
<Hds::Icon @name="lock-fill" @color="#e91e63" @size="24" />
</SF.Item>
<SF.Item as |SFI|>
<SFI.Label>Custom orange color, specified via
<code>@color</code>
argument + parent with
<code>!important</code>
green color declared (to check overrides not working)</SFI.Label>
<SF.Item as |SGI|>
<SGI.Label><code>--token-color-palette-purple-400</code></SGI.Label>
<Hds::Icon @name="lock-fill" @color="var(--token-color-palette-purple-400)" @size="24" />
</SF.Item>
<SF.Item as |SGI|>
<SGI.Label><code>orange</code>
+ parent with
<code>green !important</code>
</SGI.Label>
{{! template-lint-disable no-inline-styles }}
<div style="color:green !important">
<Hds::Icon @name="heart-fill" @color="orange" />
<Hds::Icon @name="lock-fill" @color="orange" @size="24" />
</div>
{{! template-lint-enable no-inline-styles }}
</SF.Item>
</Shw::Flex>

<Shw::Divider @level={{2}} />
<Shw::Divider />

<Shw::Text::H2>Display</Shw::Text::H2>

{{#let (array false true) as |booleans|}}
{{#each booleans as |isInline|}}
<Shw::Text::Body>{{if isInline "Inline" "Block (default)"}}</Shw::Text::Body>
<Shw::Text::H4 @tag="h3">{{if isInline "Inline" "Block (default)"}}</Shw::Text::H4>

<Shw::Flex class="shw-foundation-outline-icons" as |SF|>
<SF.Item @label="single icon">
Expand Down
6 changes: 3 additions & 3 deletions website/docs/components/icon/partials/code/component-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
<C.Property @name="name" @type="string" @required="true">
The name of the icon you wish to use. If the value does not match an existing icon name, an error will be thrown. Search for existing icon names in [the Icon library](/icons/library).
</C.Property>
<C.Property @name="color" @type="string" @default="currentColor">
The `@color` argument can be used to change the color. It works by setting the value of the icon SVG’s `fill` property.
</C.Property>
<C.Property @name="size" @type="number" @default="16" @values={{array "16" "24"}}>
Sets the size of the icon in pixels. Only two sizes are supported. (Setting a non-standard size will cause the SVG to render at the specified size but it will be invisible.)
</C.Property>
<C.Property @name="color" @type="string | CSS color" @values={{array "primary" "strong" "faint" "disabled" "high-contrast" "action" "action-hover" "action-active" "highlight" "highlight-on-surface" "highlight-high-contrast" "success" "success-on-surface" "success-high-contrast" "warning" "warning-on-surface" "warning-high-contrast" "critical" "critical-on-surface" "critical-high-contrast" }}>
The color of the icon expressed as one of the possible [foreground color](/foundations/colors?tab=palette#foreground-1) names. As a fallback solution to handle special cases, a valid CSS color string (hex, rgb, rgba, etc.) is also accepted (in this case it works by setting the value of the icon SVG’s `fill` property). If no `@color` argument is provided, the component will inherit its color from the parent container/context (`fill="currentColor"`).
</C.Property>
<C.Property @name="stretched" @type="boolean" @default="false">
Determines whether the icon will stretch to fill the parent container. Setting it to `true` will make the icon have a height and width of 100%.
</C.Property>
Expand Down
20 changes: 17 additions & 3 deletions website/docs/components/icon/partials/code/how-to-use.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,21 @@ The default size is 16px. To use the alternative 24px icon size, set the `@size`

### Color

The default value is `currentColor` which uses the inherited text color as the icon color. When setting a custom value, we recommend using one of the pre-defined variables to ensure consistency with our design language:
The default value is `currentColor` which uses the inherited text color as the icon color. When setting a custom value, we recommend using one of the pre-defined **foreground** color variables to ensure consistency with our design language:

```handlebars
<Hds::Icon @name="zap" @color="var(--token-color-foreground-success)" />
<Hds::Icon @name="zap" @color="success" />
```

Other accepted values include named colors and color values themselves (e.g., hex, rgb, etc).
For the list of possible foreground colors supported, refer to the [Component API](#component-api) section for details.

It’s also possible to provide a CSS color as a string (in this case the color will be applied as SVG `fill` property). The string can be a CSS `var()` that uses one of the [predefined color tokens](/foundations/colors?tab=palette):

```handlebars
<Hds::Icon @name="zap" @color="var(--token-color-boundary-brand)" />
```

Or it can be one of the standard CSS color formats (hex, rgb, rgba, hsl, named color, etc.):

```handlebars
<Hds::Icon @name="zap" @color="rebeccapurple" />
Expand All @@ -52,6 +60,12 @@ Other accepted values include named colors and color values themselves (e.g., he
<Hds::Icon @name="zap" @color="rgb(46, 113, 229)" />
```

!!! Warning

We don’t validate the CSS color string to ensure that the value used is correct.

!!!

### Stretched

To have the icon fill the parent container (width: 100%, height: 100%), set the `@stretched` attribute to true:
Expand Down