From 693a86cf01b702e32bc509d6dc72c5d3a266927f Mon Sep 17 00:00:00 2001 From: Sam Duncan Date: Mon, 21 Oct 2024 10:29:08 -0500 Subject: [PATCH 1/9] [PLAY-1510] Loading state for Rails form, resolution of Dialog loading (#3753) **What does this PR do?** A clear and concise description with your runway ticket url. https://runway.powerhrg.com/backlog_items/PLAY-1510 **Screenshots:** Screenshots to visualize your addition/change **How to test?** Steps to confirm the desired behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See addition/change #### Checklist: - [ ] **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. - [ ] **DEPLOY** I have added the `milano` label to show I'm ready for a review. - [ ] **TESTS** I have added test coverage to my code. --- playbook/app/entrypoints/playbook-rails.js | 2 + playbook/app/javascript/plugins.js | 2 + .../pb_dialog/docs/_dialog_loading.html.erb | 37 ++++++++++++++---- .../pb_dialog/docs/_dialog_loading.md | 2 - .../docs/_form_form_with_loading.html.erb | 39 +++++++++++++++++++ .../pb_form/docs/_form_form_with_loading.md | 1 + .../pb_kits/playbook/pb_form/docs/example.yml | 1 + playbook/app/pb_kits/playbook/pb_form/form.rb | 2 + .../pb_kits/playbook/pb_form/formHelper.js | 27 +++++++++++++ playbook/lib/playbook/pb_forms_helper.rb | 4 +- 10 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb create mode 100644 playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.md create mode 100644 playbook/app/pb_kits/playbook/pb_form/formHelper.js diff --git a/playbook/app/entrypoints/playbook-rails.js b/playbook/app/entrypoints/playbook-rails.js index 6c11e2a8a8..7769cb5fb9 100644 --- a/playbook/app/entrypoints/playbook-rails.js +++ b/playbook/app/entrypoints/playbook-rails.js @@ -1,5 +1,7 @@ // Forms import 'kits/pb_form/pb_form_validation' +import formHelper from 'kits/pb_form/formHelper' +window.formHelper = formHelper // Date Picker import datePickerHelper from 'kits/pb_date_picker/date_picker_helper' diff --git a/playbook/app/javascript/plugins.js b/playbook/app/javascript/plugins.js index d27f3cec4a..0ecdf5e1e6 100644 --- a/playbook/app/javascript/plugins.js +++ b/playbook/app/javascript/plugins.js @@ -6,3 +6,5 @@ export { default as PbTextarea } from '../pb_kits/playbook/pb_textarea' export { default as PbTooltip } from '../pb_kits/playbook/pb_tooltip' export { default as PbTypeahead } from '../pb_kits/playbook/pb_typeahead' export { default as dialogHelper } from '../pb_kits/playbook/pb_dialog/dialogHelper' +export { default as formHelper } from '../pb_kits/playbook/pb_form/formHelper' + diff --git a/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.html.erb b/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.html.erb index 5d8254f492..764f0d9559 100644 --- a/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.html.erb +++ b/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.html.erb @@ -1,13 +1,36 @@ <%= pb_rails("button", props: { text: "Open Dialog", data: {"open-dialog": "dialog-loading"} }) %> -<%= pb_rails("dialog", props: { - id:"dialog-loading", - size: "sm", - title: "Loading Exmaple", - text: "Make a loading request?", - cancel_button: "Cancel Button", +<%= pb_rails("dialog", props: { + id:"dialog-loading", + size: "sm", + title: "Loading Example", + text: "Make a loading request?", + cancel_button: "Cancel Button", cancel_button_id: "cancel-button-loading", - confirm_button: "Okay", + confirm_button: "Okay", confirm_button_id: "confirm-button-loading", loading: true, }) %> + + diff --git a/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.md b/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.md index ad7fbfab33..de0f291d1a 100644 --- a/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.md +++ b/playbook/app/pb_kits/playbook/pb_dialog/docs/_dialog_loading.md @@ -1,3 +1 @@ Pressing the "Okay" button will trigger a loading state where the button content is replaced by a spinner icon and both buttons are disabled. - -Currently, the loading state cannot be undone and will require a page refresh to reset the dialog. diff --git a/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb new file mode 100644 index 0000000000..e0702b1303 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb @@ -0,0 +1,39 @@ +<%= pb_form_with(scope: :example, url: "", method: :get, loading: true) do |form| %> + <%= form.text_field :example_text_field, props: { label: true } %> + + <%= form.actions do |action| %> + <%= action.submit %> + <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %> + <% end %> +<% end %> + + + diff --git a/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.md b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.md new file mode 100644 index 0000000000..eef1260771 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.md @@ -0,0 +1 @@ +Pressing Submit will trigger a loading state where the button content is replaced by a spinner icon and the submit button will be disabled. diff --git a/playbook/app/pb_kits/playbook/pb_form/docs/example.yml b/playbook/app/pb_kits/playbook/pb_form/docs/example.yml index b04c3fdc51..19f029d494 100644 --- a/playbook/app/pb_kits/playbook/pb_form/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_form/docs/example.yml @@ -3,3 +3,4 @@ examples: rails: - form_form_with: Default - form_form_with_validate: Default + Validation + - form_form_with_loading: Default + Loading diff --git a/playbook/app/pb_kits/playbook/pb_form/form.rb b/playbook/app/pb_kits/playbook/pb_form/form.rb index dc7b2574dd..d931173922 100644 --- a/playbook/app/pb_kits/playbook/pb_form/form.rb +++ b/playbook/app/pb_kits/playbook/pb_form/form.rb @@ -7,6 +7,7 @@ class Form < ::Playbook::KitBase type: Playbook::Props::Base prop :form_system_options, deprecated: "Use options instead", type: Playbook::Props::Base + prop :loading, type: Playbook::Props::Boolean, default: false prop :options, type: Playbook::Props::Base prop :validate, type: Playbook::Props::Boolean, default: false @@ -22,6 +23,7 @@ def form_options aria: aria, class: classname, data: data, + loading: loading, validate: validate, }.merge(prop(:options) || prop(:form_system_options) || {}) end diff --git a/playbook/app/pb_kits/playbook/pb_form/formHelper.js b/playbook/app/pb_kits/playbook/pb_form/formHelper.js new file mode 100644 index 0000000000..1c0fdb72d1 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_form/formHelper.js @@ -0,0 +1,27 @@ +const formHelper = () => { + const loadingForm = document.querySelector(".pb_form_loading") + if (loadingForm) { + loadingForm.addEventListener("submit", function(event) { + const submitButton = event['submitter']; + const cancelButton = event['target'].querySelector('button[type="reset"]'); + + if (submitButton) { + let currentClass = submitButton.className; + let newClass = currentClass.replace("_enabled", "_disabled_loading"); + + let cancelClass = cancelButton ? cancelButton.className : ""; + let newCancelClass = cancelClass.replace("_enabled", "_disabled"); + + submitButton.disabled = true; + submitButton.className = newClass; + + if (cancelButton) { + cancelButton.disabled = true; + cancelButton.className = newCancelClass; + } + } + }); + } +}; + +export default formHelper; diff --git a/playbook/lib/playbook/pb_forms_helper.rb b/playbook/lib/playbook/pb_forms_helper.rb index c2b4c11d7a..f515b5db13 100755 --- a/playbook/lib/playbook/pb_forms_helper.rb +++ b/playbook/lib/playbook/pb_forms_helper.rb @@ -22,9 +22,10 @@ module PbFormsHelper # @param data [Hash] hash of data attributes # @param validate [Boolean] whether validation should be triggered or not # @see [#form_with] for other options - def pb_form_with(data: {}, validate: false, **kwargs, &block) + def pb_form_with(data: {}, validate: false, loading: false, **kwargs, &block) data = data.merge("pb-form-validation" => validate) classname = ["pb-form", kwargs[:class]].join(" ") + classname += " pb_form_loading" if loading options = kwargs.merge( class: classname, data: data, @@ -33,6 +34,7 @@ def pb_form_with(data: {}, validate: false, **kwargs, &block) content_for(:pb_js, javascript_tag(<<~JS)) window.addEventListener("DOMContentLoaded", function() { PbFormValidation.start() }) + window.addEventListener("DOMContentLoaded", () => formHelper()) JS form_with(**options, &block) From f4c5e2fd7f91cf286220ea89f16ae0c7b6b87f3b Mon Sep 17 00:00:00 2001 From: Nick Amantia <92755007+nickamantia@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:35:56 -0400 Subject: [PATCH 2/9] [PBNTR-389](Dropdown work - States & Variant Prop [ 1 of 5]) (#3828) [PBNTR-389](https://runway.powerhrg.com/backlog_items/PBNTR-389) This PR adds new state styles and a variant prop with a subtle doc example! Screenshot 2024-09-30 at 2 04 45 PM **How to test?** Steps to confirm the desired behavior: 1. Go to the dropdown kit and confirm styles! #### 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. --- .../playbook/pb_dropdown/_dropdown.scss | 87 ++++++++++++++++++- .../playbook/pb_dropdown/_dropdown.tsx | 9 +- .../docs/_dropdown_separators_hidden.html.erb | 9 ++ .../docs/_dropdown_separators_hidden.jsx | 33 +++++++ .../docs/_dropdown_subtle_variant.html.erb | 10 +++ .../docs/_dropdown_subtle_variant.jsx | 34 ++++++++ .../docs/_dropdown_subtle_variant.md | 1 + .../playbook/pb_dropdown/docs/example.yml | 4 + .../playbook/pb_dropdown/docs/index.js | 2 + .../pb_kits/playbook/pb_dropdown/dropdown.rb | 11 ++- 10 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.html.erb create mode 100644 playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.jsx create mode 100644 playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.html.erb create mode 100644 playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.jsx create mode 100644 playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.md diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.scss b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.scss index a64e52f2aa..05b26b4b07 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +++ b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.scss @@ -69,15 +69,21 @@ padding-bottom: $space_xs; cursor: pointer; &:hover { - background-color: $border_light; + background-color: $bg_light; } &[class*="_focused"] { - background-color: $border_light; + background-color: $bg_light; } &[class*="_list"] { border-bottom: 1px solid $border_light; + + &:hover, &:focus { + .pb_body_kit { + color: $primary; + } + } } &[class*="selected"] { background-color: $primary; @@ -87,7 +93,7 @@ color: $white !important; } &:hover { - background-color: $primary !important; + background-color: $product_1_background !important; } } } @@ -125,6 +131,81 @@ } } + &.separators_hidden { + .dropdown_wrapper { + .pb_dropdown_container { + + [class*="pb_dropdown_option"] { + border: none; + } + } + } + } + + &.subtle { + .dropdown_wrapper { + .pb_dropdown_container { + + [class*="pb_dropdown_option"]:first-child { + margin-top: $space_xs; + } + + [class*="pb_dropdown_option"]:last-child { + margin-bottom: $space_xs; + } + + [class*="pb_dropdown_option"] { + margin: $space_xs; + padding: $space_xs; + border-radius: $border_radius_md; + border-bottom: none; + position: relative; + + &::after { + content: ""; + position: absolute; + left: -$space_xs; + right: -$space_xs; + bottom: -4.5px; + height: 1px; + background: $border_light; + } + } + + [class*="pb_dropdown_option"]:last-child::after { + display: none; + } + } + } + + &.separators_hidden { + .dropdown_wrapper { + .pb_dropdown_container { + [class*="pb_dropdown_option"]:first-child { + margin-top: $space_xs; + } + + [class*="pb_dropdown_option"]:last-child { + margin-bottom: $space_xs; + } + + [class*="pb_dropdown_option"] { + padding: $space_xxs $space_xs; + margin: $space_xxs $space_xs; + + &::after { + height: 0px; + } + + &[class*="selected"] { + border-bottom: none; + } + } + } + } + } + } + &.dark { .dropdown_wrapper { [class*="dropdown_trigger_wrapper"] { diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx index 27234c3fec..28af6eb76a 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +++ b/playbook/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx @@ -35,7 +35,9 @@ type DropdownProps = { label?: string; onSelect?: (arg: GenericObject) => null; options: GenericObject; + separators: boolean; triggerRef?: any; + variant?: "default" | "subtle"; }; interface DropdownComponent @@ -62,15 +64,20 @@ const Dropdown = forwardRef((props: DropdownProps, ref: any) => { label, onSelect, options, - triggerRef + separators = true, + triggerRef, + variant = "default", } = props; const ariaProps = buildAriaProps(aria); const dataProps = buildDataProps(data); const htmlProps = buildHtmlProps(htmlOptions); + const separatorsClass = separators ? '' : 'separators_hidden' const classes = classnames( buildCss("pb_dropdown"), globalProps(props), + variant, + separatorsClass, className ); diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.html.erb b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.html.erb new file mode 100644 index 0000000000..98d2b41939 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.html.erb @@ -0,0 +1,9 @@ +<% + options = [ + { label: 'United States', value: 'United States', id: 'us' }, + { label: 'Canada', value: 'Canada', id: 'ca' }, + { label: 'Pakistan', value: 'Pakistan', id: 'pk' }, + ] + +%> +<%= pb_rails("dropdown", props: {options: options, separators: false}) %> \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.jsx b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.jsx new file mode 100644 index 0000000000..053c60e339 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_separators_hidden.jsx @@ -0,0 +1,33 @@ +import React from 'react' +import { Dropdown } from 'playbook-ui' + +const DropdownSeparatorsHidden = (props) => { + + const options = [ + { + label: "United States", + value: "United States", + }, + { + label: "Canada", + value: "Canada", + }, + { + label: "Pakistan", + value: "Pakistan", + } + ]; + + + return ( +
+ +
+ ) +} + +export default DropdownSeparatorsHidden \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.html.erb b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.html.erb new file mode 100644 index 0000000000..8e37f2390f --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.html.erb @@ -0,0 +1,10 @@ +<% + options = [ + { label: 'United States', value: 'United States', id: 'us' }, + { label: 'Canada', value: 'Canada', id: 'ca' }, + { label: 'Pakistan', value: 'Pakistan', id: 'pk' }, + ] + +%> + +<%= pb_rails("dropdown", props: { options: options, variant: "subtle", separators: false }) %> \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.jsx b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.jsx new file mode 100644 index 0000000000..e2686d36b3 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.jsx @@ -0,0 +1,34 @@ +import React from 'react' +import { Dropdown } from 'playbook-ui' + +const DropdownSubtleVariant = (props) => { + + const options = [ + { + label: "United States", + value: "United States", + }, + { + label: "Canada", + value: "Canada", + }, + { + label: "Pakistan", + value: "Pakistan", + } + ]; + + + return ( + <> + + + ) +} + +export default DropdownSubtleVariant diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.md b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.md new file mode 100644 index 0000000000..723805e823 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.md @@ -0,0 +1 @@ +For the `subtle` variant, it is recommended that you set the `Separators` prop to `false` to remove the separator lines between the options for a more cleaner look. \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml b/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml index f707a2907c..5a88bb3075 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/example.yml @@ -1,6 +1,7 @@ examples: rails: - dropdown_default: Default + - dropdown_subtle_variant: Subtle Variant - dropdown_subcomponent_structure_rails: Subcomponent Structure - dropdown_with_label: With Label - dropdown_with_custom_options_rails: Custom Options @@ -10,9 +11,11 @@ examples: - dropdown_error: Dropdown with Error - dropdown_default_value: Default Value - dropdown_blank_selection: Blank Selection + - dropdown_separators_hidden: Separators Hidden react: - dropdown_default: Default + - dropdown_subtle_variant: Subtle Variant - dropdown_subcomponent_structure: Subcomponent Structure - dropdown_with_label: With Label - dropdown_with_custom_options: Custom Options @@ -23,6 +26,7 @@ examples: - dropdown_default_value: Default Value - dropdown_blank_selection: Blank Selection - dropdown_clear_selection: Clear Selection + - dropdown_separators_hidden: Separators Hidden # - dropdown_with_autocomplete: Autocomplete # - dropdown_with_autocomplete_and_custom_display: Autocomplete with Custom Display # - dropdown_with_external_control: useDropdown Hook diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js b/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js index e246c75bf3..6753fe53cb 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js +++ b/playbook/app/pb_kits/playbook/pb_dropdown/docs/index.js @@ -13,3 +13,5 @@ export { default as DropdownError } from './_dropdown_error.jsx' export { default as DropdownDefaultValue } from './_dropdown_default_value.jsx' export { default as DropdownBlankSelection } from './_dropdown_blank_selection.jsx' export { default as DropdownClearSelection } from './_dropdown_clear_selection.jsx' +export { default as DropdownSubtleVariant } from './_dropdown_subtle_variant.jsx' +export { default as DropdownSeparatorsHidden } from './_dropdown_separators_hidden.jsx' diff --git a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb index 4f839b17f0..30e6cbd2f5 100644 --- a/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb +++ b/playbook/app/pb_kits/playbook/pb_dropdown/dropdown.rb @@ -13,13 +13,18 @@ class Dropdown < Playbook::KitBase prop :default_value prop :blank_selection, type: Playbook::Props::String, default: "" + prop :variant, type: Playbook::Props::Enum, + values: %w[default subtle], + default: "default" + prop :separators, type: Playbook::Props::Boolean, + default: true def data Hash(prop(:data)).merge(pb_dropdown: true) end def classname - generate_classname("pb_dropdown") + generate_classname("pb_dropdown", variant, separators_class, separator: " ") end private @@ -32,6 +37,10 @@ def input_default_value default_value.present? ? default_value.transform_keys(&:to_s)["id"] : "" end + def separators_class + separators ? "" : "separators_hidden" + end + def options_with_blank blank_selection.present? ? [{ id: "", value: "", label: blank_selection }] + options : options end From d7b8e5e0a4685d2be8b53d703dbbb6e74d594e66 Mon Sep 17 00:00:00 2001 From: Jasper Furniss Date: Tue, 22 Oct 2024 15:37:46 -0400 Subject: [PATCH 3/9] [PLAY-1581] Height, minHeight, maxHeight for Card, Flex, FlexItem, & Dialog (#3749) **What does this PR do?** A clear and concise description with your runway ticket url. https://runway.powerhrg.com/backlog_items/PLAY-1581 In order to achieve the height functionality that the story demanded, we introduced a new utility function for handling specific global properties that require _dynamic_ values (any px, %, or vh, etc). This has been on our Todo list for a looooong time, and I'm so happy we were able to knock out the work here in this story! The work in this PR adds the following props for react: **minHeight, maxHeight, height** for React The work in this PR adds the following props for rails: **min_height, max_height, height** for Rails For now, these are applied to a few of our utility kits: Card, Flex, FlexItem, Dialog We have a path forward to apply to all other kits, but for now this work unblocks the Template work for the PB-In-Nitro team, without shipping changes to every kit which would move this to a much higher risk-level. **Screenshots:** Screenshots to visualize your addition/change **How to test?** Steps to confirm the desired behavior: 1. Go to dialog, flex, flex_item, or card. 2. Add a height prop with a value. 3. See your styles get applied. #### 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. --- .../app/pb_kits/playbook/pb_card/_card.tsx | 6 ++- .../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 | 22 +++++++++- 10 files changed, 95 insertions(+), 17 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..4c0a4eb6f5 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,7 @@ const Card = (props: CardPropTypes): React.ReactElement => { // coerce to array const cardChildren = React.Children.toArray(children) + const dynamicInlineProps = globalInlineProps(props); const subComponentTags = (tagName: string) => { return cardChildren.filter((c: string) => ( @@ -135,6 +137,7 @@ const Card = (props: CardPropTypes): React.ReactElement => { {...dataProps} {...htmlProps} className={classnames(cardCss, globalProps(props), className)} + style={dynamicInlineProps} > {subComponentTags('Header')} { @@ -163,6 +166,7 @@ const Card = (props: CardPropTypes): React.ReactElement => { {...dataProps} {...htmlProps} className={classnames(cardCss, globalProps(props), className)} + style={dynamicInlineProps} > {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 d77febae64..a302194c67 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 d41b00be0b..a9f5db1490 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 9dd67b7223..b4b093fa5b 100644 --- a/playbook/app/pb_kits/playbook/utilities/globalProps.ts +++ b/playbook/app/pb_kits/playbook/utilities/globalProps.ts @@ -170,12 +170,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 & { 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) @@ -498,7 +510,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 @@ -510,6 +537,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..8920188997 100644 --- a/playbook/lib/playbook/kit_base.rb +++ b/playbook/lib/playbook/kit_base.rb @@ -73,6 +73,10 @@ 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 @@ -82,6 +86,14 @@ def combined_html_options default_html_options.merge(html_options.deep_merge(data_attributes)) end + def global_inline_props + { + height: height, + min_height: min_height, + max_height: max_height, + }.compact + 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. @@ -113,12 +125,15 @@ def pb_content_tag(name = :div, content_or_options_with_block = {}, options = {} 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? + options end def default_html_options @@ -131,5 +146,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 From a04ad24b3ea998885ee74cd8bb76d2f48c9a0c52 Mon Sep 17 00:00:00 2001 From: Elisa Shapiro <83474365+ElisaShapiro@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:38:40 -0400 Subject: [PATCH 4/9] [PBNTR-595] Follow up to Fixed Width Filter issue on Templates (#3827) **What does this PR do?** A clear and concise description with your runway ticket url. [PBNTR-595](https://runway.powerhrg.com/backlog_items/PBNTR-595) implements the popoverProps component on the Filter kit which will unblock PBNTR's template work by allowing the team to set an optimal Filter Popover width on our templates without using inline styling or a separate scss file. PopoverProps are connected to the embedded Popover within the Filter kit (by spreading the popoverProps prop React, and by merging popover_props to the rest of the props in Rails). A "Popover Props" doc example has been added to the Rails and React kit pages which shows using the popover prop to set a custom width on the filter popover. The Popover kit itself now accepts a width prop to allow us to achieve this - no doc example necessary at this time. This is associated with an [alpha PR here](linktocome) to demonstrate its use to prevent the Rails template popover width from jumping every time an item is added to the Filter's Typeahead. **Screenshots:** Screenshots to visualize your addition/change Rails Popover Props doc example for PR popover props doc ex rails React Popover Props doc example for PR popover props doc ex react Rails Template with `popover_props` applying a set width to avoid the filter popover jumping with each additional typeahead entry desktop+mobile for PR rails template desktop filter fixed width for PR rails template mobile filter fixed width **How to test?** Steps to confirm the desired behavior: 1. Go to the filter kit review env page, specifically to the Popover Props doc example ([rails](https://pr3827.playbook.beta.px.powerapp.cloud/kits/filter#popover-props), [react](https://pr3827.playbook.beta.px.powerapp.cloud/kits/filter/react#popover-props)) 2. Click on Filter popover button and see a smaller-than-usual Filter popover appear. Compare to other doc examples on the page (default, etc.) to see the difference. 3. Go to the [rails template page](https://pr43315.nitro-web.beta.hq.powerapp.cloud/dev_docs/playbook/templates/demos/basic_table/rails) in the review environment with alpha from this PR. 4. Open the Filter and add several items to the typeahead. The width of the popover should not jump with every additional entry. #### 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.~~ --- .../pb_filter/Filter/FilterDouble.tsx | 2 + .../pb_filter/Filter/FilterSingle.tsx | 2 + .../pb_filter/Filter/FiltersPopover.tsx | 5 +- .../docs/_filter_popover_props.html.erb | 41 +++++++++++ .../pb_filter/docs/_filter_popover_props.jsx | 71 +++++++++++++++++++ .../docs/_filter_popover_props_rails.md | 1 + .../docs/_filter_popover_props_react.md | 1 + .../playbook/pb_filter/docs/example.yml | 3 + .../pb_kits/playbook/pb_filter/docs/index.js | 1 + .../playbook/pb_filter/filter.html.erb | 4 +- .../app/pb_kits/playbook/pb_filter/filter.rb | 2 + .../pb_kits/playbook/pb_popover/_popover.tsx | 6 +- .../pb_popover/docs/_popover_default.html.erb | 2 +- .../pb_kits/playbook/pb_popover/popover.rb | 4 +- 14 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.html.erb create mode 100644 playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.jsx create mode 100644 playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props_rails.md create mode 100644 playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props_react.md diff --git a/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx b/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx index 7d15d84d12..ecb21b72ad 100644 --- a/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx +++ b/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx @@ -34,6 +34,7 @@ const FilterDouble = ({ maxHeight, minWidth, placement, + popoverProps, ...bgProps }: FilterDoubleProps): React.ReactElement => ( {children} diff --git a/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx b/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx index 4d8b285f01..950575006b 100644 --- a/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +++ b/playbook/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx @@ -33,6 +33,7 @@ const FilterSingle = ({ maxHeight, minWidth, placement, + popoverProps, ...bgProps }: FilterSingleProps): React.ReactElement => { return ( @@ -52,6 +53,7 @@ const FilterSingle = ({ maxHeight={maxHeight} minWidth={minWidth} placement={placement} + popoverProps={popoverProps} > {children} diff --git a/playbook/app/pb_kits/playbook/pb_filter/Filter/FiltersPopover.tsx b/playbook/app/pb_kits/playbook/pb_filter/Filter/FiltersPopover.tsx index 72ce03420a..362915bcc0 100644 --- a/playbook/app/pb_kits/playbook/pb_filter/Filter/FiltersPopover.tsx +++ b/playbook/app/pb_kits/playbook/pb_filter/Filter/FiltersPopover.tsx @@ -2,6 +2,7 @@ import React, { ReactNode, useState } from 'react' import CircleIconButton from '../../pb_circle_icon_button/_circle_icon_button' import PbReactPopover from '../../pb_popover/_popover' +import { GenericObject } from '../../types' type FiltersPopoverProps = { children?: React.ReactChild[] | React.ReactChild | (({closePopover}: {closePopover: () => void}) => ReactNode), @@ -9,8 +10,9 @@ type FiltersPopoverProps = { maxHeight?: string, minWidth?: string, placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end", + popoverProps?: GenericObject, } -const FiltersPopover = ({ children, dark, maxHeight, minWidth, placement = "bottom-start" }: FiltersPopoverProps): React.ReactElement => { +const FiltersPopover = ({ children, dark, maxHeight, minWidth, placement = "bottom-start", popoverProps }: FiltersPopoverProps): React.ReactElement => { const [hide, updateHide] = useState(true) const toggle = () => updateHide(!hide) @@ -33,6 +35,7 @@ const FiltersPopover = ({ children, dark, maxHeight, minWidth, placement = "bott reference={filterButton} shouldClosePopover={updateHide} show={!hide} + {...popoverProps} >
{typeof children === 'function' diff --git a/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.html.erb b/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.html.erb new file mode 100644 index 0000000000..da96315cce --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.html.erb @@ -0,0 +1,41 @@ +<%= + pb_rails("filter", props: { + id: "filter_popover_props", + position: "top", + filters: [ + { name: "name", value: "John Wick" }, + { name: "city", value: "San Francisco"} + ], + sort_menu: [ + { item: "Popularity", link: "?q[sorts]=managers_popularity+asc", active: true, direction: "desc" }, + { item: "Mananger's Title", link: "?q[sorts]=managers_title+asc", active: false }, + { item: "Manager's Name", link: "?q[sorts]=managers_name+asc", active: false }, + ], + template: "default", + results: 1, + popover_props: { width: "250px" }, + }) do +%> + <% + example_collection = [ + OpenStruct.new(name: "USA", value: 1), + OpenStruct.new(name: "Canada", value: 2), + OpenStruct.new(name: "Brazil", value: 3), + OpenStruct.new(name: "Philippines", value: 4), + OpenStruct.new(name: "A galaxy far far away, like really far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far away...", value: 5) + ] + %> + <%= pb_rails("form", props: { form_system_options: { scope: :example, method: :get } }) do |form| %> + <%= form.text_field :example_text_field, props: { label: true } %> + <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: {max_width: "sm", label: true } %> + + <%= form.actions do |action| %> + <%= action.submit props: { + text: "Apply", + data: { + disable_with: "pb_rails('icon', props: { icon: 'spinner', spin: true, fixed_width: true })Searching...".html_safe + },}%> + <%= action.button props: { type: "reset", text: "Clear", variant: "secondary" } %> + <% end %> + <% end %> +<% end %> diff --git a/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.jsx b/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.jsx new file mode 100644 index 0000000000..d98bb00b04 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_filter/docs/_filter_popover_props.jsx @@ -0,0 +1,71 @@ +import React from 'react' +import { Button, Filter, Flex, Select, TextInput } from 'playbook-ui' + +const FilterPopoverProps = (props) => { + const options = [ + { value: 'USA' }, + { value: 'Canada' }, + { value: 'Brazil' }, + { value: 'Philippines' }, + { value: 'A galaxy far far away, like really far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far far away...' }, + ] + + const popoverProps = { + width: "250px" + } + + return ( + + {({ closePopover }) => ( +
+ +