-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor/UI tweaks #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
b57ab67
Refactor pagination control and add delete page functionality
u8array d1f1a41
Refactor delete page confirmation dialog
u8array c17884e
feat(ui): collapsible palette sections with persisted state
u8array b5a22bd
fix(ui): address gemini review and unify hint icons
u8array 661f334
feat(ui): wrap label output options in a collapsible Output section
u8array 0d3afcd
fix(ui): animate confirm button color hover transition
u8array File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { useEffect, useState, type ReactNode } from 'react'; | ||
| import { ChevronDownIcon } from '@heroicons/react/16/solid'; | ||
|
|
||
| interface CollapsibleSectionProps { | ||
| /** Stable identifier, used as the localStorage key for the open state. */ | ||
| id: string; | ||
| title: ReactNode; | ||
| defaultOpen?: boolean; | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| const LS_PREFIX = 'zpl:section:'; | ||
|
|
||
| function readStored(id: string, fallback: boolean): boolean { | ||
| const saved = localStorage.getItem(LS_PREFIX + id); | ||
| return saved === null ? fallback : saved === '1'; | ||
| } | ||
|
|
||
| /** | ||
| * Section with a clickable header that toggles its body. Independent of | ||
| * sibling sections — multiple can be open at once. Open state is persisted | ||
| * per `id` in localStorage so the UI feels stable across reloads. | ||
| */ | ||
| export function CollapsibleSection({ | ||
| id, | ||
| title, | ||
| defaultOpen = true, | ||
| children, | ||
| }: CollapsibleSectionProps) { | ||
| const [open, setOpen] = useState(() => readStored(id, defaultOpen)); | ||
|
|
||
| // Re-sync open state when `id` changes so the component can be reused for | ||
| // a different section without leaking the previous open state into the new | ||
| // id's storage slot. React's blessed pattern for deriving state from | ||
| // props: setState during render under a prev-vs-current guard. | ||
| // https://react.dev/reference/react/useState#storing-information-from-previous-renders | ||
| const [prevId, setPrevId] = useState(id); | ||
| if (prevId !== id) { | ||
| setPrevId(id); | ||
| setOpen(readStored(id, defaultOpen)); | ||
| } | ||
|
|
||
| useEffect(() => { | ||
| localStorage.setItem(LS_PREFIX + id, open ? '1' : '0'); | ||
| }, [id, open]); | ||
|
|
||
| const contentId = `section-content-${id}`; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-0.5"> | ||
| <button | ||
| type="button" | ||
| onClick={() => setOpen((o) => !o)} | ||
| aria-expanded={open} | ||
| aria-controls={contentId} | ||
| className="flex items-center justify-between gap-2 px-1 pt-1 pb-1.5 text-muted hover:text-text transition-colors" | ||
| > | ||
| <span className="font-mono text-[10px] font-medium uppercase tracking-widest"> | ||
| {title} | ||
| </span> | ||
| <ChevronDownIcon | ||
| className={`w-3 h-3 shrink-0 transition-transform ${open ? '' : '-rotate-90'}`} | ||
| /> | ||
| </button> | ||
| {open && ( | ||
| <div id={contentId} className="flex flex-col gap-0.5"> | ||
| {children} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { useEffect } from 'react'; | ||
| import { createPortal } from 'react-dom'; | ||
|
|
||
| interface ConfirmDialogProps { | ||
| message: string; | ||
| confirmLabel: string; | ||
| cancelLabel: string; | ||
| /** Renders the confirm button in red. Use for irreversible operations. */ | ||
| destructive?: boolean; | ||
| onConfirm: () => void; | ||
| onCancel: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * Minimal confirm dialog matching the project's modal aesthetic. | ||
| * | ||
| * Mount it conditionally (`{open && <ConfirmDialog … />}`); the parent owns | ||
| * visibility state. Backdrop click and Escape both fire `onCancel`. | ||
| */ | ||
| export function ConfirmDialog({ | ||
| message, | ||
| confirmLabel, | ||
| cancelLabel, | ||
| destructive, | ||
| onConfirm, | ||
| onCancel, | ||
| }: ConfirmDialogProps) { | ||
| useEffect(() => { | ||
| const onKey = (e: KeyboardEvent) => { | ||
| if (e.key === 'Escape') onCancel(); | ||
| }; | ||
| window.addEventListener('keydown', onKey); | ||
| // Lock background scroll while the modal is open so the dialog stays | ||
| // visually anchored and the user cannot drift past it. | ||
| const originalOverflow = document.body.style.overflow; | ||
| document.body.style.overflow = 'hidden'; | ||
| return () => { | ||
| window.removeEventListener('keydown', onKey); | ||
| document.body.style.overflow = originalOverflow; | ||
| }; | ||
| }, [onCancel]); | ||
|
|
||
| const confirmCls = destructive | ||
| ? 'bg-red-500 text-white hover:bg-red-600' | ||
| : 'bg-accent text-bg hover:opacity-90'; | ||
|
|
||
| // Portal so the fixed-position backdrop is anchored to the viewport even | ||
| // when an ancestor has a CSS transform (which would otherwise contain | ||
| // `position: fixed` and miscentre the modal). | ||
| return createPortal( | ||
| <div | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" | ||
| onClick={onCancel} | ||
| > | ||
| <div | ||
| role="alertdialog" | ||
| aria-modal="true" | ||
| aria-describedby="confirm-dialog-message" | ||
| className="bg-surface border border-border rounded shadow-lg flex flex-col w-[400px] max-w-[95vw]" | ||
| onClick={(e) => e.stopPropagation()} | ||
| > | ||
| <p | ||
| id="confirm-dialog-message" | ||
| className="px-5 py-5 text-xs text-text leading-relaxed" | ||
| > | ||
| {message} | ||
| </p> | ||
| <div className="flex justify-end gap-2 px-4 py-3 border-t border-border"> | ||
| <button | ||
| type="button" | ||
| onClick={onCancel} | ||
| autoFocus={destructive} | ||
| className="px-4 py-1.5 rounded text-xs font-mono whitespace-nowrap border border-border text-text hover:bg-surface-2 transition-colors" | ||
| > | ||
| {cancelLabel} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={onConfirm} | ||
| autoFocus={!destructive} | ||
| className={`px-4 py-1.5 rounded text-xs font-mono whitespace-nowrap ${confirmCls} transition`} | ||
| > | ||
| {confirmLabel} | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div>, | ||
| document.body, | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.