diff --git a/playbook/app/pb_kits/playbook/pb_icon/_icon.scss b/playbook/app/pb_kits/playbook/pb_icon/_icon.scss index 59aa945237..953e8b5ba2 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/_icon.scss +++ b/playbook/app/pb_kits/playbook/pb_icon/_icon.scss @@ -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; diff --git a/playbook/app/pb_kits/playbook/pb_icon/_icon.tsx b/playbook/app/pb_kits/playbook/pb_icon/_icon.tsx index c7ccbb9d43..42085e59d6 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/_icon.tsx +++ b/playbook/app/pb_kits/playbook/pb_icon/_icon.tsx @@ -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, @@ -121,6 +122,7 @@ const Icon = (props: IconProps) => { aria = {}, border = false, className, + color, customIcon, data = {}, fixedWidth = true, @@ -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 ) diff --git a/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.html.erb b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.html.erb new file mode 100644 index 0000000000..3e3b2e5520 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.html.erb @@ -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 %> \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.jsx b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.jsx new file mode 100644 index 0000000000..d3662eb89c --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.jsx @@ -0,0 +1,34 @@ +import React from "react" +import Icon from "../_icon" + +const IconDefault = (props) => { + return ( +
+ + + +
+ ) +} + +export default IconDefault diff --git a/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.md b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.md new file mode 100644 index 0000000000..98aeb62f78 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_icon/docs/_icon_color.md @@ -0,0 +1 @@ +Pass any text, status, data, product, or category Playbook color token to the `color` prop to change any icon's color. \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_icon/docs/example.yml b/playbook/app/pb_kits/playbook/pb_icon/docs/example.yml index a2cef377dd..9d0f648d79 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_icon/docs/example.yml @@ -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 @@ -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 diff --git a/playbook/app/pb_kits/playbook/pb_icon/docs/index.js b/playbook/app/pb_kits/playbook/pb_icon/docs/index.js index 72808f09ac..8aa0aefb6a 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/docs/index.js +++ b/playbook/app/pb_kits/playbook/pb_icon/docs/index.js @@ -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' diff --git a/playbook/app/pb_kits/playbook/pb_icon/icon.rb b/playbook/app/pb_kits/playbook/pb_icon/icon.rb index eb9ba3c05a..ebd1322e9e 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/icon.rb +++ b/playbook/app/pb_kits/playbook/pb_icon/icon.rb @@ -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}/ @@ -45,6 +46,7 @@ def valid_emoji? def classname generate_classname( "pb_icon_kit", + color_class, font_style_class, icon_class, border_class, @@ -65,6 +67,7 @@ def custom_icon_classname generate_classname( "pb_icon_kit", border_class, + color_class, fixed_width_class, flip_class, inverse_class, @@ -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 @@ -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 diff --git a/playbook/app/pb_kits/playbook/pb_icon/icon.test.js b/playbook/app/pb_kits/playbook/pb_icon/icon.test.js index 2e61187286..9f872e74a8 100644 --- a/playbook/app/pb_kits/playbook/pb_icon/icon.test.js +++ b/playbook/app/pb_kits/playbook/pb_icon/icon.test.js @@ -12,7 +12,7 @@ describe("Icon Kit", () => { data={{ testid: testId }} fixedWidth icon="user" - /> + /> ) const kit = screen.getByTestId(testId) @@ -27,7 +27,7 @@ describe("Icon Kit", () => { fixedWidth icon="user" rotation={rotateProp} - /> + /> ) const kit = screen.getByTestId(testId) @@ -44,7 +44,7 @@ describe("Icon Kit", () => { fixedWidth flip="horizontal" icon="user" - /> + /> ) const kit = screen.getByTestId(testId) @@ -59,7 +59,7 @@ describe("Icon Kit", () => { fixedWidth icon="spinner" spin - /> + /> ) const kit = screen.getByTestId(testId) @@ -73,7 +73,7 @@ describe("Icon Kit", () => { fixedWidth icon="arrow-left" pull="left" - /> + /> ) const kit = screen.getByTestId(testId) @@ -87,7 +87,7 @@ describe("Icon Kit", () => { fixedWidth icon="arrow-left" pull="left" - /> + /> ) const kit = screen.getByTestId(testId) @@ -101,7 +101,7 @@ describe("Icon Kit", () => { data={{ testid: testId }} fixedWidth icon="user" - /> + /> ) const kit = screen.getByTestId(testId) @@ -128,7 +128,7 @@ describe("Icon Kit", () => { data={{ testid: testId }} icon="user" size={sizeProp} - /> + /> ) const kit = screen.getByTestId(testId) @@ -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( + + ) + + const kit = screen.getByTestId(testId) + expect(kit).toHaveClass("color_primary") + }) + }) \ No newline at end of file diff --git a/playbook/spec/pb_kits/playbook/kits/icon_spec.rb b/playbook/spec/pb_kits/playbook/kits/icon_spec.rb index ff9a2a1df8..a8ca6e5785 100644 --- a/playbook/spec/pb_kits/playbook/kits/icon_spec.rb +++ b/playbook/spec/pb_kits/playbook/kits/icon_spec.rb @@ -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 @@ -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