From d4948519c6359435989f0666be7833511963a641 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:24:45 -0400 Subject: [PATCH 1/4] Add alphabetical sorting --- plugins/tidyup/src/App.tsx | 60 +++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/plugins/tidyup/src/App.tsx b/plugins/tidyup/src/App.tsx index c70e1cf11..09220e90c 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)) { @@ -87,10 +92,12 @@ function useGroundNodeRects() { 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) } @@ -331,10 +349,22 @@ function getBoundingBox(rects: Rect[]): Rect { } const allLayouts = ["horizontal", "grid", "random"] as const +const layoutTitles = { + horizontal: "Horizontal", + 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 +373,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 } = useGroundNodeRects() 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 +397,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) @@ -468,7 +498,7 @@ export function App() { > {allLayouts.map(layout => ( ))} @@ -485,7 +515,7 @@ export function App() { > {allSortings.map(sorting => ( ))} @@ -575,10 +605,6 @@ function assertNever(condition: never): never { throw Error(`Should never happen: ${String(condition)}`) } -function uppercaseFirstCharacter(value: string) { - return value.charAt(0).toUpperCase() + value.slice(1) -} - function isArray(value: unknown): value is unknown[] { return Array.isArray(value) } @@ -587,7 +613,7 @@ function isObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !isArray(value) } -function useLocaleStorageState>>( +function useLocalStorageState>>( key: string, defaultValue: v.InferOutput, schema: TSchema From acfbbdf7d4c011a3c631cc913bc1b11413eb140d Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:31:48 -0400 Subject: [PATCH 2/4] Vertical layout --- plugins/tidyup/src/App.tsx | 44 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/plugins/tidyup/src/App.tsx b/plugins/tidyup/src/App.tsx index 09220e90c..03cb27bc8 100644 --- a/plugins/tidyup/src/App.tsx +++ b/plugins/tidyup/src/App.tsx @@ -216,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) => { @@ -348,9 +359,10 @@ 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 @@ -533,20 +545,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") && ( + Date: Mon, 15 Jun 2026 18:37:16 -0400 Subject: [PATCH 3/4] Update function name --- plugins/tidyup/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/tidyup/src/App.tsx b/plugins/tidyup/src/App.tsx index 03cb27bc8..63eafb595 100644 --- a/plugins/tidyup/src/App.tsx +++ b/plugins/tidyup/src/App.tsx @@ -87,7 +87,7 @@ function isDeepEqual(a: unknown, b: unknown): boolean { return false } -function useGroundNodeRects() { +function useGroundNodes() { const root = useCanvasRoot() const selection = useSelection() @@ -385,7 +385,7 @@ const GapSchema = v.pipe(v.number(), v.integer(), v.minValue(0)) export function App() { const isAllowedToSetAttributes = useIsAllowedTo("setAttributes") - const { rects, names } = useGroundNodeRects() + const { rects, names } = useGroundNodes() const isEnabled = Object.keys(rects).length > 1 From f6e62bfaf1f76a2a871083c31ea8e51f7c894805 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:27:50 -0400 Subject: [PATCH 4/4] Add border to preview frame --- plugins/tidyup/src/App.css | 18 ++++++++++++------ plugins/tidyup/src/App.tsx | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) 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 63eafb595..1ac41d278 100644 --- a/plugins/tidyup/src/App.tsx +++ b/plugins/tidyup/src/App.tsx @@ -428,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", }}