@hyperframes/studio 0.8.50 → 0.8.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-I5YRzB02.js} +3 -3
- package/dist/assets/{index-Dt3qBI-9.js → index-BTAfgesM.js} +1 -1
- package/dist/assets/index-CxAu_Okg.css +1 -0
- package/dist/assets/index-DocJlW2v.js +376 -0
- package/dist/assets/{index-DUAIWxkI.js → index-Du5x7KUR.js} +1 -1
- package/dist/index.d.ts +419 -20
- package/dist/index.html +2 -2
- package/dist/index.js +7496 -8288
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +13 -12
- package/src/App.tsx +131 -143
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
- package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
- package/src/captions/components/CaptionOverlay.tsx +8 -4
- package/src/captions/components/CaptionOverlayUtils.ts +2 -3
- package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
- package/src/captions/components/CaptionTimeline.tsx +1 -1
- package/src/captions/components/shared.tsx +2 -2
- package/src/components/AskAgentModal.tsx +5 -5
- package/src/components/EditorShell.tsx +8 -7
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/RightPanelTabs.test.tsx +151 -0
- package/src/components/RightPanelTabs.tsx +59 -0
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.dom.test.tsx +181 -0
- package/src/components/StudioHeader.tsx +110 -210
- package/src/components/StudioLeftSidebar.tsx +7 -4
- package/src/components/StudioOverlays.tsx +1 -1
- package/src/components/StudioRightPanel.tsx +51 -39
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
- package/src/components/TimelineHistoryButtons.tsx +54 -0
- package/src/components/TimelineToolbar.test.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +8 -12
- package/src/components/dock/Dock.test.tsx +260 -0
- package/src/components/dock/Dock.tsx +281 -0
- package/src/components/dock/dock.css +28 -0
- package/src/components/dock/dockAccessibility.test.ts +166 -0
- package/src/components/dock/dockAccessibility.ts +98 -0
- package/src/components/dock/dockLayout.test.ts +134 -0
- package/src/components/dock/dockLayout.ts +105 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.ts +91 -0
- package/src/components/dock/dockTestHarness.ts +11 -0
- package/src/components/dock/panelRegistry.ts +67 -0
- package/src/components/editor/AnimationCard.tsx +4 -4
- package/src/components/editor/AnimationCardParts.tsx +6 -6
- package/src/components/editor/BlockParamsPanel.tsx +4 -4
- package/src/components/editor/BorderRadiusEditor.tsx +1 -1
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +1 -1
- package/src/components/editor/EaseParamFields.tsx +5 -5
- package/src/components/editor/FileTree.tsx +4 -4
- package/src/components/editor/FileTreeIcons.tsx +1 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -13
- package/src/components/editor/InlineTextToolbar.tsx +1 -1
- package/src/components/editor/InspectorHeaderActions.tsx +3 -3
- package/src/components/editor/KeyframeDiamond.tsx +1 -1
- package/src/components/editor/KeyframeEaseList.tsx +1 -1
- package/src/components/editor/LayersPanel.test.ts +97 -1
- package/src/components/editor/LayersPanel.tsx +7 -5
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.test.tsx +28 -0
- package/src/components/editor/TopologyLens.tsx +7 -5
- package/src/components/editor/Transform3DCube.tsx +2 -2
- package/src/components/editor/audioFxRevealTarget.ts +1 -0
- package/src/components/editor/domEditing.test.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
- package/src/components/editor/domEditingAgentPrompt.ts +4 -3
- package/src/components/editor/propertyPanelColor.tsx +11 -11
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
- package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
- package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
- package/src/components/editor/propertyPanelCommitField.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +4 -4
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
- package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
- package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +5 -5
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +6 -6
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
- package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
- package/src/components/editor/propertyPanelSections.tsx +8 -8
- package/src/components/editor/useColorGradingController.test.ts +38 -0
- package/src/components/editor/useColorGradingController.ts +9 -1
- package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
- package/src/components/editor/useColorGradingPreviews.ts +11 -1
- package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
- package/src/components/editor/usePreviewCompositionRect.ts +21 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
- package/src/components/feedback/feedbackTrigger.ts +0 -1
- package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
- package/src/components/nle/NLEPreview.tsx +3 -3
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/TimelinePane.tsx +3 -3
- package/src/components/nle/TimelineResizeDivider.tsx +2 -2
- package/src/components/panels/SlideshowPanel.tsx +2 -2
- package/src/components/panels/SlideshowSubPanels.tsx +20 -17
- package/src/components/panels/VariablesBindElement.tsx +2 -2
- package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +3 -3
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/pointerTargetSize.test.tsx +8 -0
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +7 -7
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- package/src/components/sidebar/AssetCard.tsx +8 -8
- package/src/components/sidebar/AssetContextMenu.tsx +8 -8
- package/src/components/sidebar/AssetsTab.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +6 -6
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
- package/src/components/sidebar/LeftSidebar.tsx +19 -5
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/timelineToolbarStyles.ts +5 -0
- package/src/components/ui/Button.test.tsx +252 -0
- package/src/components/ui/Button.tsx +89 -126
- package/src/components/ui/IconButton.tsx +38 -0
- package/src/components/ui/Input.tsx +115 -0
- package/src/components/ui/Menu.test.tsx +374 -0
- package/src/components/ui/Menu.tsx +213 -0
- package/src/components/ui/NumberField.tsx +122 -0
- package/src/components/ui/Popover.tsx +74 -0
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Select.tsx +100 -0
- package/src/components/ui/Slider.tsx +153 -0
- package/src/components/ui/Tabs.test.tsx +98 -0
- package/src/components/ui/Tabs.tsx +71 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/Tooltip.test.tsx +110 -0
- package/src/components/ui/Tooltip.tsx +38 -112
- package/src/components/ui/cn.test.ts +30 -0
- package/src/components/ui/cn.ts +25 -0
- package/src/components/ui/index.ts +29 -2
- package/src/components/ui/mountHost.testHelpers.ts +24 -0
- package/src/components/ui/valueControls.test.tsx +524 -0
- package/src/hooks/useAppHotkeys.ts +21 -80
- package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
- package/src/hooks/useCaptionDetection.ts +3 -1
- package/src/hooks/useEditHistoryActions.test.tsx +90 -0
- package/src/hooks/useEditHistoryActions.ts +106 -0
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
- package/src/hooks/useServerConnection.ts +43 -10
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/index.ts +51 -0
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +3 -3
- package/src/player/components/ClipContextMenu.tsx +3 -3
- package/src/player/components/CompositionThumbnail.test.ts +0 -3
- package/src/player/components/CompositionThumbnail.tsx +3 -4
- package/src/player/components/EditModal.tsx +1 -1
- package/src/player/components/ImageThumbnail.tsx +1 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
- package/src/player/components/LayerDisclosureRow.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +1 -1
- package/src/player/components/ShortcutsPanel.tsx +9 -9
- package/src/player/components/SpeedMenu.tsx +3 -5
- package/src/player/components/Timeline.tsx +2 -1
- package/src/player/components/TimelineCanvas.tsx +21 -14
- package/src/player/components/TimelineClip.tsx +0 -1
- package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
- package/src/player/components/TimelineEmptyState.tsx +5 -5
- package/src/player/components/TimelineFxButton.tsx +6 -6
- package/src/player/components/TimelineGroupHeader.tsx +5 -5
- package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +32 -0
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +1 -1
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +2 -1
- package/src/player/components/TrackGapContextMenu.tsx +1 -1
- package/src/player/components/VideoThumbnail.tsx +4 -2
- package/src/player/components/VolumeControl.tsx +2 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -1
- package/src/player/components/timelineDragDrop.ts +57 -15
- package/src/player/components/timelineLaneProps.ts +3 -0
- package/src/player/components/timelineSnapping.test.ts +27 -0
- package/src/player/components/timelineSnapping.ts +14 -1
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/lib/describeClips.ts +3 -54
- package/src/player/lib/playbackShortcuts.ts +2 -0
- package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
- package/src/player/store/previewIframeStore.test.tsx +29 -0
- package/src/player/store/previewIframeStore.ts +25 -0
- package/src/renderQueueExports.test.tsx +58 -0
- package/src/styles/classCandidates.test.ts +105 -0
- package/src/styles/classCandidates.ts +281 -0
- package/src/styles/hex-baseline.json +84 -0
- package/src/styles/hexRatchet.test.ts +201 -0
- package/src/styles/iconTokens.ts +20 -0
- package/src/styles/palette-reset.css +6 -0
- package/src/styles/studio.css +24 -21
- package/src/styles/styleSources.test.ts +20 -0
- package/src/styles/styleSources.ts +47 -0
- package/src/styles/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +243 -0
- package/src/styles/theme.test.ts +183 -0
- package/src/styles/tokenGate.test.ts +109 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioHelpers.test.ts +18 -0
- package/src/utils/studioHelpers.ts +11 -1
- package/src/utils/studioUiPreferences.ts +7 -0
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/typingTarget.ts +4 -0
- package/dist/assets/index-n2ml4tUn.js +0 -442
- package/dist/assets/index-pO6p0H27.css +0 -1
- package/src/components/PanelTabButton.tsx +0 -31
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
- package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
- package/src/components/storyboard/FramePoster.test.tsx +0 -61
- package/src/components/storyboard/FramePoster.tsx +0 -86
- package/src/components/storyboard/StoryboardDirection.tsx +0 -45
- package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
- package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
- package/src/components/storyboard/StoryboardGrid.tsx +0 -55
- package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
- package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
- package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
- package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
- package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
- package/src/components/storyboard/StoryboardView.tsx +0 -169
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
- package/src/components/storyboard/frameComments.test.ts +0 -95
- package/src/components/storyboard/frameComments.ts +0 -115
- package/src/components/storyboard/frameStatus.ts +0 -36
- package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
- package/src/components/storyboard/storyboardReviewStage.ts +0 -45
- package/src/components/storyboard/useFrameComments.ts +0 -98
- package/src/contexts/ViewModeContext.tsx +0 -142
- package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
|
@@ -1,159 +1,122 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Button
|
|
2
|
+
* Button — a native `<button>` wearing Studio's tokens.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Everything visual comes from `theme.css`: the three control heights, the
|
|
5
|
+
* radius scale, the semantic colours, the motion durations. No value is decided
|
|
6
|
+
* here, so a button cannot drift from the rest of the system.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - duration-press-hover: 120ms press, 150ms hover
|
|
8
|
+
* Two conventions: classes merge through `cn`, so a caller's `className`
|
|
9
|
+
* always wins within its group; and every interactive look is written twice,
|
|
10
|
+
* once as the real state and once as `data-[preview-state=…]` (CSS-only, for
|
|
11
|
+
* screenshots). `Button.test.tsx` asserts the two stay in sync.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
15
|
+
import { cn } from "./cn";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
export type ButtonVariant = "primary" | "secondary" | "danger" | "ghost";
|
|
18
|
+
export type ButtonSize = "sm" | "md" | "lg";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
type
|
|
20
|
+
/** Forces one interactive look for a gallery shot. CSS-only; see the header. */
|
|
21
|
+
export type PreviewState = "hover" | "active" | "focus";
|
|
21
22
|
|
|
22
|
-
interface
|
|
23
|
+
export interface ButtonBaseProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
23
24
|
variant?: ButtonVariant;
|
|
24
25
|
size?: ButtonSize;
|
|
26
|
+
"data-preview-state"?: PreviewState;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ButtonProps extends ButtonBaseProps {
|
|
25
30
|
loading?: boolean;
|
|
26
31
|
icon?: ReactNode;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"bg-
|
|
49
|
-
"enabled:hover:
|
|
50
|
-
"enabled:active:scale-[0.98]",
|
|
51
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Shared by Button and IconButton. `disabled:` keeps pointer events alive so a
|
|
36
|
+
* wrapping Tooltip can still explain why the control is disabled.
|
|
37
|
+
*/
|
|
38
|
+
export const buttonBase = cn(
|
|
39
|
+
"inline-flex items-center justify-center select-none cursor-pointer whitespace-nowrap",
|
|
40
|
+
"transition-[background-color,border-color,color,filter,transform] ease-out-quint duration-press",
|
|
41
|
+
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
42
|
+
"outline-hidden",
|
|
43
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent",
|
|
44
|
+
"data-[preview-state=focus]:outline-solid data-[preview-state=focus]:outline-2 data-[preview-state=focus]:outline-offset-1 data-[preview-state=focus]:outline-accent",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One entry per variant. The `data-[preview-state=…]` half of each string
|
|
49
|
+
* repeats the `hover:` and `active:` half exactly; the test pairs them.
|
|
50
|
+
*/
|
|
51
|
+
export const buttonVariants: Record<ButtonVariant, string> = {
|
|
52
|
+
primary: cn(
|
|
53
|
+
"bg-accent text-bg-0 font-semibold",
|
|
54
|
+
"enabled:hover:brightness-110 data-[preview-state=hover]:brightness-110",
|
|
55
|
+
"enabled:active:scale-[0.98] data-[preview-state=active]:scale-[0.98]",
|
|
56
|
+
),
|
|
57
|
+
secondary: cn(
|
|
58
|
+
"bg-transparent text-text-1 font-medium border border-border-strong",
|
|
59
|
+
"enabled:hover:bg-hover enabled:hover:text-text-0 data-[preview-state=hover]:bg-hover data-[preview-state=hover]:text-text-0",
|
|
60
|
+
"enabled:active:scale-[0.98] data-[preview-state=active]:scale-[0.98]",
|
|
61
|
+
),
|
|
62
|
+
danger: cn(
|
|
63
|
+
"bg-danger text-text-0 font-medium",
|
|
64
|
+
"enabled:hover:brightness-110 data-[preview-state=hover]:brightness-110",
|
|
65
|
+
"enabled:active:scale-[0.98] data-[preview-state=active]:scale-[0.98]",
|
|
66
|
+
),
|
|
67
|
+
ghost: cn(
|
|
68
|
+
"bg-transparent text-text-2 font-medium",
|
|
69
|
+
"enabled:hover:bg-hover enabled:hover:text-text-0 data-[preview-state=hover]:bg-hover data-[preview-state=hover]:text-text-0",
|
|
70
|
+
"enabled:active:scale-[0.98] data-[preview-state=active]:scale-[0.98]",
|
|
71
|
+
),
|
|
52
72
|
};
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The three control heights, 24 / 28 / 32 px, from `--spacing-ctl-*`.
|
|
76
|
+
* Exported so the Capture download `<a href>` can wear the same recipe as a Button.
|
|
77
|
+
*/
|
|
78
|
+
export const buttonSizes: Record<ButtonSize, string> = {
|
|
79
|
+
sm: "h-ctl-sm px-2 gap-1 rounded-sm text-step-11",
|
|
80
|
+
md: "h-ctl px-3 gap-1.5 rounded-md text-step-12",
|
|
81
|
+
lg: "h-ctl-lg px-4 gap-2 rounded-md text-step-13",
|
|
58
82
|
};
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
function Spinner() {
|
|
85
|
+
return (
|
|
86
|
+
<svg className="animate-spin size-3.5" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
87
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
88
|
+
<path
|
|
89
|
+
className="opacity-75"
|
|
90
|
+
fill="currentColor"
|
|
91
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
92
|
+
/>
|
|
93
|
+
</svg>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
63
98
|
(
|
|
64
|
-
{
|
|
65
|
-
variant = "secondary",
|
|
66
|
-
size = "md",
|
|
67
|
-
loading,
|
|
68
|
-
icon,
|
|
69
|
-
children,
|
|
70
|
-
className = "",
|
|
71
|
-
disabled,
|
|
72
|
-
...props
|
|
73
|
-
},
|
|
99
|
+
{ variant = "secondary", size = "md", loading, icon, children, className, disabled, ...props },
|
|
74
100
|
ref,
|
|
75
101
|
) => {
|
|
102
|
+
const isDisabled = disabled || loading;
|
|
103
|
+
let leading: ReactNode = null;
|
|
104
|
+
if (loading) leading = <Spinner />;
|
|
105
|
+
else if (icon) leading = <span className="shrink-0">{icon}</span>;
|
|
76
106
|
return (
|
|
77
107
|
<button
|
|
78
108
|
ref={ref}
|
|
79
|
-
disabled={
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// so a wrapping Tooltip can explain WHY they're disabled (A5).
|
|
85
|
-
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
86
|
-
"select-none cursor-pointer",
|
|
87
|
-
"outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-studio-accent",
|
|
88
|
-
variantStyles[variant],
|
|
89
|
-
sizeStyles[size],
|
|
90
|
-
className,
|
|
91
|
-
].join(" ")}
|
|
109
|
+
disabled={isDisabled}
|
|
110
|
+
// `aria-disabled` as well as `disabled`: assistive tech announces the
|
|
111
|
+
// state even where the native attribute is filtered out of the tree.
|
|
112
|
+
aria-disabled={isDisabled || undefined}
|
|
113
|
+
className={cn(buttonBase, buttonVariants[variant], buttonSizes[size], className)}
|
|
92
114
|
{...props}
|
|
93
115
|
>
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
<circle
|
|
97
|
-
className="opacity-25"
|
|
98
|
-
cx="12"
|
|
99
|
-
cy="12"
|
|
100
|
-
r="10"
|
|
101
|
-
stroke="currentColor"
|
|
102
|
-
strokeWidth="4"
|
|
103
|
-
/>
|
|
104
|
-
<path
|
|
105
|
-
className="opacity-75"
|
|
106
|
-
fill="currentColor"
|
|
107
|
-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
108
|
-
/>
|
|
109
|
-
</svg>
|
|
110
|
-
) : icon ? (
|
|
111
|
-
<span className="flex-shrink-0">{icon}</span>
|
|
112
|
-
) : null}
|
|
113
|
-
{children && <span>{children}</span>}
|
|
116
|
+
{leading}
|
|
117
|
+
{children}
|
|
114
118
|
</button>
|
|
115
119
|
);
|
|
116
120
|
},
|
|
117
121
|
);
|
|
118
122
|
Button.displayName = "Button";
|
|
119
|
-
|
|
120
|
-
// -- IconButton --
|
|
121
|
-
// For icon-only buttons. Enforces min 32px hit target.
|
|
122
|
-
|
|
123
|
-
interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
124
|
-
icon: ReactNode;
|
|
125
|
-
size?: ButtonSize;
|
|
126
|
-
variant?: ButtonVariant;
|
|
127
|
-
"aria-label": string; // REQUIRED for accessibility
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const iconSizeStyles: Record<ButtonSize, string> = {
|
|
131
|
-
sm: "min-w-7 min-h-7 rounded-button", // 28px
|
|
132
|
-
md: "min-w-8 min-h-8 rounded-button", // 32px — minimum recommended
|
|
133
|
-
lg: "min-w-9 min-h-9 rounded-button", // 36px
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// fallow-ignore-next-line unused-export
|
|
137
|
-
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
138
|
-
({ icon, size = "md", variant = "ghost", className = "", ...props }, ref) => {
|
|
139
|
-
return (
|
|
140
|
-
<button
|
|
141
|
-
ref={ref}
|
|
142
|
-
className={[
|
|
143
|
-
"inline-flex items-center justify-center",
|
|
144
|
-
"transition-all duration-press ease-standard",
|
|
145
|
-
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
146
|
-
"select-none cursor-pointer",
|
|
147
|
-
"outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-studio-accent",
|
|
148
|
-
variantStyles[variant],
|
|
149
|
-
iconSizeStyles[size],
|
|
150
|
-
className,
|
|
151
|
-
].join(" ")}
|
|
152
|
-
{...props}
|
|
153
|
-
>
|
|
154
|
-
{icon}
|
|
155
|
-
</button>
|
|
156
|
-
);
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
IconButton.displayName = "IconButton";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton — Button's square sibling; shares its base/variant classes so
|
|
3
|
+
* the two never drift. `aria-label` is required for screen readers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import { buttonBase, buttonVariants, type ButtonBaseProps, type ButtonSize } from "./Button";
|
|
9
|
+
import { cn } from "./cn";
|
|
10
|
+
|
|
11
|
+
interface IconButtonProps extends ButtonBaseProps {
|
|
12
|
+
icon: ReactNode;
|
|
13
|
+
"aria-label": string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Square boxes on the same 24 / 28 / 32 px control heights as Button. */
|
|
17
|
+
const iconSizeStyles: Record<ButtonSize, string> = {
|
|
18
|
+
sm: "size-ctl-sm rounded-sm",
|
|
19
|
+
md: "size-ctl rounded-md",
|
|
20
|
+
lg: "size-ctl-lg rounded-md",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
24
|
+
({ icon, size = "md", variant = "ghost", className, disabled, ...props }, ref) => {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
ref={ref}
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
aria-disabled={disabled || undefined}
|
|
30
|
+
className={cn(buttonBase, buttonVariants[variant], iconSizeStyles[size], className)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{icon}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
IconButton.displayName = "IconButton";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input: a boxed text field. Typing edits a local draft; Enter and blur commit it,
|
|
3
|
+
* Escape abandons it. `onTrack` fires once per commit, never per keystroke.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Input as BaseInput } from "@base-ui/react/input";
|
|
7
|
+
import { useEffect, useRef, useState, type ComponentPropsWithoutRef } from "react";
|
|
8
|
+
import { cn } from "./cn";
|
|
9
|
+
import type { PreviewState } from "./Button";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shared field look (also used by NumberField and Select). Each interactive state is written
|
|
13
|
+
* twice, real and `data-[preview-state=...]`; `valueControls.test.tsx` asserts the lists match.
|
|
14
|
+
*/
|
|
15
|
+
export const fieldBase = cn(
|
|
16
|
+
"flex min-w-0 items-center gap-1.5 h-ctl px-2 rounded-md",
|
|
17
|
+
"bg-input border border-border-input text-step-11 font-medium text-text-1",
|
|
18
|
+
"transition-[border-color,background-color] ease-standard duration-focus",
|
|
19
|
+
"hover:border-border-strong data-[preview-state=hover]:border-border-strong",
|
|
20
|
+
"outline-hidden",
|
|
21
|
+
"focus-within:outline-solid focus-within:outline-2 focus-within:outline-offset-1 focus-within:outline-accent",
|
|
22
|
+
"data-[preview-state=focus]:outline-solid data-[preview-state=focus]:outline-2 data-[preview-state=focus]:outline-offset-1 data-[preview-state=focus]:outline-accent",
|
|
23
|
+
// `aria-invalid` is not one of Tailwind's built-in aria variants, so the
|
|
24
|
+
// arbitrary form is the one that compiles. The token gate would have caught
|
|
25
|
+
// the bare `aria-invalid:` spelling as a class that resolves to nothing.
|
|
26
|
+
"aria-[invalid]:border-danger",
|
|
27
|
+
"has-[:disabled]:opacity-40 has-[:disabled]:cursor-not-allowed",
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/** The text itself, inside the box. The box owns the border and the ring. */
|
|
31
|
+
export const fieldText = cn(
|
|
32
|
+
"min-w-0 w-full bg-transparent text-inherit outline-hidden",
|
|
33
|
+
"placeholder:text-text-5 disabled:cursor-not-allowed",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface InputProps extends Omit<
|
|
37
|
+
ComponentPropsWithoutRef<"input">,
|
|
38
|
+
"value" | "onChange" | "className"
|
|
39
|
+
> {
|
|
40
|
+
/** The committed value. A draft lives inside until Enter or blur. */
|
|
41
|
+
value: string;
|
|
42
|
+
/** Called with the draft on Enter and on blur, only when it really changed. */
|
|
43
|
+
onCommit: (next: string) => void;
|
|
44
|
+
/** Called once per committed change, for design-input telemetry. */
|
|
45
|
+
onTrack?: () => void;
|
|
46
|
+
/** Marks the field invalid: red boundary and `aria-invalid`. */
|
|
47
|
+
invalid?: boolean;
|
|
48
|
+
className?: string;
|
|
49
|
+
"data-preview-state"?: PreviewState;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Input({
|
|
53
|
+
value,
|
|
54
|
+
onCommit,
|
|
55
|
+
onTrack,
|
|
56
|
+
invalid,
|
|
57
|
+
disabled,
|
|
58
|
+
className,
|
|
59
|
+
"data-preview-state": previewState,
|
|
60
|
+
...props
|
|
61
|
+
}: InputProps) {
|
|
62
|
+
const [draft, setDraft] = useState(value);
|
|
63
|
+
const valueRef = useRef(value);
|
|
64
|
+
valueRef.current = value;
|
|
65
|
+
const dirtyRef = useRef(false);
|
|
66
|
+
|
|
67
|
+
// An external write (a seek, an undo, another panel) is authoritative, but it
|
|
68
|
+
// must not eat what the user is halfway through typing.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!dirtyRef.current) setDraft(value);
|
|
71
|
+
}, [value]);
|
|
72
|
+
|
|
73
|
+
const commit = (next: string) => {
|
|
74
|
+
dirtyRef.current = false;
|
|
75
|
+
if (next === valueRef.current) return;
|
|
76
|
+
onCommit(next);
|
|
77
|
+
onTrack?.();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={cn(fieldBase, className)}
|
|
83
|
+
aria-invalid={invalid || undefined}
|
|
84
|
+
data-preview-state={previewState}
|
|
85
|
+
>
|
|
86
|
+
<BaseInput
|
|
87
|
+
value={draft}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
aria-invalid={invalid || undefined}
|
|
90
|
+
className={fieldText}
|
|
91
|
+
onValueChange={(next) => {
|
|
92
|
+
dirtyRef.current = true;
|
|
93
|
+
setDraft(next);
|
|
94
|
+
}}
|
|
95
|
+
onBlur={() => commit(draft)}
|
|
96
|
+
onKeyDown={(event) => {
|
|
97
|
+
if (event.key === "Enter") {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
commit(draft);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (event.key === "Escape") {
|
|
103
|
+
// Abandon the draft rather than leave it for the blur commit.
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
event.stopPropagation();
|
|
106
|
+
dirtyRef.current = false;
|
|
107
|
+
setDraft(valueRef.current);
|
|
108
|
+
event.currentTarget.blur();
|
|
109
|
+
}
|
|
110
|
+
}}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|