diff --git a/plugins/tidyup/src/App.css b/plugins/tidyup/src/App.css index 813a1a8c2..7c8fc2355 100644 --- a/plugins/tidyup/src/App.css +++ b/plugins/tidyup/src/App.css @@ -1,5 +1,15 @@ /* Your Plugin CSS */ +[data-framer-theme="light"] { + --preview-frame-bg: #fff; + --preview-border: rgba(0, 0, 0, 0.05); +} + +[data-framer-theme="dark"] { + --preview-frame-bg: rgba(187, 187, 187, 0.2); + --preview-border: rgba(255, 255, 255, 0.07); +} + main { display: flex; flex-direction: column; @@ -26,10 +36,6 @@ main { width: 100%; } -[data-framer-theme="light"] .preview-frame { - background: white; -} - -[data-framer-theme="dark"] .preview-frame { - background: rgba(187, 187, 187, 0.2); +.preview-frame { + background: var(--preview-frame-bg); } diff --git a/plugins/tidyup/src/App.tsx b/plugins/tidyup/src/App.tsx index c70e1cf11..1ac41d278 100644 --- a/plugins/tidyup/src/App.tsx +++ b/plugins/tidyup/src/App.tsx @@ -6,6 +6,7 @@ import { isDesignPageNode, isVectorSetNode, isWebPageNode, + supportsName, supportsPins, useIsAllowedTo, } from "framer-plugin" @@ -67,6 +68,10 @@ type RectByGroundNodeId = Record const noRectByGroundNodeId: RectByGroundNodeId = {} +type NameByGroundNodeId = Record + +const noNameByGroundNodeId: NameByGroundNodeId = {} + function isDeepEqual(a: unknown, b: unknown): boolean { if (a === b) return true if (isArray(a) && isArray(b)) { @@ -82,15 +87,17 @@ function isDeepEqual(a: unknown, b: unknown): boolean { return false } -function useGroundNodeRects() { +function useGroundNodes() { const root = useCanvasRoot() const selection = useSelection() const [rects, setRects] = useState(noRectByGroundNodeId) + const [names, setNames] = useState(noNameByGroundNodeId) useEffect(() => { if (!root) { setRects(noRectByGroundNodeId) + setNames(noNameByGroundNodeId) return } @@ -121,14 +128,17 @@ function useGroundNodeRects() { } const result: RectByGroundNodeId = {} + const resultNames: NameByGroundNodeId = {} for (const groundNode of groundNodes) { const rect = await groundNode.getRect() if (!active) return result[groundNode.id] = rect + resultNames[groundNode.id] = supportsName(groundNode) ? groundNode.name : null } setRects(current => (isDeepEqual(current, result) ? current : result)) + setNames(current => (isDeepEqual(current, resultNames) ? current : resultNames)) } void getRects() @@ -138,7 +148,7 @@ function useGroundNodeRects() { } }, [root, selection]) - return rects + return { rects, names } } interface RectWithId extends Rect { @@ -147,6 +157,7 @@ interface RectWithId extends Rect { function getSortedRects( rects: RectByGroundNodeId, + names: NameByGroundNodeId, layout: Layout, sorting: Sorting, columnCount: number, @@ -181,6 +192,13 @@ function getSortedRects( return areaB - areaA }) break + case "name": + result.sort((a, b) => { + const nameA = names[a.id] ?? "" + const nameB = names[b.id] ?? "" + return nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: "base" }) + }) + break default: assertNever(sorting) } @@ -198,6 +216,17 @@ function getSortedRects( break } + case "vertical": { + let currentY = 0 + + for (const rect of result) { + rect.x = 0 + rect.y = currentY + currentY += rect.height + rowGap + } + + break + } case "grid": { const maxSize = getMaxSize(result) result.forEach((rect, index) => { @@ -330,11 +359,24 @@ function getBoundingBox(rects: Rect[]): Rect { } } -const allLayouts = ["horizontal", "grid", "random"] as const +const allLayouts = ["horizontal", "vertical", "grid", "random"] as const +const layoutTitles = { + horizontal: "Horizontal", + vertical: "Vertical", + grid: "Grid", + random: "Random", +} as const const LayoutSchema = v.union(allLayouts.map(layout => v.literal(layout))) type Layout = v.InferOutput -const allSortings = ["position", "width", "height", "area"] as const +const allSortings = ["position", "width", "height", "area", "name"] as const +const sortingTitles = { + position: "Position", + width: "Width", + height: "Height", + area: "Area", + name: "Name (A → Z)", +} as const const SortingSchema = v.union(allSortings.map(sorting => v.literal(sorting))) type Sorting = v.InferOutput @@ -343,17 +385,17 @@ const GapSchema = v.pipe(v.number(), v.integer(), v.minValue(0)) export function App() { const isAllowedToSetAttributes = useIsAllowedTo("setAttributes") - const rects = useGroundNodeRects() + const { rects, names } = useGroundNodes() const isEnabled = Object.keys(rects).length > 1 const [transitionEnabled, setTransitionEnabled] = useState(false) - const [layout, setLayout] = useLocaleStorageState("layout", "horizontal", LayoutSchema) - const [sorting, setSorting] = useLocaleStorageState("sorting", "position", SortingSchema) - const [columnCount, setColumnCount] = useLocaleStorageState("columnCount", 3, ColumnCountSchema) - const [columnGap, setColumnGap] = useLocaleStorageState("columnGap", 100, GapSchema) - const [rowGap, setRowGap] = useLocaleStorageState("rowGap", 100, GapSchema) + const [layout, setLayout] = useLocalStorageState("layout", "horizontal", LayoutSchema) + const [sorting, setSorting] = useLocalStorageState("sorting", "position", SortingSchema) + const [columnCount, setColumnCount] = useLocalStorageState("columnCount", 3, ColumnCountSchema) + const [columnGap, setColumnGap] = useLocalStorageState("columnGap", 100, GapSchema) + const [rowGap, setRowGap] = useLocalStorageState("rowGap", 100, GapSchema) const previewElement = useRef(null) const previewSize = useElementSize({ @@ -367,8 +409,8 @@ export function App() { const [randomKey, randomize] = useReducer((state: number) => state + 1, 0) const sortedRects = useMemo( - () => getSortedRects(rects, layout, sorting, columnCount, columnGap, rowGap), - [rects, layout, sorting, columnCount, columnGap, rowGap, randomKey] + () => getSortedRects(rects, names, layout, sorting, columnCount, columnGap, rowGap), + [rects, names, layout, sorting, columnCount, columnGap, rowGap, randomKey] ) const boundingBox = getBoundingBox(sortedRects) const [previewScale, previewOffset] = getPreviewScaleAndOffset(boundingBox, previewSize) @@ -386,6 +428,7 @@ export function App() { width: "100%", borderRadius: 10, backgroundColor: "var(--framer-color-bg-tertiary)", + boxShadow: "inset 0 0 0 1px var(--preview-border)", overflow: "hidden", cursor: layout === "random" ? "pointer" : "default", }} @@ -468,7 +511,7 @@ export function App() { > {allLayouts.map(layout => ( ))} @@ -485,7 +528,7 @@ export function App() { > {allSortings.map(sorting => ( ))} @@ -503,20 +546,22 @@ export function App() { /> )} - - { - assert(v.is(GapSchema, value)) - setColumnGap(value) - setTransitionEnabled(true) - }} - /> - - {layout === "grid" && ( - + {layout !== "vertical" && ( + + { + assert(v.is(GapSchema, value)) + setColumnGap(value) + setTransitionEnabled(true) + }} + /> + + )} + {(layout === "grid" || layout === "vertical") && ( + { return typeof value === "object" && value !== null && !isArray(value) } -function useLocaleStorageState>>( +function useLocalStorageState>>( key: string, defaultValue: v.InferOutput, schema: TSchema