diff --git a/packages/sw/src/components/sw-footer/assets/menu-add-hover.svg b/packages/sw/src/components/sw-footer/assets/menu-add-hover.svg new file mode 100644 index 0000000..8b6dc9b --- /dev/null +++ b/packages/sw/src/components/sw-footer/assets/menu-add-hover.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx b/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx index 21b42c2..6aa568d 100644 --- a/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx +++ b/packages/sw/src/components/sw-header/components/SWHeaderPropertyPanel.tsx @@ -176,7 +176,8 @@ export function useSWHeaderOverlay(anchorRef: RefObject | un return { panelRef, style } } -function PropertyEditor({ property, onChange, menus }: { property: SWHeaderProperty; onChange?: SWHeaderPropertyPanelProps["onPropertyChange"]; menus?: SWHeaderPropertyMenus }) { +/** One property window row (label + field / picker). Also SWPopupPropertyRow (2026-10-20). */ +export function PropertyEditor({ property, onChange, menus }: { property: SWHeaderProperty; onChange?: SWHeaderPropertyPanelProps["onPropertyChange"]; menus?: SWHeaderPropertyMenus }) { const inputId = useId() const inputRef = useRef(null) const { type, value, disabled, mixed, readOnly, unit } = property diff --git a/packages/sw/src/components/sw-popup/SWPopup.tsx b/packages/sw/src/components/sw-popup/SWPopup.tsx index 71b1c59..8e68fa8 100644 --- a/packages/sw/src/components/sw-popup/SWPopup.tsx +++ b/packages/sw/src/components/sw-popup/SWPopup.tsx @@ -1,5 +1,5 @@ import { cloneElement, isValidElement, type HTMLAttributes, type MouseEvent, type ReactNode, type RefObject } from "react" -import { SWPopupFrame } from "../sw-header/components/SWHeaderPropertyPanel" +import { PropertyEditor, SWPopupFrame, type SWHeaderProperty, type SWHeaderPropertyMenus } from "../sw-header/components/SWHeaderPropertyPanel" export interface SWPopupProps extends Omit, "title"> { /** Title text (700, #FFC600, ellipsis). */ @@ -99,3 +99,26 @@ export function SWPopupRow({ label, htmlFor, unit, field = "box", numeric, readO ) } + +export interface SWPopupPropertyRowProps { + /** One property (the SWHeaderPropertyPanel record): name, value, type and, for layer / color / line style, `picker`. */ + property: SWHeaderProperty + /** Lists and callbacks of the field dropdowns (layers, colors, line types …), as in SWHeaderPropertyPanel. */ + menus?: SWHeaderPropertyMenus + /** Validated commit of the row (value = the chosen row id or the entered value). The host updates `property`. */ + onPropertyChange?: (property: SWHeaderProperty, value: string) => void + className?: string +} + +/** + * A property window row inside SWPopup / SWPopupSection (2026-10-20): the popup field controls of the property window + * (Figma 26:12250 layer, 26:12170 color, 26:12388 line style, number / text / select fields) with their popup look + * and behavior. Use it for layer / color pickers in a popup; the SWHeader field dropdowns are header-only. + */ +export function SWPopupPropertyRow({ property, menus, onPropertyChange, className }: SWPopupPropertyRowProps) { + return ( +
+ +
+ ) +} diff --git a/packages/sw/src/components/sw-select/SWSelect.tsx b/packages/sw/src/components/sw-select/SWSelect.tsx new file mode 100644 index 0000000..26652ed --- /dev/null +++ b/packages/sw/src/components/sw-select/SWSelect.tsx @@ -0,0 +1,35 @@ +import { forwardRef, type ReactNode, type SelectHTMLAttributes } from "react" + +export interface SWSelectOption { + value: string + /** Shown text. Default `value`. */ + label?: string + disabled?: boolean +} + +export interface SWSelectProps extends Omit, "value" | "defaultValue" | "onChange"> { + /** Controlled value. */ + value?: string + /** Uncontrolled initial value. */ + defaultValue?: string + /** Options as data. `children` (`