From cad95b887c6b06a41a2bacf28792fd4dbc808d72 Mon Sep 17 00:00:00 2001 From: Xavier Mirabelli-Montan Date: Wed, 28 Aug 2024 09:54:12 +0100 Subject: [PATCH] feat: add drag, insert, delete, duplicate & drag permissions, and isDisabled prop to Drawer.Item --- .../app/custom-ui/[...puckPath]/client.tsx | 81 +++++++++++-------- .../components/ActionBar/styles.module.css | 11 ++- .../core/components/ComponentList/index.tsx | 18 ++++- packages/core/components/DragIcon/index.tsx | 4 +- .../components/DragIcon/styles.module.css | 6 +- packages/core/components/Draggable/index.tsx | 1 + .../components/DraggableComponent/index.tsx | 37 +++++++-- packages/core/components/Drawer/index.tsx | 39 +++++---- .../core/components/Drawer/styles.module.css | 11 ++- packages/core/components/DropZone/index.tsx | 11 +++ .../Puck/components/Fields/index.tsx | 18 ++++- packages/core/components/Puck/index.tsx | 2 + packages/core/lib/get-permissions.ts | 8 +- packages/core/lib/use-puck.ts | 13 ++- packages/core/types/Config.tsx | 2 + 15 files changed, 188 insertions(+), 74 deletions(-) diff --git a/apps/demo/app/custom-ui/[...puckPath]/client.tsx b/apps/demo/app/custom-ui/[...puckPath]/client.tsx index c1ce12361..5308d14c9 100644 --- a/apps/demo/app/custom-ui/[...puckPath]/client.tsx +++ b/apps/demo/app/custom-ui/[...puckPath]/client.tsx @@ -301,11 +301,53 @@ const CustomActionBar = ({ children, label }) => { ); }; +const CustomDrawer = () => { + const { getPermissions } = usePuck(); + + return ( + +
+ {Object.keys(config.components).map((componentKey, componentIndex) => { + const canInsert = getPermissions({ + type: componentKey, + }).insert; + return ( + + {({ children }) => ( +
+ {children} +
+ )} +
+ ); + })} +
+
+ ); +}; + export function Client({ path, isEdit }: { path: string; isEdit: boolean }) { const { data, resolvedData, key } = useDemoData({ path, isEdit, }); + const configOverride: UserConfig = { ...config, components: { @@ -314,6 +356,10 @@ export function Client({ path, isEdit }: { path: string; isEdit: boolean }) { ...config.components.Hero, permissions: { debug: false, + drag: false, + delete: false, + duplicate: false, + insert: false, }, }, }, @@ -335,40 +381,7 @@ export function Client({ path, isEdit }: { path: string; isEdit: boolean }) { actionBar: ({ children, label }) => ( {children} ), - components: () => { - return ( - -
- {Object.keys(config.components).map( - (componentKey, componentIndex) => ( - - {({ children }) => ( -
- {children} -
- )} -
- ) - )} -
-
- ); - }, + components: () => , puck: () => , }} /> diff --git a/packages/core/components/ActionBar/styles.module.css b/packages/core/components/ActionBar/styles.module.css index 964371ab7..139af4cd7 100644 --- a/packages/core/components/ActionBar/styles.module.css +++ b/packages/core/components/ActionBar/styles.module.css @@ -9,6 +9,7 @@ color: var(--puck-color-white); font-family: var(--puck-font-family); gap: 4px; + min-height: 26px; } .ActionBarComponent-actionsLabel { @@ -19,13 +20,17 @@ justify-content: center; align-items: center; padding-left: 8px; - padding-right: 16px; - margin-right: 8px; - border-right: 0.5px solid var(--puck-color-grey-05); /* Fractional value required due to scaling */ + padding-right: 8px; text-overflow: ellipsis; white-space: nowrap; } +.ActionBarComponent-action:first-of-type { + border-left: 0.5px solid var(--puck-color-grey-05); /* Fractional value required due to scaling */ + border-radius: 0; + padding-left: 16px; +} + .ActionBarComponent-action { background: transparent; border: none; diff --git a/packages/core/components/ComponentList/index.tsx b/packages/core/components/ComponentList/index.tsx index b651c1442..c0019e9d4 100644 --- a/packages/core/components/ComponentList/index.tsx +++ b/packages/core/components/ComponentList/index.tsx @@ -5,6 +5,8 @@ import { useAppContext } from "../Puck/context"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Drawer } from "../Drawer"; +import { getPermissions } from "../../lib/get-permissions"; + const getClassName = getClassNameFactory("ComponentList", styles); const ComponentListItem = ({ @@ -16,9 +18,21 @@ const ComponentListItem = ({ label?: string; index: number; }) => { - const { overrides } = useAppContext(); + const { overrides, config, globalPermissions } = useAppContext(); + + const canInsert = getPermissions({ + type: name, + config, + globalPermissions: globalPermissions || {}, + }).insert; + return ( - + {overrides.componentItem} ); diff --git a/packages/core/components/DragIcon/index.tsx b/packages/core/components/DragIcon/index.tsx index 501ecf4f9..c4247e9e0 100644 --- a/packages/core/components/DragIcon/index.tsx +++ b/packages/core/components/DragIcon/index.tsx @@ -4,8 +4,8 @@ import styles from "./styles.module.css"; const getClassName = getClassNameFactory("DragIcon", styles); -export const DragIcon = () => ( -
+export const DragIcon = ({ isDragDisabled }: { isDragDisabled?: boolean }) => ( +
diff --git a/packages/core/components/DragIcon/styles.module.css b/packages/core/components/DragIcon/styles.module.css index 1c276239c..fec0b6c57 100644 --- a/packages/core/components/DragIcon/styles.module.css +++ b/packages/core/components/DragIcon/styles.module.css @@ -5,8 +5,12 @@ border-radius: 4px; } +.DragIcon--disabled { + cursor: no-drop; +} + @media (hover: hover) and (pointer: fine) { - .DragIcon:hover { + .DragIcon:not(.DragIcon--disabled):hover { color: var(--puck-color-azure-05); background-color: var(--puck-color-azure-12); } diff --git a/packages/core/components/Draggable/index.tsx b/packages/core/components/Draggable/index.tsx index ec4788aae..40c34e8b5 100644 --- a/packages/core/components/Draggable/index.tsx +++ b/packages/core/components/Draggable/index.tsx @@ -80,6 +80,7 @@ export const Draggable = ({ snapshot.isDragging || !disableAnimations ? provided.draggableProps.style?.transform : "translate(0px, 0px)", + cursor: isDragDisabled ? "no-drop" : "grab", }} > {children(provided, snapshot)} diff --git a/packages/core/components/DraggableComponent/index.tsx b/packages/core/components/DraggableComponent/index.tsx index 1d71a76a9..9f605ccdd 100644 --- a/packages/core/components/DraggableComponent/index.tsx +++ b/packages/core/components/DraggableComponent/index.tsx @@ -18,6 +18,7 @@ import { Loader } from "../Loader"; import { ActionBar } from "../ActionBar"; import { DefaultOverride } from "../DefaultOverride"; import { useLoadedOverrides } from "../../lib/use-loaded-overrides"; +import { getPermissions } from "../../lib/get-permissions"; const getClassName = getClassNameFactory("DraggableComponent", styles); @@ -82,7 +83,15 @@ export const DraggableComponent = ({ indicativeHover?: boolean; style?: CSSProperties; }) => { - const { zoomConfig, status, overrides, plugins } = useAppContext(); + const { + zoomConfig, + status, + overrides, + plugins, + selectedItem, + config, + globalPermissions, + } = useAppContext(); const isModifierHeld = useModifierHeld("Alt"); const El = status !== "LOADING" ? Draggable : DefaultDraggable; @@ -109,6 +118,14 @@ export const DraggableComponent = ({ [loadedOverrides] ); + const permissions = + selectedItem && + getPermissions({ + selectedItem, + globalPermissions: globalPermissions || {}, + config, + }); + return ( - - - - - - + {permissions && permissions.duplicate && ( + + + + )} + {permissions && permissions.delete && ( + + + + )}
diff --git a/packages/core/components/Drawer/index.tsx b/packages/core/components/Drawer/index.tsx index 8bfd1e834..8d1e8b7c8 100644 --- a/packages/core/components/Drawer/index.tsx +++ b/packages/core/components/Drawer/index.tsx @@ -22,22 +22,27 @@ const DrawerDraggable = ({ children, id, index, + isDragDisabled, }: { children: ReactNode; id: string; index: number; -}) => ( - getClassNameItem()} - > - {() => children} - -); + isDragDisabled?: boolean; +}) => { + return ( + getClassNameItem({ disabled: isDragDisabled })} + > + {() => children} + + ); +}; const DrawerItem = ({ name, @@ -45,12 +50,14 @@ const DrawerItem = ({ id, label, index, + isDragDisabled, }: { name: string; children?: (props: { children: ReactNode; name: string }) => ReactElement; id?: string; label?: string; index: number; + isDragDisabled?: boolean; }) => { const ctx = useContext(drawerContext); @@ -66,13 +73,17 @@ const DrawerItem = ({ ); return ( - +
{label ?? name}
- +
diff --git a/packages/core/components/Drawer/styles.module.css b/packages/core/components/Drawer/styles.module.css index d01a82e4e..92ed4e2dc 100644 --- a/packages/core/components/Drawer/styles.module.css +++ b/packages/core/components/Drawer/styles.module.css @@ -2,6 +2,12 @@ font-family: var(--puck-font-family); } +.DrawerItem--disabled { + background: var(--puck-color-grey-11); + color: var(--puck-color-grey-05); + cursor: not-allowed; /** Move this out of inline styles */ +} + .DrawerItem-default .DrawerItem-draggableWrapper { /* padding required here to prevent bug when cancelling a drag) */ padding-bottom: 12px; @@ -20,7 +26,6 @@ font-size: var(--puck-font-size-xxs); justify-content: space-between; align-items: center; - cursor: grab; transition: background-color 50ms ease-in, color 50ms ease-in; } @@ -37,7 +42,9 @@ } @media (hover: hover) and (pointer: fine) { - .Drawer:not(.Drawer--isDraggingFrom) .DrawerItem-draggable:hover { + .Drawer:not(.Drawer--isDraggingFrom) + .DrawerItem:not(.DrawerItem--disabled) + .DrawerItem-draggable:hover { background-color: var(--puck-color-azure-12); color: var(--puck-color-azure-04); transition: none; diff --git a/packages/core/components/DropZone/index.tsx b/packages/core/components/DropZone/index.tsx index 9bbaeefdb..779f4f78f 100644 --- a/packages/core/components/DropZone/index.tsx +++ b/packages/core/components/DropZone/index.tsx @@ -11,6 +11,7 @@ import { getZoneId } from "../../lib/get-zone-id"; import { useAppContext } from "../Puck/context"; import { DropZoneProps } from "./types"; import { ComponentConfig, PuckContext } from "../../types/Config"; +import { getPermissions } from "../../lib/get-permissions"; const getClassName = getClassNameFactory("DropZone", styles); @@ -237,6 +238,15 @@ function DropZoneEdit({ zone, allow, disallow, style }: DropZoneProps) { const label = componentConfig?.["label"] ?? item.type.toString(); + const canDrag = getPermissions({ + selectedItem: getItem( + { index: i, zone: zoneCompound }, + appContext.state.data + ), + config: appContext.config, + globalPermissions: appContext.globalPermissions || {}, + }).drag; + return (
{ resolveData, componentState, overrides, + globalPermissions, } = useAppContext(); const { data, ui } = state; const { itemSelector } = ui; @@ -247,6 +256,11 @@ export const Fields = () => { if (selectedItem && itemSelector) { const { readOnly = {} } = selectedItem; + const { edit } = getPermissions({ + selectedItem, + config, + globalPermissions: globalPermissions || {}, + }); return ( { field={field} name={fieldName} id={`${selectedItem.props.id}_${fieldName}`} - readOnly={readOnly[fieldName]} + readOnly={!edit || readOnly[fieldName]} value={selectedItem.props[fieldName]} onChange={onChange} /> diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index 26b8c80d2..e820f8fd7 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -402,6 +402,8 @@ export function Puck({ delete: true, drag: true, duplicate: true, + insert: true, + edit: true, ...permissions, }, }} diff --git a/packages/core/lib/get-permissions.ts b/packages/core/lib/get-permissions.ts index 0d69159c1..37be46ce1 100644 --- a/packages/core/lib/get-permissions.ts +++ b/packages/core/lib/get-permissions.ts @@ -7,14 +7,16 @@ import { export const getPermissions = ({ selectedItem, + type, globalPermissions, config, }: { - selectedItem: ComponentData | undefined; + selectedItem?: ComponentData | undefined; + type?: keyof DefaultComponentProps; globalPermissions: Partial; config: Config; }) => { - const componentType = (selectedItem && selectedItem.type) || ""; + const componentType = type || (selectedItem && selectedItem.type) || ""; let componentPermissions = getInitialPermissions({ componentType, config, @@ -35,6 +37,6 @@ export const getInitialPermissions = ({ }) => { return { ...globalPermissions, - ...config.components[componentType].permissions, + ...config.components[componentType]?.permissions, }; }; diff --git a/packages/core/lib/use-puck.ts b/packages/core/lib/use-puck.ts index bd19c8e20..d2d42f71d 100644 --- a/packages/core/lib/use-puck.ts +++ b/packages/core/lib/use-puck.ts @@ -1,5 +1,5 @@ import { useAppContext } from "../components/Puck/context"; -import { ComponentData } from "../types/Config"; +import { ComponentData, DefaultComponentProps } from "../types/Config"; import { getPermissions } from "./get-permissions"; export const usePuck = () => { @@ -16,9 +16,16 @@ export const usePuck = () => { appState, config, dispatch, - getPermissions: (selectedItem?: ComponentData) => { + getPermissions: ({ + item, + type, + }: { + item?: ComponentData; + type?: keyof DefaultComponentProps; + } = {}) => { return getPermissions({ - selectedItem: selectedItem || currentItem, + selectedItem: item || currentItem, + type, globalPermissions: globalPermissions || {}, config, }); diff --git a/packages/core/types/Config.tsx b/packages/core/types/Config.tsx index 405bd3ff9..51f509d5c 100644 --- a/packages/core/types/Config.tsx +++ b/packages/core/types/Config.tsx @@ -178,4 +178,6 @@ export type Permissions = { drag: boolean; duplicate: boolean; delete: boolean; + edit: boolean; + insert: boolean; } & Record;