From 7bd6207ccf6bc8c2a22d7f258c675aec306759e3 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 1 Aug 2022 16:22:24 -0400 Subject: [PATCH 01/15] style(list): update sortable interaction styles --- .../lib/stylesheets/components/_button.scss | 4 ++ .../lib/stylesheets/components/_list.scss | 40 ++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/sage-assets/lib/stylesheets/components/_button.scss b/packages/sage-assets/lib/stylesheets/components/_button.scss index 86142944f3..74a9b8e5d0 100644 --- a/packages/sage-assets/lib/stylesheets/components/_button.scss +++ b/packages/sage-assets/lib/stylesheets/components/_button.scss @@ -279,6 +279,10 @@ $-btn-loading-min-height: rem(36px); } } + .sage-list__item-sortable-handle & { + cursor: grab; + } + .sage-input-group & { position: absolute; z-index: sage-z-index(default, 1); diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index a18ea70d21..c967ec035e 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -13,45 +13,49 @@ .sage-list__item { display: flex; align-items: center; - gap: sage-spacing(md); - padding-top: sage-spacing(sm); - padding-bottom: sage-spacing(sm); + gap: sage-spacing(sm); + padding: sage-spacing(xs) rem(12px); // TODO: is there a token for the 12px value? list-style: none; - border-top: sage-border(default); + border-radius: rem(10px); // TODO: is there a token for this? - &:first-child { - .sage-list--hide-first-border & { - border-top: 0; + .sage-list:not(.sage-list--sortable-dragging) &:hover { + &:not(.sage-list__item--sortable-active):not(.sage-list__item--sortable-drag) { + background-color: sage-color(grey, 200); } } } .sage-list__item--sortable { cursor: grab; +} - &.sage-list__item--sortable-active:active, - &:active { - cursor: grabbing; - } +.sage-list__item--sortable-active { + background-color: sage-color(white); +} + +.sage-list__item--sortable-drag { + background-color: sage-color(white); + box-shadow: sage-shadow(lg); + opacity: 1; } .sage-list__item--sortable-ghost { - opacity: 0.5; + background-color: sage-color(grey, 200); } .sage-list__item-content { flex: 1; + + .sage-list__item--sortable-ghost & { + opacity: 0; + } } .sage-list__item-more-actions, .sage-list__item-sortable-handle { width: auto; -} - -.sage-list__item-sortable-handle { - color: sage-color(charcoal, 100); - .sage-list__item:hover & { - color: sage-color(charcoal, 500); + .sage-list__item--sortable-ghost & { + opacity: 0; } } From 7e3d76f1c662dcaf1f88f3fc6d9157dcf899477f Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 1 Aug 2022 16:23:22 -0400 Subject: [PATCH 02/15] feat(list): update list interaction (rails) --- .../app/views/sage_components/_sage_list_item.html.erb | 7 ++++++- packages/sage-system/lib/list.js | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb index de8ceac248..b33efb6007 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb @@ -19,7 +19,12 @@ end > <% if component.sortable %>
- <%= sage_component SageIcon, { icon: "handle" } %> + <%= sage_component SageButton, { + subtle: true, + style: "secondary", + icon: { name: "handle-2-vertical", style: "only" }, + value: "Drag to sort", + } %>
<% end %> diff --git a/packages/sage-system/lib/list.js b/packages/sage-system/lib/list.js index a0240b7118..c610bdb60b 100644 --- a/packages/sage-system/lib/list.js +++ b/packages/sage-system/lib/list.js @@ -8,9 +8,11 @@ Sage.sortableList = (function() { const SELECTOR_CONTAINER = 'data-js-list-sortable'; const SELECTOR_ITEM_UPDATE_URL = 'data-js-list-sortable-update-url'; const SETTINGS = { + dragClass: 'sage-list__item--sortable-drag', ghostClass: 'sage-list__item--sortable-ghost', chosenClass: 'sage-list__item--sortable-active', }; + const DRAGGING_CLASSNAME = 'sage-list--sortable-dragging'; // ================================================== @@ -23,7 +25,11 @@ Sage.sortableList = (function() { Sortable.create(el, { ...SETTINGS, + onStart: function(evt) { + evt.srcElement.classList.add(DRAGGING_CLASSNAME); + }, onEnd: function (evt) { + evt.srcElement.classList.remove(DRAGGING_CLASSNAME); let updateUrl = evt.item.getAttribute(SELECTOR_ITEM_UPDATE_URL) // Check if the sorted Item: From b15e6b3347c2a4cc412f407745ad6cb5eb3acdca Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 1 Aug 2022 16:23:53 -0400 Subject: [PATCH 03/15] feat(list): update list interaction (react) --- packages/sage-react/lib/List/List.jsx | 25 ++++++++++++++++++----- packages/sage-react/lib/List/ListItem.jsx | 10 ++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index 9dba7448f8..8196e216ca 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -8,7 +8,6 @@ import { OptionsDropdown } from '../Dropdown'; export const List = ({ children, className, - hideFirstBorder, items, itemRenderer, sortableConfigs, @@ -17,11 +16,10 @@ export const List = ({ const classNames = classnames( 'sage-list', className, - { - 'sage-list--hide-first-border': hideFirstBorder, - } ); + const draggingClassname = 'sage-list--sortable-dragging'; + const renderItems = () => { if (children) { return children; @@ -40,6 +38,22 @@ export const List = ({ const Tag = tag; + const localSortableConfigs = { + ...sortableConfigs, + onEnd: (evt) => { + evt.srcElement.classList.remove(draggingClassname); + if (sortableConfigs.onEnd) { + sortableConfigs.onEnd(evt); + } + }, + onStart: (evt) => { + evt.srcElement.classList.add(draggingClassname); + if (sortableConfigs.onStart) { + sortableConfigs.onStart(evt); + } + }, + }; + return sortableConfigs ? ( {renderItems()} @@ -90,6 +104,7 @@ List.propTypes = { itemRenderer: PropTypes.func, sortableConfigs: PropTypes.shape({ onEnd: PropTypes.func, + onStart: PropTypes.func, setList: PropTypes.func, // Same as useState[1] tag: PropTypes.oneOfType([ PropTypes.string, diff --git a/packages/sage-react/lib/List/ListItem.jsx b/packages/sage-react/lib/List/ListItem.jsx index d01bae2e13..ec280cd049 100644 --- a/packages/sage-react/lib/List/ListItem.jsx +++ b/packages/sage-react/lib/List/ListItem.jsx @@ -3,6 +3,7 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import { Icon } from '../Icon'; import { Dropdown, OptionsDropdown } from '../Dropdown'; +import { Button } from '../Button'; export const ListItem = ({ children, @@ -23,7 +24,14 @@ export const ListItem = ({
  • {sortable && (
    - +
    )}
    From 415e4f59b2aedcef80d9e449b1cd577af49dc954 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 1 Aug 2022 16:26:11 -0400 Subject: [PATCH 04/15] style(lists): ensure older lists styles don't conflict with new list --- packages/sage-assets/lib/stylesheets/components/_lists.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sage-assets/lib/stylesheets/components/_lists.scss b/packages/sage-assets/lib/stylesheets/components/_lists.scss index 0e5aeb5685..bf1e7c4fbb 100644 --- a/packages/sage-assets/lib/stylesheets/components/_lists.scss +++ b/packages/sage-assets/lib/stylesheets/components/_lists.scss @@ -8,8 +8,9 @@ .sage-list { padding-left: 0; list-style: none; - - &:not(.sage-list--inline-compact):not(.sage-list--inline-fit-compact) { + + // TODO: These styles interfered with the new List component and should be deprecated. + &:not(.sage-list--inline-compact):not(.sage-list--inline-fit-compact):not(.sage-list__item) { margin-right: -1 * sage-spacing(sm); margin-left: -1 * sage-spacing(sm); From e9c76d7be3081d923aaf695f7f29eedda4e7bf67 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Fri, 12 Aug 2022 15:30:55 -0400 Subject: [PATCH 05/15] feat(list): wip adding advanced drag features --- .../components/list/_preview.html.erb | 14 +++++-- .../app/sage_tokens/sage_schemas.rb | 1 + .../views/sage_components/_sage_list.html.erb | 3 +- .../sage_components/_sage_list_item.html.erb | 3 +- .../lib/stylesheets/components/_list.scss | 37 ++++++++++++++++++- packages/sage-react/lib/List/ListItem.jsx | 1 + packages/sage-system/lib/list.js | 26 +++++++++++-- 7 files changed, 74 insertions(+), 11 deletions(-) diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index a1aa0932bd..a9da2f8e8b 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -71,7 +71,10 @@ This assumes that the contents of the list are already formatted as desired. sortable: true, sortable_update_url: "#sortable-update-url?item=#{item[:id]}", } - } + }, + sortable_configs: { + handle: false, + }, } %>

    Compositional approach

    @@ -79,7 +82,12 @@ This assumes that the contents of the list are already formatted as desired. If you need more native content formatting you can instead opt to render items using a nested loop and the SageListItem component. ", use_sage_type: true) %> -<%= sage_component SageList, { sortable_resource: "sample_products_2" } do %> +<%= sage_component SageList, { + sortable_resource: "sample_products_2", + # sortable_configs: { + # handle: '.green', + # }, +} do %> <% sample_products.each do | item | %> <%= sage_component SageListItem, { id: item[:id], @@ -89,7 +97,7 @@ If you need more native content formatting you can instead opt to render items u css_classes: SageClassnames::REVEAL_CONTAINER, } do %> <%= sage_component SageCardRow, { grid_template: "ete" } do %> - + <%= sage_component SageCardBlock, {} do %>

    <%= item[:name] %>

    Item <%= item[:id] %> specs

    diff --git a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb index a8e1dca051..ca153f4b26 100644 --- a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb +++ b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb @@ -149,6 +149,7 @@ module SageSchemas LIST = { items: [:optional, [[SageSchemas::LIST_ITEM]]], + sortable_configs: [:optional, NilClass, Hash], sortable_resource: [:optional, NilClass, String], tag: [:optional, NilClass, Set.new(["ul", "ol"])], hide_first_border: [:optional, TrueClass, String], diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index 7c7a2d9b66..dc1cdbc833 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -5,9 +5,11 @@ tag = component.tag.present? ? component.tag : "ul" class=" sage-list <%= component.generated_css_classes %> + <%= "sage-list__item--all-draggable" if component.sortable_configs && component.sortable_configs[:handle] == false %> <%= "sage-list--hide-first-border" if component.hide_first_border %> " <%= "data-js-list-sortable=#{component.sortable_resource}" if component.sortable_resource.present? %> + <%= "data-js-list-sortable-configs=#{component.sortable_configs.to_json}" if component.sortable_configs.present? %> <%= component.generated_html_attributes.html_safe %> > <% if component.items.present? %> @@ -15,6 +17,5 @@ tag = component.tag.present? ? component.tag : "ul" <%= sage_component SageListItem, item_configs %> <% end %> <% end %> - <%= component.content %> > diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb index b33efb6007..8a6d9e51f2 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb @@ -20,9 +20,10 @@ end <% if component.sortable %>
    <%= sage_component SageButton, { + css_classes: "sage-list__item-sortable-handle-control", + icon: { name: "handle-2-vertical", style: "only" }, subtle: true, style: "secondary", - icon: { name: "handle-2-vertical", style: "only" }, value: "Drag to sort", } %>
    diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index c967ec035e..94995bea23 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -25,10 +25,16 @@ } } -.sage-list__item--sortable { +.sage-list__item--all-draggable { cursor: grab; + + &.sage-list--dragging:active { + cursor: grabbing; + } } +// .sage-list__item--sortable {} + .sage-list__item--sortable-active { background-color: sage-color(white); } @@ -49,13 +55,40 @@ .sage-list__item--sortable-ghost & { opacity: 0; } + + .sage-list__item--all-draggable & * { + user-select: none; + } +} + +.sage-list__item-more-actions { + width: auto; + + .sage-list__item--sortable-ghost & { + opacity: 0; + } } -.sage-list__item-more-actions, .sage-list__item-sortable-handle { width: auto; .sage-list__item--sortable-ghost & { opacity: 0; } + + .sage-list__item--all-draggable & { + pointer-events: none; + } + + &:hover { + cursor: grab; + } + + .sage-list--dragging &:active { + cursor: grabbing; + } +} + +.sage-list__item-sortable-handle-control { + user-select: none; } diff --git a/packages/sage-react/lib/List/ListItem.jsx b/packages/sage-react/lib/List/ListItem.jsx index ec280cd049..cdf96bb1fb 100644 --- a/packages/sage-react/lib/List/ListItem.jsx +++ b/packages/sage-react/lib/List/ListItem.jsx @@ -26,6 +26,7 @@ export const ListItem = ({
    +
    )}
    From 6b6f0ff5cbe7ba8348c60fb7227dd138b93ef8ce Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Tue, 16 Aug 2022 19:55:06 -0400 Subject: [PATCH 07/15] feat(list): add fallback and improved documentation --- .../components/list/_preview.html.erb | 56 +++++++++++++++---- .../examples/components/list/_props.html.erb | 10 ++++ .../views/sage_components/_sage_list.html.erb | 2 +- .../lib/stylesheets/components/_list.scss | 27 +++------ packages/sage-react/lib/List/List.jsx | 47 +++++++++++----- packages/sage-react/lib/List/List.story.jsx | 11 ++++ packages/sage-react/lib/List/ListItem.jsx | 1 - packages/sage-system/lib/list.js | 4 +- 8 files changed, 111 insertions(+), 47 deletions(-) diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index a9da2f8e8b..b257778cf4 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -56,16 +56,49 @@ sample_products = [

    Default configuration

    <%= md(" SageList is implemented most simply by passing the desired contents for the items through the `SageList` `items` property. -This assumes that the contents of the list are already formatted as desired. +This assumes that the contents of the list are already formatted as desired or you're outputting simple content values. +", use_sage_type: true) %> + +<%= sage_component SageList, { + items: sample_products.map { | item | { + content: "Item #{item[:name]}", + id: item[:id], + more_actions: { items: dropdown_items(item[:id]) }, + } + }, +} %> + +

    Sortable configuration

    +<%= md(" +Sortable lists can be created with SageList by adding a `sortable_resource` to the `SageList` +and setting `sortable: true` on the items. +You can also pass a `sortable_update_url` for sorting callbacks +and additional configurations through `sortable_configs`. See the Props list for more details. ", use_sage_type: true) %> <%= sage_component SageList, { sortable_resource: "sample_products", items: sample_products.map { | item | { - content: %( -

    Item #{item[:name]}

    -

    Item #{item[:id]} specs

    - ).html_safe, + content: "Item #{item[:name]}", + id: item[:id], + more_actions: { items: dropdown_items(item[:id]) }, + sortable: true, + sortable_update_url: "#sortable-update-url?item=#{item[:id]}", + } + }, +} %> + +

    Fully draggable row

    +<%= md(" +Sortable lists are implemented by default such that an item is only draggable +by clicking and dragging on the drag handle at the start of its row. +If you would like the whole row to be draggable, use the `sortable_configs` to set `handle: false`. +", use_sage_type: true) %> + +<%= sage_component SageList, { + sortable_resource: "sample_products", + items: sample_products.map { | item | { + content: "Item #{item[:name]}", id: item[:id], more_actions: { items: dropdown_items(item[:id]) }, sortable: true, @@ -74,19 +107,20 @@ This assumes that the contents of the list are already formatted as desired. }, sortable_configs: { handle: false, - }, + } } %>

    Compositional approach

    <%= md(" -If you need more native content formatting you can instead opt to render items using a nested loop and the SageListItem component. +If you need more native content formatting you can instead opt to render items using a nested loop and the `SageListItem` component. +Note that this example also implements the [Reveal utility class](#{pages_helpers_path(:reveal)}). ", use_sage_type: true) %> <%= sage_component SageList, { sortable_resource: "sample_products_2", - # sortable_configs: { - # handle: '.green', - # }, + sortable_configs: { + handle: false, + } , } do %> <% sample_products.each do | item | %> <%= sage_component SageListItem, { @@ -97,7 +131,7 @@ If you need more native content formatting you can instead opt to render items u css_classes: SageClassnames::REVEAL_CONTAINER, } do %> <%= sage_component SageCardRow, { grid_template: "ete" } do %> - + <%= sage_component SageCardBlock, {} do %>

    <%= item[:name] %>

    Item <%= item[:id] %> specs

    diff --git a/docs/app/views/examples/components/list/_props.html.erb b/docs/app/views/examples/components/list/_props.html.erb index 4a6eb704cb..cb8133df0c 100644 --- a/docs/app/views/examples/components/list/_props.html.erb +++ b/docs/app/views/examples/components/list/_props.html.erb @@ -9,6 +9,16 @@ <%= md('See schema for `SageListItem` below.') %> <%= md('`[]`') %> + + <%= md('`sortable_configs`') %> + <%= md(' + Provide additional configurations according to those available for [SortableJS](https://github.com/SortableJS/Sortable#options). + At this time, we only support properties that allow simple data types as values (string, boolean, number). + In particular, use `handle: false` in order to make each item fully draggable (rather than just by the drag handle). + ') %> + <%= md('Hash') %> + <%= md('`nil`') %> + <%= md('`sortable_resource`') %> <%= md('Provide the resource name for a sortable list. Required only when `sortable` is activated for any child items') %> diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index dc1cdbc833..8efe7e816e 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -5,7 +5,7 @@ tag = component.tag.present? ? component.tag : "ul" class=" sage-list <%= component.generated_css_classes %> - <%= "sage-list__item--all-draggable" if component.sortable_configs && component.sortable_configs[:handle] == false %> + <%= "sage-list--all-draggable" if component.sortable_configs && component.sortable_configs[:handle] == false %> <%= "sage-list--hide-first-border" if component.hide_first_border %> " <%= "data-js-list-sortable=#{component.sortable_resource}" if component.sortable_resource.present? %> diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index 94995bea23..65172f83b6 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -23,20 +23,21 @@ background-color: sage-color(grey, 200); } } -} - -.sage-list__item--all-draggable { - cursor: grab; - &.sage-list--dragging:active { - cursor: grabbing; + .sage-list--all-draggable & { + cursor: grab; } } + // .sage-list__item--sortable {} .sage-list__item--sortable-active { background-color: sage-color(white); + + .sage-list--all-draggable & { + cursor: grabbing; + } } .sage-list__item--sortable-drag { @@ -55,10 +56,6 @@ .sage-list__item--sortable-ghost & { opacity: 0; } - - .sage-list__item--all-draggable & * { - user-select: none; - } } .sage-list__item-more-actions { @@ -76,19 +73,11 @@ opacity: 0; } - .sage-list__item--all-draggable & { - pointer-events: none; - } - &:hover { cursor: grab; } - .sage-list--dragging &:active { + .sage-list__item--sortable-active & { cursor: grabbing; } } - -.sage-list__item-sortable-handle-control { - user-select: none; -} diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index 8196e216ca..69c038c8af 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -16,6 +16,9 @@ export const List = ({ const classNames = classnames( 'sage-list', className, + { + 'sage-list--all-draggable': sortableConfigs && sortableConfigs.handle === false, + } ); const draggingClassname = 'sage-list--sortable-dragging'; @@ -39,7 +42,36 @@ export const List = ({ const Tag = tag; const localSortableConfigs = { + handle: '.sage-list__item-sortable-handle', + /* + NOTE: This is added because Safari 13+ has a draggable api bug. + Yet we allow the user to override if desired. + https://github.com/SortableJS/Sortable/issues/1571 + */ + forceFallback: true, + /* + NOTE: Order matters here as the values above `...sortableConfigs` + are defaults that we allow to be overridden, + while values that come after the spread are values we want fixed. + */ ...sortableConfigs, + chosenClass: 'sage-list__item--sortable-active', + className: classNames, + dragClass: 'sage-list__item--sortable-drag', + ghostClass: 'sage-list__item--sortable-ghost', + list: items, + /* + NOTE: `tag` is allowed as a primary property for non-sortable lists, + so we allow it to be passed directly here for sortable list + rather than leaving it only available through `sortableConfigs` + */ + tag, + /* + NOTE: `onEnd` and `onStart` need to come after this + so that they override any function provided for these. + For these, we need a little default behavior for these + that will then call a provided function if it is present. + */ onEnd: (evt) => { evt.srcElement.classList.remove(draggingClassname); if (sortableConfigs.onEnd) { @@ -55,15 +87,7 @@ export const List = ({ }; return sortableConfigs ? ( - + {renderItems()} ) : ( @@ -103,13 +127,10 @@ List.propTypes = { ), itemRenderer: PropTypes.func, sortableConfigs: PropTypes.shape({ + handle: PropTypes.oneOfType([PropTypes.string, PropTypes.oneOf(false)]), onEnd: PropTypes.func, onStart: PropTypes.func, setList: PropTypes.func, // Same as useState[1] - tag: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.elementType, - ]), // NOTE: See https://github.com/SortableJS/react-sortablejs for full list of additional options }), tag: PropTypes.oneOf(['ul', 'ol']), diff --git a/packages/sage-react/lib/List/List.story.jsx b/packages/sage-react/lib/List/List.story.jsx index 015318a6c3..8d0fd07f8a 100644 --- a/packages/sage-react/lib/List/List.story.jsx +++ b/packages/sage-react/lib/List/List.story.jsx @@ -32,3 +32,14 @@ export const SortableList = () => { /> ); }; + +export const FullyDraggableSortableList = () => { + const [items, setItems] = React.useState(sampleItems); + return ( + + ); +}; diff --git a/packages/sage-react/lib/List/ListItem.jsx b/packages/sage-react/lib/List/ListItem.jsx index 7bee631aeb..5335707b50 100644 --- a/packages/sage-react/lib/List/ListItem.jsx +++ b/packages/sage-react/lib/List/ListItem.jsx @@ -3,7 +3,6 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import { Icon } from '../Icon'; import { Dropdown, OptionsDropdown } from '../Dropdown'; -import { Button } from '../Button'; export const ListItem = ({ children, diff --git a/packages/sage-system/lib/list.js b/packages/sage-system/lib/list.js index 6ec6216913..b083bb0bff 100644 --- a/packages/sage-system/lib/list.js +++ b/packages/sage-system/lib/list.js @@ -8,7 +8,6 @@ Sage.sortableList = (function() { // ================================================== const DRAGGING_CLASSNAME = 'sage-list--sortable-dragging'; - const HANDLE_CLASSNAME = 'sage-list__item-sortable-handle'; const SELECTOR_CONFIGS = 'data-js-list-sortable-configs'; const SELECTOR_CONTAINER = 'data-js-list-sortable'; const SELECTOR_ITEM_UPDATE_URL = 'data-js-list-sortable-update-url'; @@ -16,7 +15,8 @@ Sage.sortableList = (function() { dragClass: 'sage-list__item--sortable-drag', ghostClass: 'sage-list__item--sortable-ghost', chosenClass: 'sage-list__item--sortable-active', - handle: `.${HANDLE_CLASSNAME}`, + handle: '.sage-list__item-sortable-handle', + forceFallback: true, // NOTE: This is added because Safari 13+ has a draggable api bug https://github.com/SortableJS/Sortable/issues/1571 }; From 27237c5deb3a226eb12403d680cf589f0508a5a9 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Tue, 16 Aug 2022 20:15:10 -0400 Subject: [PATCH 08/15] feat(lsit): cleanup --- .../components/list/_preview.html.erb | 6 ++--- .../lib/stylesheets/components/_button.scss | 4 --- .../lib/stylesheets/components/_list.scss | 27 +++++++++---------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index b257778cf4..f70302e241 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -55,7 +55,7 @@ sample_products = [

    Default configuration

    <%= md(" -SageList is implemented most simply by passing the desired contents for the items through the `SageList` `items` property. +`SageList` is implemented most simply by passing the desired contents for the items through the `SageList` `items` property. This assumes that the contents of the list are already formatted as desired or you're outputting simple content values. ", use_sage_type: true) %> @@ -70,7 +70,7 @@ This assumes that the contents of the list are already formatted as desired or y

    Sortable configuration

    <%= md(" -Sortable lists can be created with SageList by adding a `sortable_resource` to the `SageList` +Sortable lists can be created with `SageList` by adding a `sortable_resource` to the `SageList` and setting `sortable: true` on the items. You can also pass a `sortable_update_url` for sorting callbacks and additional configurations through `sortable_configs`. See the Props list for more details. @@ -131,7 +131,7 @@ Note that this example also implements the [Reveal utility class](#{pages_helper css_classes: SageClassnames::REVEAL_CONTAINER, } do %> <%= sage_component SageCardRow, { grid_template: "ete" } do %> - + <%= sage_component SageCardBlock, {} do %>

    <%= item[:name] %>

    Item <%= item[:id] %> specs

    diff --git a/packages/sage-assets/lib/stylesheets/components/_button.scss b/packages/sage-assets/lib/stylesheets/components/_button.scss index 74a9b8e5d0..86142944f3 100644 --- a/packages/sage-assets/lib/stylesheets/components/_button.scss +++ b/packages/sage-assets/lib/stylesheets/components/_button.scss @@ -279,10 +279,6 @@ $-btn-loading-min-height: rem(36px); } } - .sage-list__item-sortable-handle & { - cursor: grab; - } - .sage-input-group & { position: absolute; z-index: sage-z-index(default, 1); diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index 65172f83b6..d63917f579 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -14,24 +14,23 @@ display: flex; align-items: center; gap: sage-spacing(sm); - padding: sage-spacing(xs) rem(12px); // TODO: is there a token for the 12px value? + padding: sage-spacing(xs) rem(12px); list-style: none; - border-radius: rem(10px); // TODO: is there a token for this? + border-radius: sage-border(radius-medium); - .sage-list:not(.sage-list--sortable-dragging) &:hover { - &:not(.sage-list__item--sortable-active):not(.sage-list__item--sortable-drag) { - background-color: sage-color(grey, 200); - } + // .sage-list:not(.sage-list--sortable-dragging) &:hover { + // &:not(.sage-list__item--sortable-active):not(.sage-list__item--sortable-drag) { + &:hover { + background-color: sage-color(grey, 200); } + // } + // } .sage-list--all-draggable & { cursor: grab; } } - -// .sage-list__item--sortable {} - .sage-list__item--sortable-active { background-color: sage-color(white); @@ -43,7 +42,7 @@ .sage-list__item--sortable-drag { background-color: sage-color(white); box-shadow: sage-shadow(lg); - opacity: 1; + opacity: 1 !important; } .sage-list__item--sortable-ghost { @@ -69,10 +68,6 @@ .sage-list__item-sortable-handle { width: auto; - .sage-list__item--sortable-ghost & { - opacity: 0; - } - &:hover { cursor: grab; } @@ -80,4 +75,8 @@ .sage-list__item--sortable-active & { cursor: grabbing; } + + .sage-list__item--sortable-ghost & { + opacity: 0; + } } From 9cc82533a2e9e6c61dd9261c1749cb97bf2d54fc Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Tue, 16 Aug 2022 20:17:08 -0400 Subject: [PATCH 09/15] feat(list): remove `hide_first_border` property that is no longer needed --- docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb | 1 - .../sage_rails/app/views/sage_components/_sage_list.html.erb | 1 - packages/sage-assets/lib/stylesheets/components/_list.scss | 4 ---- packages/sage-react/lib/List/List.jsx | 2 -- 4 files changed, 8 deletions(-) diff --git a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb index ca153f4b26..ff157c2f9c 100644 --- a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb +++ b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb @@ -152,7 +152,6 @@ module SageSchemas sortable_configs: [:optional, NilClass, Hash], sortable_resource: [:optional, NilClass, String], tag: [:optional, NilClass, Set.new(["ul", "ol"])], - hide_first_border: [:optional, TrueClass, String], } PANEL_FIGURE = { diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index 8efe7e816e..f76b31d5ce 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -6,7 +6,6 @@ tag = component.tag.present? ? component.tag : "ul" sage-list <%= component.generated_css_classes %> <%= "sage-list--all-draggable" if component.sortable_configs && component.sortable_configs[:handle] == false %> - <%= "sage-list--hide-first-border" if component.hide_first_border %> " <%= "data-js-list-sortable=#{component.sortable_resource}" if component.sortable_resource.present? %> <%= "data-js-list-sortable-configs=#{component.sortable_configs.to_json}" if component.sortable_configs.present? %> diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index d63917f579..b0f412125b 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -18,13 +18,9 @@ list-style: none; border-radius: sage-border(radius-medium); - // .sage-list:not(.sage-list--sortable-dragging) &:hover { - // &:not(.sage-list__item--sortable-active):not(.sage-list__item--sortable-drag) { &:hover { background-color: sage-color(grey, 200); } - // } - // } .sage-list--all-draggable & { cursor: grab; diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index 69c038c8af..b1f9d226b2 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -102,7 +102,6 @@ List.Item = ListItem; List.defaultProps = { children: null, className: null, - hideFirstBorder: false, items: [], itemRenderer: null, sortableConfigs: null, @@ -112,7 +111,6 @@ List.defaultProps = { List.propTypes = { children: PropTypes.node, className: PropTypes.string, - hideFirstBorder: PropTypes.bool, items: PropTypes.arrayOf( PropTypes.shape({ children: PropTypes.node, From c98ad632fdb7133ebdbcd8a19dbbadc8fec69a28 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Wed, 17 Aug 2022 10:36:18 -0400 Subject: [PATCH 10/15] chore(list): clean up linting and documentation exception --- packages/sage-assets/lib/stylesheets/components/_list.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index b0f412125b..91f90e8ce2 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -38,7 +38,9 @@ .sage-list__item--sortable-drag { background-color: sage-color(white); box-shadow: sage-shadow(lg); - opacity: 1 !important; + // NOTE: !important is added and lint-ignored here so that it can override + // inline opacity that the SortableJS utility we're using adds during sorting. + opacity: 1 !important; /* stylelint-disable-line declaration-no-important */ } .sage-list__item--sortable-ghost { From 608e6087ac8963c6c7ceff8150b93bbba7585a20 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Thu, 18 Aug 2022 14:19:28 -0400 Subject: [PATCH 11/15] chore(list): clean up from feedback --- .../views/examples/components/list/_preview.html.erb | 10 +++++----- .../app/views/examples/components/list/_props.html.erb | 6 ------ packages/sage-system/lib/list.js | 2 -- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index f70302e241..9b6964d891 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -62,7 +62,7 @@ This assumes that the contents of the list are already formatted as desired or y <%= sage_component SageList, { items: sample_products.map { | item | { content: "Item #{item[:name]}", - id: item[:id], + id: "example-default-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, } }, @@ -80,7 +80,7 @@ and additional configurations through `sortable_configs`. See the Props list for sortable_resource: "sample_products", items: sample_products.map { | item | { content: "Item #{item[:name]}", - id: item[:id], + id: "example-sortable-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, sortable: true, sortable_update_url: "#sortable-update-url?item=#{item[:id]}", @@ -99,7 +99,7 @@ If you would like the whole row to be draggable, use the `sortable_configs` to s sortable_resource: "sample_products", items: sample_products.map { | item | { content: "Item #{item[:name]}", - id: item[:id], + id: "example-fully-draggable-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, sortable: true, sortable_update_url: "#sortable-update-url?item=#{item[:id]}", @@ -124,14 +124,14 @@ Note that this example also implements the [Reveal utility class](#{pages_helper } do %> <% sample_products.each do | item | %> <%= sage_component SageListItem, { - id: item[:id], + id: "example-default-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, sortable: true, sortable_update_url: "#sortable-update-url?item={item[:id]}", css_classes: SageClassnames::REVEAL_CONTAINER, } do %> <%= sage_component SageCardRow, { grid_template: "ete" } do %> - + <%= sage_component SageCardBlock, {} do %>

    <%= item[:name] %>

    Item <%= item[:id] %> specs

    diff --git a/docs/app/views/examples/components/list/_props.html.erb b/docs/app/views/examples/components/list/_props.html.erb index cb8133df0c..870df428c1 100644 --- a/docs/app/views/examples/components/list/_props.html.erb +++ b/docs/app/views/examples/components/list/_props.html.erb @@ -25,12 +25,6 @@ <%= md('String') %> <%= md('`nil`') %> - - <%= md('`hide_first_border`') %> - <%= md('Removes top border from first list item within a SageList') %> - <%= md('Boolean') %> - <%= md('`false`') %> - <%= md("**SageListItem**") %> diff --git a/packages/sage-system/lib/list.js b/packages/sage-system/lib/list.js index b083bb0bff..0cdbe014cf 100644 --- a/packages/sage-system/lib/list.js +++ b/packages/sage-system/lib/list.js @@ -38,8 +38,6 @@ Sage.sortableList = (function() { sortableConfigs = { ...DEFAULT_CONFIGS }; } - console.log('final configs', resourceName, sortableConfigs); - Sortable.create(el, { ...sortableConfigs, onStart: function(evt) { From f2f43dbf01a26dfd3aa8e353b48a547443df0067 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 22 Aug 2022 14:40:16 -0400 Subject: [PATCH 12/15] feat(list): adjust handle affordance for clarity --- .../components/list/_preview.html.erb | 41 +++--- .../examples/components/list/_props.html.erb | 11 +- .../app/sage_tokens/sage_schemas.rb | 4 +- .../views/sage_components/_sage_list.html.erb | 5 +- .../sage_components/_sage_list_item.html.erb | 9 +- .../lib/stylesheets/components/_list.scss | 11 +- packages/sage-react/lib/List/List.jsx | 121 ++++++++---------- packages/sage-react/lib/List/List.story.jsx | 14 +- packages/sage-react/lib/List/configs.js | 4 + packages/sage-system/lib/list.js | 42 ++---- 10 files changed, 117 insertions(+), 145 deletions(-) create mode 100644 packages/sage-react/lib/List/configs.js diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index 9b6964d891..d18d106e22 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -70,19 +70,18 @@ This assumes that the contents of the list are already formatted as desired or y

    Sortable configuration

    <%= md(" -Sortable lists can be created with `SageList` by adding a `sortable_resource` to the `SageList` -and setting `sortable: true` on the items. -You can also pass a `sortable_update_url` for sorting callbacks -and additional configurations through `sortable_configs`. See the Props list for more details. +Sortable lists can be created with `SageList` by adding `sortable: true` and a `sortable_resource`. +You can also pass a `sortable_update_url` for sorting callbacks to items +that will be called after the user finishes sorting an item. ", use_sage_type: true) %> <%= sage_component SageList, { + sortable: true, sortable_resource: "sample_products", items: sample_products.map { | item | { content: "Item #{item[:name]}", id: "example-sortable-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, - sortable: true, sortable_update_url: "#sortable-update-url?item=#{item[:id]}", } }, @@ -90,24 +89,20 @@ and additional configurations through `sortable_configs`. See the Props list for

    Fully draggable row

    <%= md(" -Sortable lists are implemented by default such that an item is only draggable -by clicking and dragging on the drag handle at the start of its row. -If you would like the whole row to be draggable, use the `sortable_configs` to set `handle: false`. +By default only the drag handle is active for dragging/sorting a row. +However, `draggable_anywhere` can be set to `true` in order to allow the whole row to be draggable instead. ", use_sage_type: true) %> <%= sage_component SageList, { - sortable_resource: "sample_products", + sortable: true, + sortable_resource: "sample_products_2", items: sample_products.map { | item | { - content: "Item #{item[:name]}", - id: "example-fully-draggable-item-#{item[:id]}", - more_actions: { items: dropdown_items(item[:id]) }, - sortable: true, - sortable_update_url: "#sortable-update-url?item=#{item[:id]}", - } - }, - sortable_configs: { - handle: false, - } + content: "Item #{item[:name]}", + id: "example-fully-draggable-item-#{item[:id]}", + more_actions: { items: dropdown_items(item[:id]) }, + sortable_update_url: "#sortable-update-url?item=#{item[:id]}", + }}, + drag_handle_type: "row", } %>

    Compositional approach

    @@ -117,16 +112,14 @@ Note that this example also implements the [Reveal utility class](#{pages_helper ", use_sage_type: true) %> <%= sage_component SageList, { - sortable_resource: "sample_products_2", - sortable_configs: { - handle: false, - } , + sortable: true, + sortable_resource: "sample_products_3", + drag_handle_type: "row", } do %> <% sample_products.each do | item | %> <%= sage_component SageListItem, { id: "example-default-item-#{item[:id]}", more_actions: { items: dropdown_items(item[:id]) }, - sortable: true, sortable_update_url: "#sortable-update-url?item={item[:id]}", css_classes: SageClassnames::REVEAL_CONTAINER, } do %> diff --git a/docs/app/views/examples/components/list/_props.html.erb b/docs/app/views/examples/components/list/_props.html.erb index 870df428c1..b82b551784 100644 --- a/docs/app/views/examples/components/list/_props.html.erb +++ b/docs/app/views/examples/components/list/_props.html.erb @@ -10,14 +10,13 @@ <%= md('`[]`') %> - <%= md('`sortable_configs`') %> + <%= md('`drag_handle_type`') %> <%= md(' - Provide additional configurations according to those available for [SortableJS](https://github.com/SortableJS/Sortable#options). - At this time, we only support properties that allow simple data types as values (string, boolean, number). - In particular, use `handle: false` in order to make each item fully draggable (rather than just by the drag handle). + By default only the drag handle is active for dragging/sorting a row. + However, `drag_handle_type` can be set to `"row"` in order to allow the whole row to be draggable instead. ') %> - <%= md('Hash') %> - <%= md('`nil`') %> + <%= md('`"default"` | `"row"` ') %> + <%= md('`"default"`') %> <%= md('`sortable_resource`') %> diff --git a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb index ff157c2f9c..1a9516fb5e 100644 --- a/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb +++ b/docs/lib/sage_rails/app/sage_tokens/sage_schemas.rb @@ -143,13 +143,13 @@ module SageSchemas LIST_ITEM = { id: [:optional, Integer, String], more_actions: [:optional, NilClass, SageSchemas::DROPDOWN], - sortable: [:optional, NilClass, TrueClass], sortable_update_url: [:optional, NilClass, String], } LIST = { + drag_handle_type: [:optional, NilClass, Set.new(["default", "row"])], items: [:optional, [[SageSchemas::LIST_ITEM]]], - sortable_configs: [:optional, NilClass, Hash], + sortable: [:optional, NilClass, TrueClass], sortable_resource: [:optional, NilClass, String], tag: [:optional, NilClass, Set.new(["ul", "ol"])], } diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index f76b31d5ce..1739f5e4c5 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -1,14 +1,15 @@ <% tag = component.tag.present? ? component.tag : "ul" +drag_handle_type = component.tag.present? ? component.tag : "default" %> <<%= tag %> class=" sage-list <%= component.generated_css_classes %> - <%= "sage-list--all-draggable" if component.sortable_configs && component.sortable_configs[:handle] == false %> + <%= "sage-list--sortable" if component.sortable %> + <%= "sage-list--draggable-by-row" if component.drag_handle_type == "row" %> " <%= "data-js-list-sortable=#{component.sortable_resource}" if component.sortable_resource.present? %> - <%= "data-js-list-sortable-configs=#{component.sortable_configs.to_json}" if component.sortable_configs.present? %> <%= component.generated_html_attributes.html_safe %> > <% if component.items.present? %> diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb index d1bf13baea..6159106435 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list_item.html.erb @@ -10,18 +10,15 @@ end
  • <%= component.generated_css_classes %> " <%= "data-js-list-sortable-update-url=#{component.sortable_update_url}" if component.sortable_update_url.present? %> <%= "id=#{component.id}" if component.id %> <%= component.generated_html_attributes.html_safe %> > - <% if component.sortable %> -
    - <%= sage_component SageIcon, { icon: "handle-2-vertical" } %> -
    - <% end %> +
    + <%= sage_component SageIcon, { icon: "handle-2-vertical" } %> +
    <%= component.content %> diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index 91f90e8ce2..5c08f6a1bb 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -22,7 +22,7 @@ background-color: sage-color(grey, 200); } - .sage-list--all-draggable & { + .sage-list--draggable-by-row & { cursor: grab; } } @@ -30,7 +30,7 @@ .sage-list__item--sortable-active { background-color: sage-color(white); - .sage-list--all-draggable & { + .sage-list--draggable-by-row & { cursor: grabbing; } } @@ -64,7 +64,12 @@ } .sage-list__item-sortable-handle { - width: auto; + display: none; + + .sage-list--sortable & { + width: auto; + display: initial; + } &:hover { cursor: grab; diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index b1f9d226b2..65ae783988 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -3,21 +3,26 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import { ReactSortable } from 'react-sortablejs'; import { ListItem } from './ListItem'; -import { OptionsDropdown } from '../Dropdown'; +import { LIST_DRAG_HANDLE_TYPES } from './configs'; export const List = ({ children, className, + dragHandleType, items, itemRenderer, - sortableConfigs, + onEnd, + onStart, + setList, + sortable, tag, }) => { const classNames = classnames( 'sage-list', className, { - 'sage-list--all-draggable': sortableConfigs && sortableConfigs.handle === false, + 'sage-list--sortable': sortable, + 'sage-list--draggable-by-row': dragHandleType === LIST_DRAG_HANDLE_TYPES.ROW, } ); @@ -31,7 +36,7 @@ export const List = ({ return ( <> {items.map(({ id, ...rest }) => ( - + {itemRenderer && itemRenderer({ id, ...rest })} ))} @@ -41,53 +46,35 @@ export const List = ({ const Tag = tag; - const localSortableConfigs = { - handle: '.sage-list__item-sortable-handle', - /* - NOTE: This is added because Safari 13+ has a draggable api bug. - Yet we allow the user to override if desired. - https://github.com/SortableJS/Sortable/issues/1571 - */ - forceFallback: true, - /* - NOTE: Order matters here as the values above `...sortableConfigs` - are defaults that we allow to be overridden, - while values that come after the spread are values we want fixed. - */ - ...sortableConfigs, - chosenClass: 'sage-list__item--sortable-active', - className: classNames, - dragClass: 'sage-list__item--sortable-drag', - ghostClass: 'sage-list__item--sortable-ghost', - list: items, - /* - NOTE: `tag` is allowed as a primary property for non-sortable lists, - so we allow it to be passed directly here for sortable list - rather than leaving it only available through `sortableConfigs` - */ - tag, - /* - NOTE: `onEnd` and `onStart` need to come after this - so that they override any function provided for these. - For these, we need a little default behavior for these - that will then call a provided function if it is present. - */ - onEnd: (evt) => { - evt.srcElement.classList.remove(draggingClassname); - if (sortableConfigs.onEnd) { - sortableConfigs.onEnd(evt); - } - }, - onStart: (evt) => { - evt.srcElement.classList.add(draggingClassname); - if (sortableConfigs.onStart) { - sortableConfigs.onStart(evt); - } - }, - }; - - return sortableConfigs ? ( - + return sortable ? ( + { + evt.srcElement.classList.remove(draggingClassname); + if (onEnd) { + onEnd(evt); + } + }} + onStart={(evt) => { + evt.srcElement.classList.add(draggingClassname); + if (onStart) { + onStart(evt); + } + }} + setList={setList} + tag={tag} + > {renderItems()} ) : ( @@ -98,38 +85,30 @@ export const List = ({ }; List.Item = ListItem; +List.DRAG_HANDLE_TYPES = LIST_DRAG_HANDLE_TYPES; List.defaultProps = { children: null, className: null, items: [], itemRenderer: null, - sortableConfigs: null, + dragHandleType: List.DRAG_HANDLE_TYPES.DEFAULT, + onEnd: null, + onStart: null, + setList: null, + sortable: false, tag: 'ul', }; List.propTypes = { children: PropTypes.node, className: PropTypes.string, - items: PropTypes.arrayOf( - PropTypes.shape({ - children: PropTypes.node, - chosen: PropTypes.bool, // From react-sortablejs - className: PropTypes.string, - filtered: PropTypes.bool, // From react-sortablejs - id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - moreActions: PropTypes.shape({ ...OptionsDropdown.propTypes }), - sortable: PropTypes.bool, - selected: PropTypes.bool, // From react-sortablejs - }) - ), + dragHandleType: PropTypes.oneOf(Object.values(List.DRAG_HANDLE_TYPES)), + items: PropTypes.arrayOf(PropTypes.shape({ ...ListItem.propTypes })), itemRenderer: PropTypes.func, - sortableConfigs: PropTypes.shape({ - handle: PropTypes.oneOfType([PropTypes.string, PropTypes.oneOf(false)]), - onEnd: PropTypes.func, - onStart: PropTypes.func, - setList: PropTypes.func, // Same as useState[1] - // NOTE: See https://github.com/SortableJS/react-sortablejs for full list of additional options - }), + onEnd: PropTypes.func, + onStart: PropTypes.func, + setList: PropTypes.func, // Same as useState[1] + sortable: PropTypes.bool, tag: PropTypes.oneOf(['ul', 'ol']), }; diff --git a/packages/sage-react/lib/List/List.story.jsx b/packages/sage-react/lib/List/List.story.jsx index 8d0fd07f8a..6835b32ff4 100644 --- a/packages/sage-react/lib/List/List.story.jsx +++ b/packages/sage-react/lib/List/List.story.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { selectArgs } from '../story-support/helpers'; import { List } from './List'; import { sampleItems, @@ -8,7 +9,11 @@ import { export default { title: 'Sage/List', component: List, - argTypes: {}, + argTypes: { + ...selectArgs({ + dragHandleType: List.DRAG_HANDLE_TYPES, + }), + }, args: { items: [], }, @@ -28,7 +33,8 @@ export const SortableList = () => { ); }; @@ -37,9 +43,11 @@ export const FullyDraggableSortableList = () => { const [items, setItems] = React.useState(sampleItems); return ( ); }; diff --git a/packages/sage-react/lib/List/configs.js b/packages/sage-react/lib/List/configs.js new file mode 100644 index 0000000000..f5d59bf5d3 --- /dev/null +++ b/packages/sage-react/lib/List/configs.js @@ -0,0 +1,4 @@ +export const LIST_DRAG_HANDLE_TYPES = { + DEFAULT: 'default', + ROW: 'row', +}; diff --git a/packages/sage-system/lib/list.js b/packages/sage-system/lib/list.js index 0cdbe014cf..2c25f50d77 100644 --- a/packages/sage-system/lib/list.js +++ b/packages/sage-system/lib/list.js @@ -8,38 +8,25 @@ Sage.sortableList = (function() { // ================================================== const DRAGGING_CLASSNAME = 'sage-list--sortable-dragging'; - const SELECTOR_CONFIGS = 'data-js-list-sortable-configs'; const SELECTOR_CONTAINER = 'data-js-list-sortable'; + const SELECTOR_DRAGGABLE_BY_ROW = 'sage-list--draggable-by-row'; const SELECTOR_ITEM_UPDATE_URL = 'data-js-list-sortable-update-url'; - const DEFAULT_CONFIGS = { - dragClass: 'sage-list__item--sortable-drag', - ghostClass: 'sage-list__item--sortable-ghost', - chosenClass: 'sage-list__item--sortable-active', - handle: '.sage-list__item-sortable-handle', - forceFallback: true, // NOTE: This is added because Safari 13+ has a draggable api bug https://github.com/SortableJS/Sortable/issues/1571 - }; // ================================================== // Functions // ================================================== - function init(el) { + const init = (el) => { let resourceName = el.getAttribute(SELECTOR_CONTAINER); if (!resourceName) return console.error(`Sage Sortable requires a resource name \n\n EXAMPLE: \n [${SELECTOR_CONTAINER}="resourceName"]`); - let sortableConfigs = JSON.parse(el.getAttribute(SELECTOR_CONFIGS)); - if (sortableConfigs) { - sortableConfigs = { - ...DEFAULT_CONFIGS, - ...sortableConfigs, - }; - } else { - sortableConfigs = { ...DEFAULT_CONFIGS }; - } - Sortable.create(el, { - ...sortableConfigs, + chosenClass: 'sage-list__item--sortable-active', + dragClass: 'sage-list__item--sortable-drag', + forceFallback: true, // NOTE: This is added because Safari 13+ has a draggable api bug https://github.com/SortableJS/Sortable/issues/1571 + ghostClass: 'sage-list__item--sortable-ghost', + handle: el.classList.contains(SELECTOR_DRAGGABLE_BY_ROW) ? false : '.sage-list__item-sortable-handle', onStart: function(evt) { evt.srcElement.classList.add(DRAGGING_CLASSNAME); }, @@ -58,18 +45,17 @@ Sage.sortableList = (function() { params.append(`${resourceName}[sort_position]`, evt.newIndex); Sage.util.ajaxRequestWithJsInjection('POST', updateUrl, params); - } + }, }); - } + }; - function unbind(el) { + const unbind = (el) => { let sortableInstance = Sortable.get(el); sortableInstance.destroy(); - } + }; return { - init: init, - unbind: unbind - } - + init, + unbind, + }; })(); From d677c8aee713dd2256c7bca33027b051ab35d42a Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Mon, 22 Aug 2022 16:38:19 -0400 Subject: [PATCH 13/15] chore: lint --- packages/sage-assets/lib/stylesheets/components/_list.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sage-assets/lib/stylesheets/components/_list.scss b/packages/sage-assets/lib/stylesheets/components/_list.scss index 5c08f6a1bb..335225cfc6 100644 --- a/packages/sage-assets/lib/stylesheets/components/_list.scss +++ b/packages/sage-assets/lib/stylesheets/components/_list.scss @@ -67,8 +67,8 @@ display: none; .sage-list--sortable & { - width: auto; display: initial; + width: auto; } &:hover { From 14c6be06996b77fc2a40304771534b80d3a03834 Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Tue, 23 Aug 2022 09:54:03 -0400 Subject: [PATCH 14/15] chore(list): clean up from feedback --- .../examples/components/list/_preview.html.erb | 6 +++--- .../app/views/sage_components/_sage_list.html.erb | 9 ++++++--- packages/sage-react/lib/List/List.jsx | 15 +++++++++------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb index d18d106e22..fd9460c9f7 100644 --- a/docs/app/views/examples/components/list/_preview.html.erb +++ b/docs/app/views/examples/components/list/_preview.html.erb @@ -88,10 +88,10 @@ that will be called after the user finishes sorting an item. } %>

    Fully draggable row

    -<%= md(" +<%= md(' By default only the drag handle is active for dragging/sorting a row. -However, `draggable_anywhere` can be set to `true` in order to allow the whole row to be draggable instead. -", use_sage_type: true) %> +However, `drag_handle_type` can be set to `"row"` in order to allow the whole row to be draggable instead. +', use_sage_type: true) %> <%= sage_component SageList, { sortable: true, diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index 1739f5e4c5..4cbe760078 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -1,13 +1,16 @@ <% -tag = component.tag.present? ? component.tag : "ul" -drag_handle_type = component.tag.present? ? component.tag : "default" +tag = component.sortable ? "ol" : "ul" +if component.tag.present? + tag = component.tag +end +drag_handle_type = component.drag_handle_type.present? ? component.drag_handle_type : "default" %> <<%= tag %> class=" sage-list <%= component.generated_css_classes %> <%= "sage-list--sortable" if component.sortable %> - <%= "sage-list--draggable-by-row" if component.drag_handle_type == "row" %> + <%= "sage-list--draggable-by-row" if drag_handle_type == "row" %> " <%= "data-js-list-sortable=#{component.sortable_resource}" if component.sortable_resource.present? %> <%= component.generated_html_attributes.html_safe %> diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index 65ae783988..2630729317 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -44,7 +44,10 @@ export const List = ({ ); }; - const Tag = tag; + let Tag = sortable ? 'ol' : 'ul'; + if (tag) { + Tag = tag; + } return sortable ? ( null, dragHandleType: List.DRAG_HANDLE_TYPES.DEFAULT, - onEnd: null, - onStart: null, - setList: null, + onEnd: () => null, + onStart: () => null, + setList: () => [], sortable: false, - tag: 'ul', + tag: null, }; List.propTypes = { From 62c7bf13920523e69a30fea98c68da0055a6100e Mon Sep 17 00:00:00 2001 From: Phil Schanely Date: Tue, 23 Aug 2022 11:03:32 -0400 Subject: [PATCH 15/15] chore: adjust from feedback --- .../app/views/sage_components/_sage_list.html.erb | 4 +--- packages/sage-react/lib/List/List.jsx | 14 ++++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb index 4cbe760078..92ed7ad808 100644 --- a/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb +++ b/docs/lib/sage_rails/app/views/sage_components/_sage_list.html.erb @@ -1,8 +1,6 @@ <% tag = component.sortable ? "ol" : "ul" -if component.tag.present? - tag = component.tag -end +tag = component.tag || tag drag_handle_type = component.drag_handle_type.present? ? component.drag_handle_type : "default" %> <<%= tag %> diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx index 2630729317..0efb6cf2d9 100644 --- a/packages/sage-react/lib/List/List.jsx +++ b/packages/sage-react/lib/List/List.jsx @@ -45,9 +45,7 @@ export const List = ({ }; let Tag = sortable ? 'ol' : 'ul'; - if (tag) { - Tag = tag; - } + Tag = tag || Tag; return sortable ? ( {renderItems()} @@ -94,11 +92,11 @@ List.defaultProps = { children: null, className: null, items: [], - itemRenderer: () => null, + itemRenderer: () => {}, dragHandleType: List.DRAG_HANDLE_TYPES.DEFAULT, - onEnd: () => null, - onStart: () => null, - setList: () => [], + onEnd: () => {}, + onStart: () => {}, + setList: () => {}, sortable: false, tag: null, };