From 3ee381afee8112e8086113c0ff8563dd36c46f1d Mon Sep 17 00:00:00 2001 From: Jasper Furniss Date: Fri, 8 Nov 2024 11:57:45 -0500 Subject: [PATCH] [PLAY-1627] Fix Height Prop for HtmlOptions Support (#3873) **What does this PR do?** A clear and concise description with your runway ticket url. https://runway.powerhrg.com/backlog_items/PLAY-1627 **Screenshots:** Screenshots to visualize your addition/change The htmlOptions now works, even with the addition of our new height prop. **How to test?** Steps to confirm the desired behavior: 1. Go to a doc example for Card of Flex kit, and add a new height prop. 2. Test one that also has our htmlOptions prop. #### 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. ~- [ ] **TESTS** I have added test coverage to my code.~ --- .../app/pb_kits/playbook/pb_card/_card.tsx | 15 ++++-- .../pb_kits/playbook/pb_dialog/_dialog.tsx | 6 ++- .../app/pb_kits/playbook/pb_flex/_flex.tsx | 4 +- .../pb_kits/playbook/pb_flex/_flex_item.tsx | 10 +++- .../playbook/pb_flex/flex_item.html.erb | 9 ++-- .../app/pb_kits/playbook/pb_flex/flex_item.rb | 9 +++- .../pb_kits/playbook/pb_popover/_popover.tsx | 2 +- .../playbook/utilities/globalPropNames.mjs | 3 ++ .../pb_kits/playbook/utilities/globalProps.ts | 41 +++++++++++++++- playbook/lib/playbook/kit_base.rb | 48 +++++++++++++++++-- 10 files changed, 123 insertions(+), 24 deletions(-) diff --git a/playbook/app/pb_kits/playbook/pb_card/_card.tsx b/playbook/app/pb_kits/playbook/pb_card/_card.tsx index 149130d8b2..225f40def0 100755 --- a/playbook/app/pb_kits/playbook/pb_card/_card.tsx +++ b/playbook/app/pb_kits/playbook/pb_card/_card.tsx @@ -5,7 +5,7 @@ import { get } from 'lodash' import classnames from 'classnames' import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props' -import { GlobalProps, globalProps } from '../utilities/globalProps' +import { GlobalProps, globalProps, globalInlineProps } from '../utilities/globalProps' import type { ProductColors, CategoryColors, BackgroundColors } from '../types/colors' import Icon from '../pb_icon/_icon' @@ -49,6 +49,7 @@ type CardBodyProps = { padding?: string, } & GlobalProps + // Header component const Header = (props: CardHeaderProps) => { const { children, className, headerColor = 'category_1', headerColorStriped = false } = props @@ -107,6 +108,10 @@ const Card = (props: CardPropTypes): React.ReactElement => { // coerce to array const cardChildren = React.Children.toArray(children) + const dynamicInlineProps = globalInlineProps(props); + const { style: htmlStyle = {}, ...restHtmlProps } = htmlProps as { style?: React.CSSProperties }; + const mergedStyles: React.CSSProperties = { ...htmlStyle, ...dynamicInlineProps }; + const subComponentTags = (tagName: string) => { return cardChildren.filter((c: string) => ( @@ -122,7 +127,7 @@ const Card = (props: CardPropTypes): React.ReactElement => { const tagOptions = ['div', 'section', 'footer', 'header', 'article', 'aside', 'main', 'nav'] const Tag = tagOptions.includes(tag) ? tag : 'div' - + return ( <> { @@ -133,8 +138,9 @@ const Card = (props: CardPropTypes): React.ReactElement => { {subComponentTags('Header')} { @@ -161,8 +167,9 @@ const Card = (props: CardPropTypes): React.ReactElement => { {subComponentTags('Header')} {nonHeaderChildren} diff --git a/playbook/app/pb_kits/playbook/pb_dialog/_dialog.tsx b/playbook/app/pb_kits/playbook/pb_dialog/_dialog.tsx index e25eb702ae..32e5443019 100644 --- a/playbook/app/pb_kits/playbook/pb_dialog/_dialog.tsx +++ b/playbook/app/pb_kits/playbook/pb_dialog/_dialog.tsx @@ -6,7 +6,7 @@ import classnames from "classnames"; import Modal from "react-modal"; import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props"; -import { globalProps } from "../utilities/globalProps"; +import { globalProps, globalInlineProps } from "../utilities/globalProps"; import Body from "../pb_body/_body"; import Button from "../pb_button/_button"; @@ -91,6 +91,8 @@ const Dialog = (props: DialogProps): React.ReactElement => { beforeClose: "pb_dialog_overlay_before_close", }; + const dynamicInlineProps = globalInlineProps(props); + const classes = classnames( buildCss("pb_dialog_wrapper"), globalProps(props), @@ -184,6 +186,7 @@ const Dialog = (props: DialogProps): React.ReactElement => { overlayClassName={overlayClassNames} portalClassName={portalClassName} shouldCloseOnOverlayClick={shouldCloseOnOverlayClick && !loading} + style={{ content: dynamicInlineProps }} > <> {title && !status ? {title} : null} @@ -192,6 +195,7 @@ const Dialog = (props: DialogProps): React.ReactElement => { { const alignSelfClass = alignSelf !== 'none' ? `align_self_${alignSelf}` : '' const dataProps = buildDataProps(data) const htmlProps = buildHtmlProps(htmlOptions) + const dynamicInlineProps = globalInlineProps(props) return ( @@ -83,6 +84,7 @@ const Flex = (props: FlexProps): React.ReactElement => { globalProps(props), className )} + style={dynamicInlineProps} {...dataProps} {...htmlProps} > diff --git a/playbook/app/pb_kits/playbook/pb_flex/_flex_item.tsx b/playbook/app/pb_kits/playbook/pb_flex/_flex_item.tsx index 6c61d55cb0..b8a27e9704 100644 --- a/playbook/app/pb_kits/playbook/pb_flex/_flex_item.tsx +++ b/playbook/app/pb_kits/playbook/pb_flex/_flex_item.tsx @@ -1,7 +1,7 @@ import React from 'react' import classnames from 'classnames' import { buildCss, buildHtmlProps } from '../utilities/props' -import { globalProps, GlobalProps } from '../utilities/globalProps' +import { globalProps, GlobalProps, globalInlineProps} from '../utilities/globalProps' type FlexItemPropTypes = { children: React.ReactNode[] | React.ReactNode, fixedSize?: string, @@ -35,14 +35,20 @@ const FlexItem = (props: FlexItemPropTypes): React.ReactElement => { const fixedStyle = fixedSize !== undefined ? { flexBasis: `${fixedSize}` } : null const orderClass = order !== 'none' ? `order_${order}` : null + const dynamicInlineProps = globalInlineProps(props) + const combinedStyles = { + ...fixedStyle, + ...dynamicInlineProps + } const htmlProps = buildHtmlProps(htmlOptions) + return (
{children}
diff --git a/playbook/app/pb_kits/playbook/pb_flex/flex_item.html.erb b/playbook/app/pb_kits/playbook/pb_flex/flex_item.html.erb index 240e1ce109..96c6c7af2f 100644 --- a/playbook/app/pb_kits/playbook/pb_flex/flex_item.html.erb +++ b/playbook/app/pb_kits/playbook/pb_flex/flex_item.html.erb @@ -1,8 +1,5 @@ -<%= content_tag(:div, - id: object.id, - data: object.data, - class: object.classname, - style: object.style_value, - **combined_html_options) do %> +<%= pb_content_tag(:div, + style: object.inline_styles +) do %> <%= content.presence %> <% end %> diff --git a/playbook/app/pb_kits/playbook/pb_flex/flex_item.rb b/playbook/app/pb_kits/playbook/pb_flex/flex_item.rb index dc99471511..caa11d6c9f 100644 --- a/playbook/app/pb_kits/playbook/pb_flex/flex_item.rb +++ b/playbook/app/pb_kits/playbook/pb_flex/flex_item.rb @@ -20,8 +20,13 @@ def classname generate_classname("pb_flex_item_kit", fixed_size_class, grow_class, shrink_class, display_flex_class) + align_self_class end - def style_value - "flex-basis: #{fixed_size};" if fixed_size.present? + def inline_styles + styles = [] + styles << "flex-basis: #{fixed_size};" if fixed_size.present? + styles << "height: #{height};" if height.present? + styles << "min-height: #{min_height};" if min_height.present? + styles << "max-height: #{max_height};" if max_height.present? + styles.join(" ") end private diff --git a/playbook/app/pb_kits/playbook/pb_popover/_popover.tsx b/playbook/app/pb_kits/playbook/pb_popover/_popover.tsx index d67921b5c7..4cf9eec90a 100644 --- a/playbook/app/pb_kits/playbook/pb_popover/_popover.tsx +++ b/playbook/app/pb_kits/playbook/pb_popover/_popover.tsx @@ -21,7 +21,7 @@ import classnames from "classnames"; import { globalProps, GlobalProps } from "../utilities/globalProps"; import { uniqueId } from 'lodash'; -type ModifiedGlobalProps = Omit +type ModifiedGlobalProps = Omit type PbPopoverProps = { aria?: { [key: string]: string }; diff --git a/playbook/app/pb_kits/playbook/utilities/globalPropNames.mjs b/playbook/app/pb_kits/playbook/utilities/globalPropNames.mjs index 217f976a8d..639cc31619 100644 --- a/playbook/app/pb_kits/playbook/utilities/globalPropNames.mjs +++ b/playbook/app/pb_kits/playbook/utilities/globalPropNames.mjs @@ -1,4 +1,7 @@ export default [ + "minHeight", + "maxHeight", + "height", "left", "bottom", "right", diff --git a/playbook/app/pb_kits/playbook/utilities/globalProps.ts b/playbook/app/pb_kits/playbook/utilities/globalProps.ts index 3f6e2f2f88..7462481913 100644 --- a/playbook/app/pb_kits/playbook/utilities/globalProps.ts +++ b/playbook/app/pb_kits/playbook/utilities/globalProps.ts @@ -174,12 +174,24 @@ type ZIndex = { zIndex?: ZIndexType, } | ZIndexResponsiveType +type Height = { + height?: string +} + +type MaxHeight = { + maxHeight?: string +} + +type MinHeight = { + minHeight?: string +} + // keep this as the last type definition export type GlobalProps = AlignContent & AlignItems & AlignSelf & BorderRadius & Cursor & Dark & Display & DisplaySizes & Flex & FlexDirection & FlexGrow & FlexShrink & FlexWrap & JustifyContent & JustifySelf & LineHeight & Margin & MinWidth & MaxWidth & NumberSpacing & Order & Overflow & Padding & - Position & Shadow & TextAlign & Truncate & VerticalAlign & ZIndex & GroupHover & { hover?: string } & Top & Right & Bottom & Left; + Position & Shadow & TextAlign & Truncate & VerticalAlign & ZIndex & { hover?: string } & Top & Right & Bottom & Left & Height & MaxHeight & MinHeight; const getResponsivePropClasses = (prop: {[key: string]: string}, classPrefix: string) => { const keys: string[] = Object.keys(prop) @@ -503,7 +515,22 @@ const PROP_CATEGORIES: {[key:string]: (props: {[key: string]: any}) => string} = } else { return verticalAlign ? `vertical_align_${verticalAlign} ` : '' } - } + }, + +} + +const PROP_INLINE_CATEGORIES: {[key:string]: (props: {[key: string]: any}) => {[key: string]: any}} = { + heightProps: ({ height }: Height) => { + return height ? { height } : {}; + }, + + maxHeightProps: ({ maxHeight }: MaxHeight) => { + return maxHeight ? { maxHeight } : {}; + }, + + minHeightProps: ({ minHeight }: MinHeight) => { + return minHeight ? { minHeight } : {}; + }, } type DefaultProps = {[key: string]: string} | Record @@ -515,6 +542,16 @@ export const globalProps = (props: GlobalProps, defaultProps: DefaultProps = {}) }).filter((value) => value?.length > 0).join(" ") } +// New function for inline styles +export const globalInlineProps = (props: GlobalProps): React.CSSProperties => { + const styles = Object.keys(PROP_INLINE_CATEGORIES).reduce((acc, key) => { + const result = PROP_INLINE_CATEGORIES[key](props); + return { ...acc, ...(typeof result === 'object' ? result : {}) }; // Ensure result is an object before spreading + }, {}); + + return styles; // Return the styles object directly +} + export const deprecatedProps = (): void => { // if (process.env.NODE_ENV === 'development') { diff --git a/playbook/lib/playbook/kit_base.rb b/playbook/lib/playbook/kit_base.rb index 470a296929..65583833b1 100644 --- a/playbook/lib/playbook/kit_base.rb +++ b/playbook/lib/playbook/kit_base.rb @@ -73,15 +73,15 @@ class KitBase < ViewComponent::Base prop :aria, type: Playbook::Props::HashProp, default: {} prop :html_options, type: Playbook::Props::HashProp, default: {} prop :children, type: Playbook::Props::Proc + prop :style, type: Playbook::Props::HashProp, default: {} + prop :height + prop :min_height + prop :max_height def object self end - def combined_html_options - default_html_options.merge(html_options.deep_merge(data_attributes)) - end - # rubocop:disable Layout/CommentIndentation # pb_content_tag information (potentially to be abstracted into its own dev doc in the future) # The pb_content_tag generates HTML content tags for rails kits with flexible options. @@ -110,15 +110,48 @@ def pb_content_tag(name = :div, content_or_options_with_block = {}, options = {} end # rubocop:enable Style/OptionalBooleanParameter + def combined_html_options + merged = default_html_options.dup + + html_options.each do |key, value| + if key == :style && value.is_a?(Hash) + # Convert style hash to CSS string + merged[:style] = value.map { |k, v| "#{k.to_s.gsub('_', '-')}: #{v}" }.join("; ") + else + merged[key] = value + end + end + + inline_styles = dynamic_inline_props + merged[:style] = if inline_styles.present? + merged[:style].present? ? "#{merged[:style]}; #{inline_styles}" : inline_styles + end + + merged.deep_merge(data_attributes) + end + + def global_inline_props + { + height: height, + min_height: min_height, + max_height: max_height, + }.compact + end + private def default_options - { + options = { id: id, data: data, class: classname, aria: aria, } + + inline_styles = dynamic_inline_props + options[:style] = inline_styles if inline_styles.present? && !html_options.key?(:style) + + options end def default_html_options @@ -131,5 +164,10 @@ def data_attributes aria: aria, }.transform_keys { |key| key.to_s.tr("_", "-").to_sym } end + + def dynamic_inline_props + styles = global_inline_props.map { |key, value| "#{key.to_s.gsub('_', '-')}: #{value}" if value.present? }.compact + styles.join("; ").presence + end end end