diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e35e36..005bec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). +## [1.7.0] - 2024-10-09 +- Updated dependencies. +- `Repeater`, `Draggable`, `DraggableList` and `ItemCollection` will now behave better when `undefined` is passed as value. +- Fixed overlap issues with `Select` components. +- Improved `ColorPicker` grouping logic and made grouped menu more compact. +- Added `MiniResponsive` for cases you need to use a `Responsive` in a limited space. Works best with `OptionSelect` or other `Button`-like components! +- Fixed `Spacer` not applying `className` in some cases. +- Tweaked `MediaPlaceholder` colors a bit. +- `MediaPlaceholder` can now have any child items passed. They are rendered below the label and icon. + ## [1.6.1] - 2024-09-30 - Fixed `clearable` not working in Select components. - Fixed `Tabs` tab alignment when in horizontal mode. @@ -189,6 +199,7 @@ This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a - Initial release [Unreleased]: https://github.com/infinum/eightshift-ui-components/compare/master...HEAD +[1.7.0]: https://github.com/infinum/eightshift-ui-components/compare/1.6.1...1.7.0 [1.6.1]: https://github.com/infinum/eightshift-ui-components/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/infinum/eightshift-ui-components/compare/1.5.1...1.6.0 [1.5.1]: https://github.com/infinum/eightshift-ui-components/compare/1.5.0...1.5.1 diff --git a/lib/components/color-pickers/color-picker.jsx b/lib/components/color-pickers/color-picker.jsx index ec478ba..a23a6d8 100644 --- a/lib/components/color-pickers/color-picker.jsx +++ b/lib/components/color-pickers/color-picker.jsx @@ -4,7 +4,6 @@ import { ColorSwatch } from './color-swatch'; import { RichLabel } from '../rich-label/rich-label'; import { BaseControl } from '../base-control/base-control'; import { clsx } from 'clsx/lite'; - import { icons } from '../../icons/icons'; /** @@ -259,7 +258,7 @@ export const ColorPicker = (props) => { return ( {colors.map((color) => ( { - return items.map((item, i) => ({ + return items?.map((item, i) => ({ ...item, id: item?.id ?? `${itemIdBase}-${i}`, })); @@ -81,7 +81,11 @@ export const DraggableList = (props) => { ...rest } = props; - const items = fixIds(rawItems, itemIdBase); + if (typeof rawItems === 'undefined' || rawItems === null || !Array.isArray(rawItems)) { + console.warn(__("DraggableList: 'items' are not an array or are undefined!", 'eightshift-ui-components')); + } + + const items = fixIds(rawItems ?? [], itemIdBase); if (hidden) { return null; diff --git a/lib/components/draggable/draggable.jsx b/lib/components/draggable/draggable.jsx index 624c628..356f79e 100644 --- a/lib/components/draggable/draggable.jsx +++ b/lib/components/draggable/draggable.jsx @@ -8,10 +8,12 @@ import { DragDropProvider } from '@dnd-kit/react'; import { move } from '@dnd-kit/helpers'; const fixIds = (items, itemIdBase) => { - return items.map((item, i) => ({ - ...item, - id: item?.id ?? `${itemIdBase}-${i}`, - })); + return ( + items?.map((item, i) => ({ + ...item, + id: item?.id ?? `${itemIdBase}-${i}`, + })) ?? [] + ); }; const SortableItem = ({ id, index, disabled, children, axis }) => { @@ -93,7 +95,11 @@ export const Draggable = (props) => { ...rest } = props; - const [items, setItems] = useState(fixIds(rawItems)); + if (typeof rawItems === 'undefined' || rawItems === null || !Array.isArray(rawItems)) { + console.warn(__("Draggable: 'items' are not an array or are undefined!", 'eightshift-ui-components')); + } + + const [items, setItems] = useState(fixIds(rawItems ?? [])); // Ensure the internal state is updated if items are updated externally. useEffect(() => { diff --git a/lib/components/index.js b/lib/components/index.js index ecf616c..d3424f0 100644 --- a/lib/components/index.js +++ b/lib/components/index.js @@ -26,6 +26,7 @@ export { ListBox } from './list-box/list-box'; export { MatrixAlign } from './matrix-align/matrix-align'; export { MediaPlaceholder } from './placeholders/media-placeholder'; export { Menu, MenuItem, MenuSection, MenuSeparator, SubMenuItem } from './menu/menu'; +export { MiniResponsive } from './responsive/mini-responsive'; export { Notice } from './notice/notice'; export { NumberPicker } from './number-picker/number-picker'; export { Popover, TriggeredPopover } from './popover/popover'; diff --git a/lib/components/item-collection/item-collection.jsx b/lib/components/item-collection/item-collection.jsx index ae8b045..66f65e2 100644 --- a/lib/components/item-collection/item-collection.jsx +++ b/lib/components/item-collection/item-collection.jsx @@ -1,4 +1,5 @@ import { Fragment } from 'react'; +import { __ } from '@wordpress/i18n'; /** * A simple component to manage a collection of items. @@ -29,12 +30,18 @@ import { Fragment } from 'react'; * @preserve */ export const ItemCollection = (props) => { - const { children, items, onChange, hidden } = props; + const { children, items: rawItems, onChange, hidden } = props; if (hidden) { return null; } + if (typeof rawItems === 'undefined' || rawItems === null || !Array.isArray(rawItems)) { + console.warn(__("ItemCollection: 'items' are not an array or are undefined!", 'eightshift-ui-components')); + } + + const items = rawItems ?? []; + return items.map((item, index) => ( {children({ diff --git a/lib/components/menu/menu.jsx b/lib/components/menu/menu.jsx index 849b7ac..3b95ef6 100644 --- a/lib/components/menu/menu.jsx +++ b/lib/components/menu/menu.jsx @@ -155,7 +155,7 @@ export const MenuSection = (props) => { className={clsx( 'es-uic-space-y-1 es-uic-border-b es-uic-border-b-gray-200 es-uic-pb-1 last:es-uic-border-b-0', label && 'es-uic-pt-2 first:es-uic-pt-1.5', - !label && 'last:es-uic-pb-1', + !label && 'last:es-uic-pb-1 has-[>_:only-child]:es-uic-pb-0', )} > {label &&
{label}
} diff --git a/lib/components/placeholders/media-placeholder.jsx b/lib/components/placeholders/media-placeholder.jsx index 6afe7c1..2f1d851 100644 --- a/lib/components/placeholders/media-placeholder.jsx +++ b/lib/components/placeholders/media-placeholder.jsx @@ -24,15 +24,15 @@ import { icons } from '../../icons/icons'; * @preserve */ export const MediaPlaceholder = (props) => { - const { style = 'default', size = 'default', className, icon, helpText, hidden } = props; + const { style = 'default', size = 'default', className, icon, helpText, children, hidden } = props; if (hidden) { return null; } const styleClassName = { - default: 'es-uic-rounded-lg es-uic-border es-uic-border-gray-300 es-uic-bg-gray-50 es-uic-text-gray-300 es-uic-shadow', - simple: 'es-uic-rounded-lg es-uic-border es-uic-border-gray-300 es-uic-border-dashed es-uic-text-gray-300', + default: 'es-uic-rounded-lg es-uic-border es-uic-border-gray-300 es-uic-bg-gray-50 es-uic-text-gray-400 es-uic-shadow', + simple: 'es-uic-rounded-lg es-uic-border es-uic-border-gray-300 es-uic-border-dashed es-uic-text-gray-400', }; const sizeClassName = { @@ -48,7 +48,7 @@ export const MediaPlaceholder = (props) => { return (
{ >
{icon ?? icons.image}
- {helpText &&
{helpText}
} + {helpText &&
{helpText}
} + + {children &&
{children}
}
); }; diff --git a/lib/components/repeater/repeater.jsx b/lib/components/repeater/repeater.jsx index 8e230fb..9c2a973 100644 --- a/lib/components/repeater/repeater.jsx +++ b/lib/components/repeater/repeater.jsx @@ -9,7 +9,7 @@ import { clsx } from 'clsx/lite'; import { List, arrayMove, arrayRemove } from 'react-movable'; const fixIds = (items, itemIdBase) => { - return items.map((item, i) => ({ + return items?.map((item, i) => ({ ...item, id: item?.id ?? `${itemIdBase}-${i}`, })); @@ -94,7 +94,11 @@ export const Repeater = (props) => { hidden, } = props; - const items = fixIds(rawItems, itemIdBase); + if (typeof rawItems === 'undefined' || rawItems === null || !Array.isArray(rawItems)) { + console.warn(__("Repeater: 'items' are not an array or are undefined!", 'eightshift-ui-components')); + } + + const items = fixIds(rawItems ?? [], itemIdBase); const canDelete = items.length > (minItems ?? 0); const canAdd = items.length < (maxItems ?? Number.MAX_SAFE_INTEGER); diff --git a/lib/components/responsive/mini-responsive.jsx b/lib/components/responsive/mini-responsive.jsx new file mode 100644 index 0000000..5782ad6 --- /dev/null +++ b/lib/components/responsive/mini-responsive.jsx @@ -0,0 +1,552 @@ +import { cloneElement } from 'react'; +import { DecorativeTooltip } from '../tooltip/tooltip'; +import { clsx } from 'clsx/lite'; +import { __, sprintf } from '@wordpress/i18n'; +import { BreakpointPreview } from '../breakpoint-preview/breakpoint-preview'; +import { upperFirst } from '../../utilities'; +import { icons } from '../../icons/icons'; +import { ResponsivePreview } from '../responsive-preview/responsive-preview'; +import { Button, ButtonGroup } from '../button/button'; +import { Spacer } from '../spacer/spacer'; +import { BaseControl } from '../base-control/base-control'; +import { TriggeredPopover } from '../popover/popover'; +import { OptionSelect } from '../option-select/option-select'; +import { Text } from 'react-aria-components'; + +/** + * A compact, inline version of `Responsive`. Allows the user to set different values for different breakpoints. + * + * Inner items should be passed as a render function. + * The following props are passed to the render function: + * - `breakpoint: string` - Name of the current breakpoint. + * - `currentValue: any` - Current value. + * - `handleChange: Function<(attributeName: string, value: any) => void>` - A function to change the value for the breakpoint.. + * - `options: Object` - (Optional) Options list passed to the `ResponsiveLegacy` component. (optional) + * - `isInlineCollapsedView: boolean` - (Optional) `true` if in the main view, with the detail popover closed. + * - `isInlineExpandedView: boolean` - (Optional) `true` if the detail popover is open. + * + * @component + * @param {Object} props - Component props. + * @param {Object} props.value - The current value of the component. + * @param {Function} props.onChange - Function to run when the value changes. `(newValue: Object) => void`. + * @param {JSX.Element} props.icon - The icon of the component. + * @param {string} props.help - The help text of the component. + * @param {string} props.label - The label of the component. + * @param {string} props.subtitle - The subtitle of the component. + * @param {{label: string, value: string}[]} props.options - Options of the attribute the component is linked to. `{ value: string, label: string }[]`. + * @param {string[]} props.breakpoints - Breakpoints to use. + * @param {string[]} [props.desktopFirstBreakpoints] - Breakpoints to use in desktop-first mode. If not provided, the breakpoints will be used in reverse order. + * @param {Object} [props.breakpointData] - Currently used breakpoint data. `{ [breakpoint: string]: number }`. + * @param {Object} [props.breakpointUiData] - Allows overriding breakpoint names and icons. `{ [breakpoint: string]: { label: string, icon: JSX.Element|string } }`. + * @param {boolean} [props.noModeSelect] - If `true`, the mode selection (desktop-first/mobile-first) is hidden. + * @param {boolean} [props.hidden] - If `true`, the component is not rendered. + * @param {'start' | 'center' | 'end' | 'stretch'} [props.innerContentAlign='start'] - Determines inner content alignment + * + * @returns {JSX.Element} The MiniResponsive component. + * + * @example + * + * {({ breakpoint, currentValue, options, handleChange }) => ( + *