diff --git a/.gitignore b/.gitignore index a84e5d3..c53e981 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ src/styled # Figma fidelity audit scratch (playwright + screenshots) .audit/ + +# Local-only superpowers specs and plans (never committed) +docs/superpowers/ diff --git a/src/components/side-panel/SidePanel.tsx b/src/components/side-panel/SidePanel.tsx index 1d082e9..7c58e4c 100644 --- a/src/components/side-panel/SidePanel.tsx +++ b/src/components/side-panel/SidePanel.tsx @@ -8,7 +8,12 @@ import type { AvatarColor } from "../avatar"; import { Avatar } from "../avatar"; import { AvatarPill } from "../avatar-pill"; import { Button } from "../button"; -import { Popover, PopoverAnchor, PopoverContent } from "../popover"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, +} from "../dialog"; import { TextField } from "../text-field"; import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip"; import { ArrowsMerge } from "./ArrowsMergeIcon"; @@ -18,7 +23,7 @@ import { ArrowsMerge } from "./ArrowsMergeIcon"; * AymurAI UI Library node 40002322:53113. * * Composite assembled from {@link AvatarPill}, {@link TextField}, - * {@link Button}, {@link Popover} and {@link Tooltip}. Sections: selected + * {@link Button}, {@link Dialog} and {@link Tooltip}. Sections: selected * turn card, suggested people, timestamp, and turn actions (merge * previous/next, add below, delete). * @@ -29,7 +34,7 @@ import { ArrowsMerge } from "./ArrowsMergeIcon"; * Consumers need a `TooltipProvider` somewhere up the tree for the Acciones * tooltips (see Tooltip.tsx / this component's story). * - * Merging two turns whose speakers differ shows a confirm popover (Figma + * Merging two turns whose speakers differ shows a confirm modal (Figma * "Conflicto Nombre etiqueta", node 40002384:38487) before firing * `onMergePrevious`/`onMergeNext` — pass `previousTurnName`/`nextTurnName` to * enable it; without them, merge fires immediately (previous behaviour). @@ -78,9 +83,9 @@ export type SidePanelProps = { onTimestampChange?: (value: string) => void; onMergePrevious?: () => void; onMergeNext?: () => void; - /** Speaker name of the previous turn — enables the merge confirm popover */ + /** Speaker name of the previous turn — enables the merge confirm modal */ previousTurnName?: string; - /** Speaker name of the next turn — enables the merge confirm popover */ + /** Speaker name of the next turn — enables the merge confirm modal */ nextTurnName?: string; onAddBelow?: () => void; onDelete?: () => void; @@ -142,25 +147,20 @@ const divider = css({ bg: "[#BCBAB8]", // Figma divider line (border/primary colour) }); -// Confirm popover (Figma "Conflicto Nombre etiqueta", node 40002384:38487): -// title + description + Combinar/Cancelar. Anchored to the Acciones section. -const confirmBox = css({ - ...stack.raw({ gap: "3" }), // 12px - p: "6", // 24px - maxW: "[341px]", -}); +// Confirm modal (Figma "Conflicto Nombre etiqueta", node 40002384:38487): +// title + description + Combinar/Cancelar, centered over a full-screen overlay. const confirmTitle = css({ - textStyle: "subtitle.sm.strong", + textStyle: "subtitle.md.strong", color: "text.default", }); const confirmDescription = css({ - textStyle: "label.sm.default", - color: "text.lighter", + textStyle: "subtitle.sm.default", + color: "text.default", }); const confirmButtons = css({ display: "flex", alignItems: "center", - gap: "4", // 16px + gap: "3", // 12px }); function Section({ @@ -199,6 +199,54 @@ function ActionButton({ ); } +const confirmTextBlock = css({ + ...stack.raw({ gap: "1" }), // 4px +}); + +function ConfirmDialog({ + open, + onOpenChange, + title, + description, + onConfirm, + onCancel, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: string; + onConfirm: () => void; + onCancel: () => void; +}) { + return ( + + +
+ +

{title}

+
+ +

{description}

