diff --git a/docs/app/views/examples/components/list/_preview.html.erb b/docs/app/views/examples/components/list/_preview.html.erb
index a1aa0932bd..fd9460c9f7 100644
--- a/docs/app/views/examples/components/list/_preview.html.erb
+++ b/docs/app/views/examples/components/list/_preview.html.erb
@@ -55,41 +55,76 @@ sample_products = [
<%= md("**SageListItem**") %>
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..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,15 +143,15 @@ 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: [:optional, NilClass, TrueClass],
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 7c7a2d9b66..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,11 +1,14 @@
<%
-tag = component.tag.present? ? component.tag : "ul"
+tag = component.sortable ? "ol" : "ul"
+tag = component.tag || tag
+drag_handle_type = component.drag_handle_type.present? ? component.drag_handle_type : "default"
%>
<<%= tag %>
class="
sage-list
<%= component.generated_css_classes %>
- <%= "sage-list--hide-first-border" if component.hide_first_border %>
+ <%= "sage-list--sortable" if component.sortable %>
+ <%= "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 %>
@@ -15,6 +18,5 @@ tag = component.tag.present? ? component.tag : "ul"
<%= sage_component SageListItem, item_configs %>
<% end %>
<% end %>
-
<%= component.content %>
<%= tag %>>
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..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" } %>
-
- <% 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 a18ea70d21..335225cfc6 100644
--- a/packages/sage-assets/lib/stylesheets/components/_list.scss
+++ b/packages/sage-assets/lib/stylesheets/components/_list.scss
@@ -13,45 +13,73 @@
.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);
list-style: none;
- border-top: sage-border(default);
+ border-radius: sage-border(radius-medium);
- &:first-child {
- .sage-list--hide-first-border & {
- border-top: 0;
- }
+ &:hover {
+ background-color: sage-color(grey, 200);
+ }
+
+ .sage-list--draggable-by-row & {
+ cursor: grab;
}
}
-.sage-list__item--sortable {
- cursor: grab;
+.sage-list__item--sortable-active {
+ background-color: sage-color(white);
- &.sage-list__item--sortable-active:active,
- &:active {
+ .sage-list--draggable-by-row & {
cursor: grabbing;
}
}
+.sage-list__item--sortable-drag {
+ background-color: sage-color(white);
+ box-shadow: sage-shadow(lg);
+ // 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 {
- 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 {
+.sage-list__item-more-actions {
width: auto;
+
+ .sage-list__item--sortable-ghost & {
+ opacity: 0;
+ }
}
.sage-list__item-sortable-handle {
- color: sage-color(charcoal, 100);
+ display: none;
+
+ .sage-list--sortable & {
+ display: initial;
+ width: auto;
+ }
+
+ &:hover {
+ cursor: grab;
+ }
+
+ .sage-list__item--sortable-active & {
+ cursor: grabbing;
+ }
- .sage-list__item:hover & {
- color: sage-color(charcoal, 500);
+ .sage-list__item--sortable-ghost & {
+ opacity: 0;
}
}
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);
diff --git a/packages/sage-react/lib/List/List.jsx b/packages/sage-react/lib/List/List.jsx
index 9dba7448f8..0efb6cf2d9 100644
--- a/packages/sage-react/lib/List/List.jsx
+++ b/packages/sage-react/lib/List/List.jsx
@@ -3,25 +3,31 @@ 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,
- hideFirstBorder,
+ dragHandleType,
items,
itemRenderer,
- sortableConfigs,
+ onEnd,
+ onStart,
+ setList,
+ sortable,
tag,
}) => {
const classNames = classnames(
'sage-list',
className,
{
- 'sage-list--hide-first-border': hideFirstBorder,
+ 'sage-list--sortable': sortable,
+ 'sage-list--draggable-by-row': dragHandleType === LIST_DRAG_HANDLE_TYPES.ROW,
}
);
+ const draggingClassname = 'sage-list--sortable-dragging';
+
const renderItems = () => {
if (children) {
return children;
@@ -30,7 +36,7 @@ export const List = ({
return (
<>
{items.map(({ id, ...rest }) => (
-
+
{itemRenderer && itemRenderer({ id, ...rest })}
))}
@@ -38,17 +44,37 @@ export const List = ({
);
};
- const Tag = tag;
+ let Tag = sortable ? 'ol' : 'ul';
+ Tag = tag || Tag;
- 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()}
@@ -60,42 +86,30 @@ export const List = ({
};
List.Item = ListItem;
+List.DRAG_HANDLE_TYPES = LIST_DRAG_HANDLE_TYPES;
List.defaultProps = {
children: null,
className: null,
- hideFirstBorder: false,
items: [],
- itemRenderer: null,
- sortableConfigs: null,
- tag: 'ul',
+ itemRenderer: () => {},
+ dragHandleType: List.DRAG_HANDLE_TYPES.DEFAULT,
+ onEnd: () => {},
+ onStart: () => {},
+ setList: () => {},
+ sortable: false,
+ tag: null,
};
List.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
- hideFirstBorder: PropTypes.bool,
- 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({
- onEnd: 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
- }),
+ 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 015318a6c3..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,21 @@ 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 d01bae2e13..5335707b50 100644
--- a/packages/sage-react/lib/List/ListItem.jsx
+++ b/packages/sage-react/lib/List/ListItem.jsx
@@ -23,7 +23,7 @@ export const ListItem = ({
{sortable && (
-
+
)}
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 a0240b7118..2c25f50d77 100644
--- a/packages/sage-system/lib/list.js
+++ b/packages/sage-system/lib/list.js
@@ -1,3 +1,5 @@
+// NOTE: Uses SortableJS
+// https://github.com/SortableJS/Sortable
import Sortable from 'sortablejs/modular/sortable.core.esm.js';
Sage.sortableList = (function() {
@@ -5,25 +7,32 @@ Sage.sortableList = (function() {
// Variables
// ==================================================
+ const DRAGGING_CLASSNAME = 'sage-list--sortable-dragging';
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 SETTINGS = {
- ghostClass: 'sage-list__item--sortable-ghost',
- chosenClass: 'sage-list__item--sortable-active',
- };
// ==================================================
// 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"]`);
Sortable.create(el, {
- ...SETTINGS,
+ 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);
+ },
onEnd: function (evt) {
+ evt.srcElement.classList.remove(DRAGGING_CLASSNAME);
+
let updateUrl = evt.item.getAttribute(SELECTOR_ITEM_UPDATE_URL)
// Check if the sorted Item:
@@ -35,19 +44,18 @@ Sage.sortableList = (function() {
params.append('_method', 'PUT');
params.append(`${resourceName}[sort_position]`, evt.newIndex);
- Sage.util.ajaxRequestWithJsInjection('POST', updateUrl, params)
- }
+ 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,
+ };
})();
|