Skip to content

Commit

Permalink
[PLAY-1138] Icon kit: add a color prop (#3527)
Browse files Browse the repository at this point in the history
**What does this PR do?** A clear and concise description with your
runway ticket url.
[PLAY-1138](https://runway.powerhrg.com/backlog_items/PLAY-1138) adds a
color prop to the icon kit so that a dev can color an icon with one of
the [Playbook color
tokens](https://playbook.powerapp.cloud/visual_guidelines/colors). An
Icon Color doc example section has been added to the Rails and React
Icon kit pages showing how to add a color prop (and showing that it
works with both playbook-icons and font awesome icons). The classname is
updated to include "color_#{color_token}" when a color token is used.
Tests have been added as well.

In screenshot section see exploration into color cascade/css hierarchy
within an isolated playbook section -> IRL for nitro-web the css in
place and cascades/overrides seem to like they are set up with a bit
more complexity (and ID specificity).

**Screenshots:** Screenshots to visualize your addition/change
Rails Icon Color Doc Example
<img width="1190" alt="rails for PR"
src="https://github.com/user-attachments/assets/29506a9b-6ef8-4c97-988d-37f39c79448a">
React Icon Color Doc Example
<img width="1066" alt="react for PR"
src="https://github.com/user-attachments/assets/9f57e814-f395-42dd-928a-81225e04b926">

Below is an example of expected/idealized color cascade/hierarchy. Three
screenshots from within playbook with example code borrowed from
nitro-web's Powerlife Did You Know section.
Icon default color (no css wrapper or color prop applied)
<img width="326" alt="copied nitro-web no imported styles icon is
default color"
src="https://github.com/user-attachments/assets/cad52b3b-f8d7-4220-a07a-ab16d0c5d907">
Icon takes color from css wrapper containing it and nearby text
<img width="514" alt="copied nitro-web code no color prop but ability
does not supersede wrapper"
src="https://github.com/user-attachments/assets/c87ceeb9-529e-47f4-a65a-6a60c1b61d85">
Icon takes color from color prop
<img width="536" alt="copied nitro-web code color prop supersedes
wrapper"
src="https://github.com/user-attachments/assets/ed5efb11-b5ea-4fa1-9101-328ed3a9d559">

**How to test?** Steps to confirm the desired behavior:
1. To see the Icon Color doc examples, go to the icon page in the
[playbook review
environment](https://pr3527.playbook.beta.gm.powerapp.cloud/kits/icon)
and scroll down to the Icon Color doc example (or [go there
directly](https://pr3527.playbook.beta.gm.powerapp.cloud/kits/icon#icon-color)).
4. See each doc example icon which uses different Playbook color tokens.
Several of the icons are from playbook-icons and some are font awesome
icons still - this works for them both.
5. To see evidence these changes do not break Nitro, go to the nitro-web
review environment example pages [Rails (or go to /projects choose any
project)](https://pr41186.nitro-web.beta.gm.powerapp.cloud/projects/3206553)
and
[React](https://pr41186.nitro-web.beta.gm.powerapp.cloud/user_profile/user_profile).
6. See colored icons on these pages (door-open icon on Door Builder
button on Projects page for Rails; envelopes, tshirt, snowflake, boot,
syringe, person-dress, utensils icons on User Profile page for React).


#### Checklist:
- [x] **LABELS** Add a label: `enhancement`, `bug`, `improvement`, `new
kit`, `deprecated`, or `breaking`. See [Changelog &
Labels](https://github.com/powerhome/playbook/wiki/Changelog-&-Labels)
for details.
- [x] **DEPLOY** I have added the `milano` label to show I'm ready for a
review.
- [x] **TESTS** I have added test coverage to my code.
  • Loading branch information
ElisaShapiro authored Jul 25, 2024
1 parent b458deb commit e83c193
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 10 deletions.
17 changes: 17 additions & 0 deletions playbook/app/pb_kits/playbook/pb_icon/_icon.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
@import "../tokens/colors";

// All the merges below create $icon_colors, a map of all color tokens in colors.scss
$merge_kits1: map-merge($status_colors, $category_colors);
$merge_kits2: map-merge($merge_kits1, $product_colors);
$merge_kits3: map-merge($merge_kits2, $text_colors);
$icon_colors: map-merge($merge_kits3, $data_colors);

.pb_custom_icon, .pb_icon_kit {
@each $color_name, $color_value in $icon_colors {
&[class*="#{$color_name}"] {
color: $color_value;
}
}
}

// Rails custom icon styles
svg.pb_custom_icon {
width: 1em;
fill: currentColor;
Expand Down
3 changes: 3 additions & 0 deletions playbook/app/pb_kits/playbook/pb_icon/_icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type IconProps = {
aria?: {[key: string]: string},
border?: string,
className?: string,
color?: string,
customIcon?: {[key: string] :SVGElement},
data?: {[key: string]: string},
fixedWidth?: boolean,
Expand Down Expand Up @@ -121,6 +122,7 @@ const Icon = (props: IconProps) => {
aria = {},
border = false,
className,
color,
customIcon,
data = {},
fixedWidth = true,
Expand Down Expand Up @@ -169,6 +171,7 @@ const Icon = (props: IconProps) => {
(!iconElement && !customIcon) ? 'pb_icon_kit' : '',
(iconElement || customIcon) ? 'pb_custom_icon' : fontStyle,
iconElement ? 'svg-inline--fa' : '',
color ? `color_${color}` : '',
globalProps(props),
className
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= pb_rails("flex", props: {orientation: "column"}) do %>
<%= pb_rails("icon", props: { icon: "user", fixed_width: true, color: "primary", padding_bottom: "sm", size: "2x" }) %>
<%= pb_rails("icon", props: { icon: "recycle", fixed_width: true, color: "data_4", padding_bottom: "sm", size: "2x" }) %>
<%= pb_rails("icon", props: { icon: "roofing", fixed_width: true, color: "product_5_background", size: "2x" }) %>
<% end %>
34 changes: 34 additions & 0 deletions playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react"
import Icon from "../_icon"

const IconDefault = (props) => {
return (
<div style={{ display: "flex", flexDirection: "column"}}>
<Icon
color="primary"
fixedWidth
icon="user"
paddingBottom="sm"
size="2x"
{...props}
/>
<Icon
color="data_4"
fixedWidth
icon="recycle"
paddingBottom="sm"
size="2x"
{...props}
/>
<Icon
color="product_5_background"
fixedWidth
icon="product-roofing"
size="2x"
{...props}
/>
</div>
)
}

export default IconDefault
1 change: 1 addition & 0 deletions playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pass any text, status, data, product, or category Playbook <a href="https://playbook.powerapp.cloud/visual_guidelines/colors" target="_blank">color token</a> to the `color` prop to change any icon's color.
2 changes: 2 additions & 0 deletions playbook/app/pb_kits/playbook/pb_icon/docs/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ examples:
- icon_sizes: Icon Sizes
- icon_custom: Icon Custom
- icon_fa_kit: Icon with FontAwesome Kit
- icon_color: Icon Color

react:
- icon_default: Icon Default
Expand All @@ -20,6 +21,7 @@ examples:
- icon_sizes: Icon Sizes
- icon_custom: Icon Custom
- icon_fa_kit: Icon with FontAwesome Kit
- icon_color: Icon Color

swift:
- icon_default_swift: Icon Default
Expand Down
1 change: 1 addition & 0 deletions playbook/app/pb_kits/playbook/pb_icon/docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as IconBorder } from './_icon_border.jsx'
export { default as IconSizes } from './_icon_sizes.jsx'
export { default as IconCustom } from './_icon_custom.jsx'
export { default as IconFaKit} from './_icon_fa_kit.jsx'
export { default as IconColor } from './_icon_color.jsx'
10 changes: 9 additions & 1 deletion playbook/app/pb_kits/playbook/pb_icon/icon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Icon < Playbook::KitBase
default: "far"
prop :spin, type: Playbook::Props::Boolean,
default: false
prop :color, type: Playbook::Props::String

def valid_emoji?
emoji_regex = /\p{Emoji}/
Expand All @@ -45,6 +46,7 @@ def valid_emoji?
def classname
generate_classname(
"pb_icon_kit",
color_class,
font_style_class,
icon_class,
border_class,
Expand All @@ -65,6 +67,7 @@ def custom_icon_classname
generate_classname(
"pb_icon_kit",
border_class,
color_class,
fixed_width_class,
flip_class,
inverse_class,
Expand Down Expand Up @@ -104,7 +107,8 @@ def render_svg
svg["aria"] = object.aria
svg["height"] = "auto"
svg["width"] = "auto"
doc.at_css("path")["fill"] = "currentColor"
fill_color = object.color || "currentColor"
doc.at_css("path")["fill"] = fill_color
raw doc
end

Expand Down Expand Up @@ -200,6 +204,10 @@ def spin_class
class_name = is_svg? ? "spin" : "fa-spin"
spin ? class_name : nil
end

def color_class
color ? "color_#{color}" : nil
end
end
end
end
31 changes: 22 additions & 9 deletions playbook/app/pb_kits/playbook/pb_icon/icon.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("Icon Kit", () => {
data={{ testid: testId }}
fixedWidth
icon="user"
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -27,7 +27,7 @@ describe("Icon Kit", () => {
fixedWidth
icon="user"
rotation={rotateProp}
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -44,7 +44,7 @@ describe("Icon Kit", () => {
fixedWidth
flip="horizontal"
icon="user"
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -59,7 +59,7 @@ describe("Icon Kit", () => {
fixedWidth
icon="spinner"
spin
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -73,7 +73,7 @@ describe("Icon Kit", () => {
fixedWidth
icon="arrow-left"
pull="left"
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -87,7 +87,7 @@ describe("Icon Kit", () => {
fixedWidth
icon="arrow-left"
pull="left"
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -101,7 +101,7 @@ describe("Icon Kit", () => {
data={{ testid: testId }}
fixedWidth
icon="user"
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -128,7 +128,7 @@ describe("Icon Kit", () => {
data={{ testid: testId }}
icon="user"
size={sizeProp}
/>
/>
)

const kit = screen.getByTestId(testId)
Expand All @@ -145,11 +145,24 @@ describe("Icon Kit", () => {
fixedWidth
fontStyle="fas"
icon="user"
/>
/>
)

const kit = screen.getByTestId(testId)
expect(kit).toHaveClass("fa-user pb_icon_kit fa-fw fas")
})

test("renders with color prop", () => {
render(
<Icon
color="primary"
data={{ testid: testId }}
icon="user"
/>
)

const kit = screen.getByTestId(testId)
expect(kit).toHaveClass("color_primary")
})

})
16 changes: 16 additions & 0 deletions playbook/spec/pb_kits/playbook/kits/icon_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
is_expected.to define_prop(:spin)
.of_type(Playbook::Props::Boolean)
}
it {
is_expected.to define_prop(:color)
.of_type(Playbook::Props::String)
.with_default(nil)
}

describe "#custom_icon" do
it "returns an icon with custom data-collapsible-main attribute", :aggregate_failures do
Expand Down Expand Up @@ -83,5 +88,16 @@
expect(subject.new(icon: icon, spin: true).classname).to eq "pb_icon_kit far fa-user fa-spin"
expect(subject.new(icon: icon, classname: "additional_class").classname).to eq "pb_icon_kit far fa-user additional_class"
end
it "includes color class when color prop is provided", :aggregate_failures do
icon = "user"
color = "primary"

expect(subject.new(icon: icon, color: color).classname).to include "color_primary"
end
it "does not include color class when color prop is not provided", :aggregate_failures do
icon = "user"

expect(subject.new(icon: icon).classname).not_to include "color_"
end
end
end

0 comments on commit e83c193

Please sign in to comment.