+
+
+
+ + +
+
+
+ ); +} + export function SidePanel({ turn, people, @@ -256,11 +304,8 @@ export function SidePanel({ ); if (targetIndex >= 0) { - // Drop out of typing/input mode — the confirm popover is the only - // active affordance now. Leaving the input mounted here would sit - // inside PopoverAnchor (outside PopoverContent), so a click into it - // to keep editing registers as a Radix pointer-down-outside and - // cancels the whole edit instead. + // Drop out of typing/input mode — the confirm dialog is the only + // active affordance now. setEditingIndex(null); setEditValue(""); setRenameConflict({ @@ -329,77 +374,38 @@ export function SidePanel({
{people.map((person, index) => { const isEditing = editingIndex === index; - const hasRenameConflict = renameConflict?.sourceIndex === index; - const conflictTarget = hasRenameConflict - ? people[renameConflict.targetIndex] - : undefined; const canRename = person.renamable && onRenamePerson && onMergePeople; return ( - { - if (!open) finishEditing(); - }} + className={css({ display: "inline-flex" })} > - - - onSelectPerson?.(index)} - onRename={ - canRename ? () => startEditing(index) : undefined - } - editValue={isEditing ? editValue : undefined} - onEditValueChange={isEditing ? setEditValue : undefined} - onEditCommit={ - isEditing - ? (value) => handleRenameCommit(index, value) - : undefined - } - onEditCancel={isEditing ? finishEditing : undefined} - renameInputLabel={`Editar nombre de ${person.name}`} - /> - - - {hasRenameConflict && conflictTarget && ( - -

- Ya existe una persona llamada "{conflictTarget.name}". -

-

- Si continuás, ambas identidades se combinarán en una sola. -

-
- - -
-
- )} -
+ onSelectPerson?.(index)} + onRename={canRename ? () => startEditing(index) : undefined} + editValue={isEditing ? editValue : undefined} + onEditValueChange={isEditing ? setEditValue : undefined} + onEditCommit={ + isEditing + ? (value) => handleRenameCommit(index, value) + : undefined + } + onEditCancel={isEditing ? finishEditing : undefined} + renameInputLabel={`Editar nombre de ${person.name}`} + /> + ); })}
+ { + if (!open) finishEditing(); + }} + title={ + renameConflict + ? `Ya existe "${people[renameConflict.targetIndex]?.name}".` + : "" + } + description={ + renameConflict + ? `Al combinar, los turnos de "${people[renameConflict.sourceIndex]?.name}" pasan a "${people[renameConflict.targetIndex]?.name}".` + : "" + } + onConfirm={confirmPeopleMerge} + onCancel={finishEditing} + />
@@ -423,63 +447,43 @@ export function SidePanel({ {/* Actions */}
- + + + Unir con el anterior + + + + Unir con el siguiente + + + + Agregar debajo + + + + Eliminar + +
+ { if (!open) setConfirm(null); }} - > - -
- - - Unir con el anterior - - - - Unir con el siguiente - - - - Agregar debajo - - - - Eliminar - -
-
- {confirm && ( - -

{confirm.title}

-

{confirm.description}

-
- - -
-
- )} - + title={confirm?.title ?? ""} + description={confirm?.description ?? ""} + onConfirm={() => confirm?.onConfirm()} + onCancel={() => setConfirm(null)} + /> ); diff --git a/src/components/toolbar/Toolbar.tsx b/src/components/toolbar/Toolbar.tsx index ef89163..0572cdd 100644 --- a/src/components/toolbar/Toolbar.tsx +++ b/src/components/toolbar/Toolbar.tsx @@ -102,7 +102,8 @@ export function Toolbar({ // set-de-datos — items: end, justify: start, gap: 24px, py: 24px // search-switch — items: center, justify: space-between, pt: 42px, pb: 24px const isSetDeDatos = context === "set-de-datos"; - const alignItems = context === "search-switch" ? "center" : "flex-end"; + const isSearchSwitch = context === "search-switch"; + const alignItems = isSearchSwitch ? "center" : "flex-end"; return (
- {/* Search zone — always present, fills available space */} + {/* Search zone. Fills available space in anonimizador/set-de-datos; + fixed at 711.5px in search-switch (Figma node 40001478:54722) so it + doesn't crowd the Switch+label on the right. */}