@lar-works/design-system 0.2.0 → 0.3.0
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/README.md +5 -1
- package/dist/actions/Button.d.ts +39 -1
- package/dist/actions/Button.js +39 -9
- package/dist/actions/Button.js.map +1 -1
- package/dist/actions/ButtonLink.d.ts +5 -1
- package/dist/actions/ButtonLink.js +28 -2
- package/dist/actions/ButtonLink.js.map +1 -1
- package/dist/actions/ButtonRecipes.js +7 -0
- package/dist/actions/ButtonRecipes.js.map +1 -0
- package/dist/actions/IconButton.d.ts +2 -2
- package/dist/actions/IconButton.js +1 -1
- package/dist/actions/IconButton.js.map +1 -1
- package/dist/actions/ThemeSwitch.js +1 -1
- package/dist/board/ActivityLine.d.ts +36 -0
- package/dist/board/ActivityLine.js +144 -0
- package/dist/board/ActivityLine.js.map +1 -0
- package/dist/board/ActivityTrack.d.ts +11 -0
- package/dist/board/ActivityTrack.js +25 -0
- package/dist/board/ActivityTrack.js.map +1 -0
- package/dist/board/AddItem.d.ts +82 -0
- package/dist/board/AddItem.js +185 -0
- package/dist/board/AddItem.js.map +1 -0
- package/dist/board/Archive.d.ts +21 -0
- package/dist/board/Archive.js +63 -0
- package/dist/board/Archive.js.map +1 -0
- package/dist/board/Attention.d.ts +66 -0
- package/dist/board/Attention.js +122 -0
- package/dist/board/Attention.js.map +1 -0
- package/dist/board/BoardShell.d.ts +16 -0
- package/dist/board/BoardShell.js +35 -0
- package/dist/board/BoardShell.js.map +1 -0
- package/dist/board/BoardTitleBar.d.ts +50 -0
- package/dist/board/BoardTitleBar.js +125 -0
- package/dist/board/BoardTitleBar.js.map +1 -0
- package/dist/board/ButtonKinds.d.ts +24 -0
- package/dist/board/ButtonKinds.js +19 -0
- package/dist/board/ButtonKinds.js.map +1 -0
- package/dist/board/Cleanup.d.ts +8 -0
- package/dist/board/Cleanup.js +15 -0
- package/dist/board/Cleanup.js.map +1 -0
- package/dist/board/Counters.d.ts +101 -0
- package/dist/board/Counters.js +202 -0
- package/dist/board/Counters.js.map +1 -0
- package/dist/board/DataAttributes.d.ts +6 -0
- package/dist/board/Dependencies.d.ts +17 -0
- package/dist/board/Dependencies.js +44 -0
- package/dist/board/Dependencies.js.map +1 -0
- package/dist/board/Desk.d.ts +52 -0
- package/dist/board/Desk.js +130 -0
- package/dist/board/Desk.js.map +1 -0
- package/dist/board/Dossier.d.ts +100 -0
- package/dist/board/Dossier.js +194 -0
- package/dist/board/Dossier.js.map +1 -0
- package/dist/board/Filters.d.ts +33 -0
- package/dist/board/Filters.js +64 -0
- package/dist/board/Filters.js.map +1 -0
- package/dist/board/ItemDetails.d.ts +167 -0
- package/dist/board/ItemDetails.js +339 -0
- package/dist/board/ItemDetails.js.map +1 -0
- package/dist/board/ItemDialog.d.ts +73 -0
- package/dist/board/ItemDialog.js +140 -0
- package/dist/board/ItemDialog.js.map +1 -0
- package/dist/board/ItemParts.d.ts +42 -0
- package/dist/board/ItemParts.js +82 -0
- package/dist/board/ItemParts.js.map +1 -0
- package/dist/board/ItemProgress.d.ts +56 -0
- package/dist/board/ItemProgress.js +138 -0
- package/dist/board/ItemProgress.js.map +1 -0
- package/dist/board/List.d.ts +65 -0
- package/dist/board/List.js +118 -0
- package/dist/board/List.js.map +1 -0
- package/dist/board/LoadingScreen.d.ts +38 -0
- package/dist/board/LoadingScreen.js +74 -0
- package/dist/board/LoadingScreen.js.map +1 -0
- package/dist/board/NeedsYou.d.ts +13 -0
- package/dist/board/NeedsYou.js +31 -0
- package/dist/board/NeedsYou.js.map +1 -0
- package/dist/board/Onboarding.d.ts +53 -0
- package/dist/board/Onboarding.js +122 -0
- package/dist/board/Onboarding.js.map +1 -0
- package/dist/board/PartsList.d.ts +60 -0
- package/dist/board/PartsList.js +115 -0
- package/dist/board/PartsList.js.map +1 -0
- package/dist/board/ProjectIcon.d.ts +51 -0
- package/dist/board/ProjectIcon.js +120 -0
- package/dist/board/ProjectIcon.js.map +1 -0
- package/dist/board/PullRequests.d.ts +43 -0
- package/dist/board/PullRequests.js +83 -0
- package/dist/board/PullRequests.js.map +1 -0
- package/dist/board/RowStyles.js +14 -0
- package/dist/board/RowStyles.js.map +1 -0
- package/dist/board/Settings.d.ts +54 -0
- package/dist/board/Settings.js +112 -0
- package/dist/board/Settings.js.map +1 -0
- package/dist/board/Slices.d.ts +32 -0
- package/dist/board/Slices.js +88 -0
- package/dist/board/Slices.js.map +1 -0
- package/dist/board/SourceIcon.d.ts +12 -0
- package/dist/board/SourceIcon.js +24 -0
- package/dist/board/SourceIcon.js.map +1 -0
- package/dist/board/StagePane.d.ts +43 -0
- package/dist/board/StagePane.js +97 -0
- package/dist/board/StagePane.js.map +1 -0
- package/dist/board/StageRow.d.ts +141 -0
- package/dist/board/StageRow.js +287 -0
- package/dist/board/StageRow.js.map +1 -0
- package/dist/board/StageTrack.d.ts +18 -0
- package/dist/board/StageTrack.js +38 -0
- package/dist/board/StageTrack.js.map +1 -0
- package/dist/board/StartControls.d.ts +30 -0
- package/dist/board/StartControls.js +51 -0
- package/dist/board/StartControls.js.map +1 -0
- package/dist/board/StatusColors.d.ts +5 -0
- package/dist/board/StatusColors.js +17 -0
- package/dist/board/StatusColors.js.map +1 -0
- package/dist/board/StatusGlyph.d.ts +18 -0
- package/dist/board/StatusGlyph.js +51 -0
- package/dist/board/StatusGlyph.js.map +1 -0
- package/dist/board/Threads.d.ts +13 -0
- package/dist/board/Threads.js +35 -0
- package/dist/board/Threads.js.map +1 -0
- package/dist/board/TrackerLabels.d.ts +27 -0
- package/dist/board/TrackerLabels.js +56 -0
- package/dist/board/TrackerLabels.js.map +1 -0
- package/dist/board/UpdatePill.d.ts +52 -0
- package/dist/board/UpdatePill.js +188 -0
- package/dist/board/UpdatePill.js.map +1 -0
- package/dist/board/Vocabulary.d.ts +56 -0
- package/dist/board/Vocabulary.js +104 -0
- package/dist/board/Vocabulary.js.map +1 -0
- package/dist/board/Warnings.d.ts +35 -0
- package/dist/board/Warnings.js +85 -0
- package/dist/board/Warnings.js.map +1 -0
- package/dist/board/WorkMap.d.ts +136 -0
- package/dist/board/WorkMap.js +274 -0
- package/dist/board/WorkMap.js.map +1 -0
- package/dist/board/index.d.ts +42 -0
- package/dist/collections/CollectionState.d.ts +10 -1
- package/dist/collections/CollectionState.js +12 -5
- package/dist/collections/CollectionState.js.map +1 -1
- package/dist/collections/ResultList.js +1 -1
- package/dist/collections/ScrollRow.js +1 -1
- package/dist/collections/VirtualList.js +1 -1
- package/dist/display/Avatar.d.ts +5 -2
- package/dist/display/Avatar.js +2 -2
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Badge.d.ts +6 -1
- package/dist/display/Badge.js +9 -4
- package/dist/display/Badge.js.map +1 -1
- package/dist/display/CheckList.js +1 -1
- package/dist/display/Comment.d.ts +12 -0
- package/dist/display/Comment.js +39 -0
- package/dist/display/Comment.js.map +1 -0
- package/dist/display/CopyButton.d.ts +10 -2
- package/dist/display/CopyButton.js +29 -16
- package/dist/display/CopyButton.js.map +1 -1
- package/dist/display/Diagram.d.ts +17 -0
- package/dist/display/Diagram.js +49 -0
- package/dist/display/Diagram.js.map +1 -0
- package/dist/display/ImagePicker.js +1 -1
- package/dist/display/InfoBox.js +2 -2
- package/dist/display/InsetPanel.d.ts +3 -1
- package/dist/display/InsetPanel.js +7 -4
- package/dist/display/InsetPanel.js.map +1 -1
- package/dist/display/ItemAction.js +2 -2
- package/dist/display/LabelChip.d.ts +26 -0
- package/dist/display/LabelChip.js +41 -0
- package/dist/display/LabelChip.js.map +1 -0
- package/dist/display/LabelTint.js +60 -0
- package/dist/display/LabelTint.js.map +1 -0
- package/dist/display/Person.d.ts +5 -0
- package/dist/display/Person.js +19 -0
- package/dist/display/Person.js.map +1 -0
- package/dist/display/PropertySheet.js +1 -1
- package/dist/display/RadioChoice.d.ts +9 -2
- package/dist/display/RadioChoice.js +5 -1
- package/dist/display/RadioChoice.js.map +1 -1
- package/dist/display/RatingLink.js +1 -1
- package/dist/display/RichText.d.ts +12 -0
- package/dist/display/RichText.js +94 -0
- package/dist/display/RichText.js.map +1 -0
- package/dist/display/RowList.js +1 -1
- package/dist/display/index.d.ts +6 -1
- package/dist/feedback/Alert.js +1 -1
- package/dist/feedback/AsyncState.js +1 -1
- package/dist/feedback/DismissButton.d.ts +12 -0
- package/dist/feedback/DismissButton.js +20 -0
- package/dist/feedback/DismissButton.js.map +1 -0
- package/dist/feedback/ErrorNotification.js +2 -0
- package/dist/feedback/ErrorNotification.js.map +1 -1
- package/dist/feedback/FormResult.d.ts +16 -0
- package/dist/feedback/FormResult.js +30 -0
- package/dist/feedback/FormResult.js.map +1 -0
- package/dist/feedback/LiveRegion.d.ts +7 -0
- package/dist/feedback/LiveRegion.js +15 -0
- package/dist/feedback/LiveRegion.js.map +1 -0
- package/dist/feedback/Loading.d.ts +19 -0
- package/dist/feedback/Loading.js +63 -0
- package/dist/feedback/Loading.js.map +1 -0
- package/dist/feedback/Notifications.d.ts +35 -1
- package/dist/feedback/Notifications.js +196 -4
- package/dist/feedback/Notifications.js.map +1 -1
- package/dist/feedback/Progress.d.ts +15 -0
- package/dist/feedback/Progress.js +38 -0
- package/dist/feedback/Progress.js.map +1 -0
- package/dist/feedback/ProgressStatusRow.js +1 -1
- package/dist/feedback/StatusIndicator.d.ts +6 -1
- package/dist/feedback/StatusIndicator.js +19 -5
- package/dist/feedback/StatusIndicator.js.map +1 -1
- package/dist/feedback/index.d.ts +8 -4
- package/dist/forms/Checkbox.js +1 -1
- package/dist/forms/ColorSwatches.d.ts +21 -0
- package/dist/forms/ColorSwatches.js +26 -0
- package/dist/forms/ColorSwatches.js.map +1 -0
- package/dist/forms/FormSection.d.ts +68 -6
- package/dist/forms/FormSection.js +139 -8
- package/dist/forms/FormSection.js.map +1 -1
- package/dist/forms/HiddenField.d.ts +6 -0
- package/dist/forms/HiddenField.js +13 -0
- package/dist/forms/HiddenField.js.map +1 -0
- package/dist/forms/Input.d.ts +12 -2
- package/dist/forms/Input.js +10 -3
- package/dist/forms/Input.js.map +1 -1
- package/dist/forms/LabelPicker.d.ts +43 -0
- package/dist/forms/LabelPicker.js +107 -0
- package/dist/forms/LabelPicker.js.map +1 -0
- package/dist/forms/MarkdownEditor.d.ts +21 -0
- package/dist/forms/MarkdownEditor.js +175 -0
- package/dist/forms/MarkdownEditor.js.map +1 -0
- package/dist/forms/MarkdownToolbar.js +204 -0
- package/dist/forms/MarkdownToolbar.js.map +1 -0
- package/dist/forms/OptionSelect.d.ts +30 -9
- package/dist/forms/OptionSelect.js +53 -13
- package/dist/forms/OptionSelect.js.map +1 -1
- package/dist/forms/Select.d.ts +4 -1
- package/dist/forms/Select.js +3 -1
- package/dist/forms/Select.js.map +1 -1
- package/dist/forms/Switch.d.ts +4 -1
- package/dist/forms/Switch.js +5 -2
- package/dist/forms/Switch.js.map +1 -1
- package/dist/forms/TagInput.js +1 -1
- package/dist/forms/index.d.ts +7 -3
- package/dist/foundations/AmbientMotion.d.ts +2 -0
- package/dist/foundations/AmbientMotion.js +39 -0
- package/dist/foundations/AmbientMotion.js.map +1 -0
- package/dist/foundations/DesignSystemProvider.d.ts +34 -0
- package/dist/foundations/DesignSystemProvider.js +26 -1
- package/dist/foundations/DesignSystemProvider.js.map +1 -1
- package/dist/foundations/index.d.ts +2 -1
- package/dist/icons/BrandIcons.d.ts +8 -2
- package/dist/icons/BrandIcons.js +14 -3
- package/dist/icons/BrandIcons.js.map +1 -1
- package/dist/icons/dynamic.d.ts +2 -0
- package/dist/icons/dynamic.js +1 -0
- package/dist/icons/index.d.ts +2 -2
- package/dist/icons/index.js +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +71 -10
- package/dist/index.js +78 -21
- package/dist/layout/Site.js +1 -1
- package/dist/layout/TitleBar.js +1 -1
- package/dist/layout/index.d.ts +2 -2
- package/dist/layout/index.js +3 -3
- package/dist/layout/toolbar/CommandPaletteTrigger.d.ts +17 -2
- package/dist/layout/toolbar/CommandPaletteTrigger.js +89 -6
- package/dist/layout/toolbar/CommandPaletteTrigger.js.map +1 -1
- package/dist/layout/toolbar/TriggerFit.js +56 -0
- package/dist/layout/toolbar/TriggerFit.js.map +1 -0
- package/dist/navigation/CollectionBrowseSelector.js +1 -1
- package/dist/navigation/CollectionViewToggle.d.ts +26 -0
- package/dist/navigation/CollectionViewToggle.js +54 -23
- package/dist/navigation/CollectionViewToggle.js.map +1 -1
- package/dist/navigation/DrilldownBrowseSelector.js +1 -1
- package/dist/navigation/FolderSelector.js +2 -2
- package/dist/navigation/SortMenu.d.ts +10 -0
- package/dist/navigation/SortMenu.js +33 -0
- package/dist/navigation/SortMenu.js.map +1 -0
- package/dist/navigation/index.d.ts +3 -2
- package/dist/overlays/CommandPalette.d.ts +38 -4
- package/dist/overlays/CommandPalette.js +140 -54
- package/dist/overlays/CommandPalette.js.map +1 -1
- package/dist/overlays/Drawer.js +1 -1
- package/dist/overlays/DropdownMenu.d.ts +7 -1
- package/dist/overlays/DropdownMenu.js +13 -2
- package/dist/overlays/DropdownMenu.js.map +1 -1
- package/dist/overlays/HelpTip.d.ts +8 -0
- package/dist/overlays/HelpTip.js +29 -0
- package/dist/overlays/HelpTip.js.map +1 -0
- package/dist/overlays/ImageViewer.js +2 -2
- package/dist/overlays/Modal.d.ts +5 -0
- package/dist/overlays/Modal.js +17 -2
- package/dist/overlays/Modal.js.map +1 -1
- package/dist/overlays/ModalExpandButton.d.ts +18 -0
- package/dist/overlays/ModalExpandButton.js +43 -0
- package/dist/overlays/ModalExpandButton.js.map +1 -0
- package/dist/overlays/Popover.d.ts +2 -1
- package/dist/overlays/Popover.js +1 -0
- package/dist/overlays/Popover.js.map +1 -1
- package/dist/overlays/index.d.ts +4 -2
- package/dist/showcase/Cards.js +1 -1
- package/dist/showcase/FeatureRow.js +1 -1
- package/dist/showcase/PhotoSearchConcept.js +1 -1
- package/dist/showcase/ProductBand.js +1 -1
- package/dist/showcase/Status.js +1 -1
- package/dist/typography/Code.d.ts +18 -0
- package/dist/typography/Code.js +24 -0
- package/dist/typography/Code.js.map +1 -0
- package/dist/typography/Link.d.ts +7 -2
- package/dist/typography/Link.js +15 -5
- package/dist/typography/Link.js.map +1 -1
- package/dist/typography/Typography.d.ts +19 -1
- package/dist/typography/Typography.js +11 -3
- package/dist/typography/Typography.js.map +1 -1
- package/dist/typography/index.d.ts +3 -3
- package/package.json +26 -3
- package/src/actions/Button.tsx +78 -11
- package/src/actions/ButtonLink.tsx +34 -0
- package/src/actions/ButtonRecipes.ts +10 -0
- package/src/actions/IconButton.tsx +7 -4
- package/src/board/ActivityLine.tsx +176 -0
- package/src/board/ActivityTrack.tsx +23 -0
- package/src/board/AddItem.tsx +259 -0
- package/src/board/Archive.tsx +75 -0
- package/src/board/Attention.tsx +135 -0
- package/src/board/BoardShell.tsx +30 -0
- package/src/board/BoardTitleBar.tsx +167 -0
- package/src/board/ButtonKinds.ts +32 -0
- package/src/board/Cleanup.tsx +19 -0
- package/src/board/Counters.tsx +224 -0
- package/src/board/DataAttributes.ts +3 -0
- package/src/board/Dependencies.tsx +49 -0
- package/src/board/Desk.tsx +155 -0
- package/src/board/Dossier.tsx +262 -0
- package/src/board/Filters.tsx +85 -0
- package/src/board/ItemDetails.tsx +435 -0
- package/src/board/ItemDialog.tsx +179 -0
- package/src/board/ItemParts.tsx +128 -0
- package/src/board/ItemProgress.tsx +164 -0
- package/src/board/List.tsx +154 -0
- package/src/board/LoadingScreen.tsx +78 -0
- package/src/board/NeedsYou.tsx +30 -0
- package/src/board/Onboarding.tsx +124 -0
- package/src/board/PartsList.tsx +160 -0
- package/src/board/ProjectIcon.tsx +172 -0
- package/src/board/PullRequests.tsx +106 -0
- package/src/board/RowStyles.ts +17 -0
- package/src/board/Settings.tsx +124 -0
- package/src/board/Slices.tsx +103 -0
- package/src/board/SourceIcon.tsx +33 -0
- package/src/board/StagePane.tsx +128 -0
- package/src/board/StageRow.tsx +320 -0
- package/src/board/StageTrack.tsx +61 -0
- package/src/board/StartControls.tsx +77 -0
- package/src/board/StatusColors.ts +28 -0
- package/src/board/StatusGlyph.tsx +70 -0
- package/src/board/Threads.tsx +38 -0
- package/src/board/TrackerLabels.tsx +75 -0
- package/src/board/UpdatePill.tsx +222 -0
- package/src/board/Vocabulary.tsx +155 -0
- package/src/board/Warnings.tsx +83 -0
- package/src/board/WorkMap.tsx +436 -0
- package/src/board/base.css +121 -0
- package/src/board/button-kinds.css +8 -0
- package/src/board/index.ts +224 -0
- package/src/board/items.css +75 -0
- package/src/board/loading-screen.css +171 -0
- package/src/board/rows.css +180 -0
- package/src/board/title-bar.css +56 -0
- package/src/board/tokens.css +52 -0
- package/src/board.css +19 -0
- package/src/collections/CollectionState.tsx +20 -7
- package/src/display/Avatar.tsx +6 -3
- package/src/display/Badge.tsx +25 -3
- package/src/display/Comment.tsx +26 -0
- package/src/display/CopyButton.tsx +48 -18
- package/src/display/Diagram.tsx +59 -0
- package/src/display/InsetPanel.tsx +4 -1
- package/src/display/LabelChip.tsx +73 -0
- package/src/display/LabelTint.ts +72 -0
- package/src/display/Person.tsx +11 -0
- package/src/display/RadioChoice.tsx +11 -2
- package/src/display/RichText.tsx +114 -0
- package/src/display/index.ts +5 -0
- package/src/feedback/DismissButton.tsx +28 -0
- package/src/feedback/ErrorNotification.ts +3 -0
- package/src/feedback/FormResult.tsx +50 -0
- package/src/feedback/LiveRegion.tsx +10 -0
- package/src/feedback/Loading.tsx +75 -0
- package/src/feedback/Notifications.tsx +267 -2
- package/src/feedback/Progress.tsx +44 -0
- package/src/feedback/StatusIndicator.tsx +34 -3
- package/src/feedback/index.ts +12 -2
- package/src/forms/ColorSwatches.tsx +50 -0
- package/src/forms/FormSection.tsx +245 -10
- package/src/forms/HiddenField.tsx +4 -0
- package/src/forms/Input.tsx +26 -4
- package/src/forms/LabelPicker.tsx +140 -0
- package/src/forms/MarkdownEditor.tsx +283 -0
- package/src/forms/MarkdownToolbar.tsx +267 -0
- package/src/forms/OptionSelect.tsx +110 -25
- package/src/forms/Select.tsx +20 -13
- package/src/forms/Switch.tsx +16 -2
- package/src/forms/index.ts +16 -2
- package/src/foundations/AmbientMotion.ts +51 -0
- package/src/foundations/DesignSystemProvider.tsx +19 -0
- package/src/foundations/index.ts +1 -0
- package/src/foundations/theme.css +64 -0
- package/src/icons/BrandIcons.tsx +12 -3
- package/src/icons/dynamic.ts +4 -0
- package/src/icons/index.ts +1 -1
- package/src/index.ts +2 -1
- package/src/layout/index.ts +1 -1
- package/src/layout/toolbar/CommandPaletteTrigger.tsx +187 -31
- package/src/layout/toolbar/TriggerFit.ts +84 -0
- package/src/navigation/CollectionViewToggle.tsx +94 -24
- package/src/navigation/SortMenu.tsx +41 -0
- package/src/navigation/index.ts +2 -1
- package/src/overlays/CommandPalette.tsx +190 -60
- package/src/overlays/DropdownMenu.tsx +12 -1
- package/src/overlays/HelpTip.tsx +29 -0
- package/src/overlays/Modal.tsx +23 -0
- package/src/overlays/ModalExpandButton.tsx +48 -0
- package/src/overlays/Popover.tsx +3 -1
- package/src/overlays/index.ts +9 -1
- package/src/showcase.css +6 -0
- package/src/styles.css +9 -1
- package/src/typography/Code.tsx +24 -0
- package/src/typography/Link.tsx +19 -10
- package/src/typography/Typography.tsx +25 -2
- package/src/typography/index.ts +1 -1
- package/dist/feedback/index.js +0 -11
- package/dist/typography/index.js +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* oxlint-disable react/only-export-components -- The option hook belongs to the palette. */
|
|
2
2
|
|
|
3
3
|
import { Modal as NinnaModal } from '@ninna-ui/overlays';
|
|
4
|
-
import { SearchIcon, XIcon } from 'lucide-react';
|
|
4
|
+
import { CheckIcon, SearchIcon, XIcon } from 'lucide-react';
|
|
5
5
|
import { createContext, type ReactNode, useContext, useEffect, useId, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { IconButton } from '../actions/IconButton';
|
|
@@ -11,6 +11,16 @@ import { Text } from '../typography/Typography';
|
|
|
11
11
|
import { useModalAutoFocus } from './useModalAutoFocus';
|
|
12
12
|
import { useScrollFade } from './useScrollFade';
|
|
13
13
|
|
|
14
|
+
/** A chip before the field for something that narrows the list, such as a filter. */
|
|
15
|
+
export interface CommandPaletteToken {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
/** An icon or colour mark before the label. */
|
|
19
|
+
leading?: ReactNode;
|
|
20
|
+
/** A button on the chip that takes it away; the focus stays in the field. */
|
|
21
|
+
remove?: { label: string; onRemove: () => void };
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
interface CommandPaletteProps {
|
|
15
25
|
/** The dialog's accessible title and description; the field says the rest. */
|
|
16
26
|
label: string;
|
|
@@ -20,13 +30,23 @@ interface CommandPaletteProps {
|
|
|
20
30
|
onQueryChange: (query: string) => void;
|
|
21
31
|
/** Narrows the list to one level, shown as a chip before the field; Backspace in an empty field leaves it. */
|
|
22
32
|
scope?: { label: string; leaveLabel: string; onLeave: () => void };
|
|
33
|
+
/** Chips before the field, such as active filters, which wrap and scroll on their own when there are many. A
|
|
34
|
+
* palette given tokens, even none, is a filtering search, as wide as its chips need. */
|
|
35
|
+
tokens?: readonly CommandPaletteToken[];
|
|
36
|
+
/** Backspace in an empty field, such as to take the last token away. */
|
|
37
|
+
onBackspaceEmpty?: () => void;
|
|
38
|
+
/** A control after the field, such as Clear. A press on it leaves the focus in the field. */
|
|
39
|
+
trailing?: ReactNode;
|
|
23
40
|
/** The options' values in list order, for the arrow keys and Enter. */
|
|
24
41
|
options: readonly string[];
|
|
25
|
-
|
|
42
|
+
/** May return the option the highlight moves to, when choosing changes the list. */
|
|
43
|
+
onSelect: (value: string) => string | void;
|
|
26
44
|
onClose: () => void;
|
|
27
45
|
listLabel: string;
|
|
46
|
+
/** Marks the list busy while more may arrive; `busyLabel` shows under it meanwhile. */
|
|
28
47
|
busy?: boolean;
|
|
29
|
-
|
|
48
|
+
busyLabel?: ReactNode;
|
|
49
|
+
/** Shown when nothing is listed and nothing is pending. */
|
|
30
50
|
empty?: ReactNode;
|
|
31
51
|
/** Quiet key hints under the list, on screens with a keyboard. */
|
|
32
52
|
footer?: ReactNode;
|
|
@@ -37,10 +57,12 @@ const OptionContext = createContext<{
|
|
|
37
57
|
listId: string;
|
|
38
58
|
activeValue: string | undefined;
|
|
39
59
|
setActiveValue: (value: string) => void;
|
|
60
|
+
select: (value: string) => void;
|
|
40
61
|
} | null>(null);
|
|
41
62
|
|
|
42
63
|
/** T3 Code's command palette: a field over one keyboard list of grouped options, as a dialog over the page
|
|
43
|
-
* or, on phones, the whole screen. The arrow keys move through `options`, Enter selects, Escape closes.
|
|
64
|
+
* or, on phones, the whole screen. The arrow keys move through `options`, Enter selects, Escape closes. Tokens
|
|
65
|
+
* before the field say what narrows the list. */
|
|
44
66
|
export function CommandPalette({
|
|
45
67
|
label,
|
|
46
68
|
description,
|
|
@@ -48,11 +70,15 @@ export function CommandPalette({
|
|
|
48
70
|
query,
|
|
49
71
|
onQueryChange,
|
|
50
72
|
scope,
|
|
73
|
+
tokens: givenTokens,
|
|
74
|
+
onBackspaceEmpty,
|
|
75
|
+
trailing,
|
|
51
76
|
options,
|
|
52
77
|
onSelect,
|
|
53
78
|
onClose,
|
|
54
79
|
listLabel,
|
|
55
80
|
busy = false,
|
|
81
|
+
busyLabel,
|
|
56
82
|
empty,
|
|
57
83
|
footer,
|
|
58
84
|
children,
|
|
@@ -65,6 +91,19 @@ export function CommandPalette({
|
|
|
65
91
|
const [chosen, setActiveValue] = useState<string>();
|
|
66
92
|
// The highlight follows the list: when the chosen option leaves it, the first option takes over.
|
|
67
93
|
const activeValue = chosen !== undefined && options.includes(chosen) ? chosen : options[0];
|
|
94
|
+
const select = (value: string) => {
|
|
95
|
+
const next = onSelect(value);
|
|
96
|
+
|
|
97
|
+
if (next) setActiveValue(next);
|
|
98
|
+
};
|
|
99
|
+
// A scope is the one token of a palette that narrows by levels.
|
|
100
|
+
const tokens: readonly CommandPaletteToken[] = scope
|
|
101
|
+
? [{ id: 'scope', label: scope.label, remove: { label: scope.leaveLabel, onRemove: scope.onLeave } }]
|
|
102
|
+
: (givenTokens ?? []);
|
|
103
|
+
const leaveLast = scope ? scope.onLeave : onBackspaceEmpty;
|
|
104
|
+
// A palette that takes tokens, or a trailing control, is a filtering search: wide, with its field wrapping onto
|
|
105
|
+
// rows under the tokens, from the top.
|
|
106
|
+
const rows = givenTokens !== undefined || Boolean(trailing);
|
|
68
107
|
|
|
69
108
|
useEffect(() => {
|
|
70
109
|
if (activeValue) document.getElementById(optionId(listId, activeValue))?.scrollIntoView({ block: 'nearest' });
|
|
@@ -77,75 +116,115 @@ export function CommandPalette({
|
|
|
77
116
|
description={description}
|
|
78
117
|
centered={false}
|
|
79
118
|
{...focusProps}
|
|
80
|
-
|
|
119
|
+
data-command-palette
|
|
120
|
+
// The dialog listens for Escape on the document, past the field's own keys; an input method's Escape
|
|
121
|
+
// only ends its composition.
|
|
122
|
+
onEscapeKeyDown={(event) => {
|
|
123
|
+
if (event.isComposing) event.preventDefault();
|
|
124
|
+
}}
|
|
125
|
+
className={`flex max-h-[min(36rem,calc(100dvh-6rem))] w-[calc(100vw-2rem)] max-w-2xl flex-col overflow-hidden p-0 shadow-(--lar-shadow-dialog) max-sm:top-0 max-sm:left-0 max-sm:h-dvh max-sm:max-h-none max-sm:w-full max-sm:max-w-none max-sm:translate-x-0 max-sm:rounded-none! max-sm:border-0 max-sm:pt-[env(safe-area-inset-top)] ${rows ? 'max-sm:pb-[env(safe-area-inset-bottom)] sm:w-[calc(100vw-2rem)] sm:max-w-2xl' : ''}`}
|
|
81
126
|
>
|
|
82
|
-
<div
|
|
127
|
+
<div
|
|
128
|
+
className={`border-base-300 [&>svg]:text-base-600 flex shrink-0 gap-2 border-b px-4 [&>svg]:size-4 [&>svg]:shrink-0 ${rows ? 'items-start py-2 [&>svg]:mt-2' : 'items-center py-3'}`}
|
|
129
|
+
>
|
|
83
130
|
<SearchIcon />
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
aria-label={scope.leaveLabel}
|
|
91
|
-
className="hover:bg-primary/15 rounded-sm p-0.5 [&>svg]:size-3"
|
|
92
|
-
onClick={() => {
|
|
93
|
-
scope.onLeave();
|
|
94
|
-
input.current?.focus();
|
|
95
|
-
}}
|
|
131
|
+
<div className={`flex min-w-0 flex-1 items-center gap-x-2 ${rows ? 'flex-wrap gap-y-1' : ''}`}>
|
|
132
|
+
{tokens.length > 0 ? (
|
|
133
|
+
// Many tokens scroll on their own, so the field stays in view however many there are. Under
|
|
134
|
+
// a coarse pointer each remove button is 44 px, reaching into the gap between rows.
|
|
135
|
+
<ul
|
|
136
|
+
className={`flex max-w-full min-w-0 shrink-0 items-center gap-1 ${rows ? 'max-h-19 flex-wrap overflow-y-auto overscroll-contain py-1 pointer-coarse:max-h-22 pointer-coarse:gap-y-3 pointer-coarse:py-1.5' : ''}`}
|
|
96
137
|
>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
{tokens.map((token) => (
|
|
139
|
+
<li
|
|
140
|
+
key={token.id}
|
|
141
|
+
className={`bg-primary/12 text-primary flex max-w-full min-w-0 shrink-0 items-center gap-1 rounded-md py-0.5 pr-1 pl-2 text-xs font-medium ${rows ? 'dark:text-base-content pointer-coarse:min-h-8 pointer-coarse:py-0 pointer-coarse:pr-0' : ''}`}
|
|
142
|
+
>
|
|
143
|
+
{token.leading ? (
|
|
144
|
+
<span className="flex shrink-0 items-center [&>svg]:size-3.5">
|
|
145
|
+
{token.leading}
|
|
146
|
+
</span>
|
|
147
|
+
) : null}
|
|
148
|
+
<span className="min-w-0 truncate">{token.label}</span>
|
|
149
|
+
{token.remove ? (
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
// A scope's chip leaves with Backspace alone; several tokens' buttons are tab stops.
|
|
153
|
+
tabIndex={scope ? -1 : undefined}
|
|
154
|
+
aria-label={token.remove.label}
|
|
155
|
+
className={`hover:bg-primary/15 grid shrink-0 place-items-center rounded-sm p-0.5 [&>svg]:size-3 ${rows ? 'pointer-coarse:-my-1.5 pointer-coarse:size-11 pointer-coarse:p-0' : ''}`}
|
|
156
|
+
onClick={() => {
|
|
157
|
+
token.remove?.onRemove();
|
|
158
|
+
input.current?.focus();
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<XIcon />
|
|
162
|
+
</button>
|
|
163
|
+
) : null}
|
|
164
|
+
</li>
|
|
165
|
+
))}
|
|
166
|
+
</ul>
|
|
167
|
+
) : null}
|
|
168
|
+
<input
|
|
169
|
+
ref={input}
|
|
170
|
+
role="combobox"
|
|
171
|
+
aria-label={label}
|
|
172
|
+
aria-expanded
|
|
173
|
+
aria-controls={listId}
|
|
174
|
+
aria-autocomplete="list"
|
|
175
|
+
aria-activedescendant={activeValue ? optionId(listId, activeValue) : undefined}
|
|
176
|
+
aria-busy={busy}
|
|
177
|
+
placeholder={placeholder}
|
|
178
|
+
value={query}
|
|
179
|
+
className={`text-base-content placeholder:text-base-600 min-w-0 flex-1 bg-transparent text-base outline-none sm:text-sm ${rows ? 'h-8 min-w-32' : ''}`}
|
|
180
|
+
onChange={(event) => onQueryChange(event.target.value)}
|
|
181
|
+
onKeyDown={(event) => {
|
|
182
|
+
if (event.nativeEvent.isComposing) return;
|
|
183
|
+
|
|
184
|
+
if (event.key === 'Backspace' && !query && leaveLast) {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
leaveLast();
|
|
187
|
+
} else if ((event.key === 'ArrowDown' || event.key === 'ArrowUp') && options.length) {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
|
|
190
|
+
const index = activeValue ? options.indexOf(activeValue) : -1;
|
|
191
|
+
const step = event.key === 'ArrowDown' ? 1 : -1;
|
|
192
|
+
|
|
193
|
+
setActiveValue(options[(index + step + options.length) % options.length]!);
|
|
194
|
+
} else if (event.key === 'Enter' && activeValue) {
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
select(activeValue);
|
|
197
|
+
}
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
{trailing ? (
|
|
202
|
+
// Under a coarse pointer the control is 44 px, as a token's remove button is.
|
|
203
|
+
<div
|
|
204
|
+
className="flex min-h-8 shrink-0 items-center pointer-coarse:[&_[data-slot=button]]:h-11!"
|
|
205
|
+
onClick={() => input.current?.focus()}
|
|
206
|
+
>
|
|
207
|
+
{trailing}
|
|
208
|
+
</div>
|
|
100
209
|
) : null}
|
|
101
|
-
<input
|
|
102
|
-
ref={input}
|
|
103
|
-
role="combobox"
|
|
104
|
-
aria-label={label}
|
|
105
|
-
aria-expanded
|
|
106
|
-
aria-controls={listId}
|
|
107
|
-
aria-autocomplete="list"
|
|
108
|
-
aria-activedescendant={activeValue ? optionId(listId, activeValue) : undefined}
|
|
109
|
-
aria-busy={busy}
|
|
110
|
-
placeholder={placeholder}
|
|
111
|
-
value={query}
|
|
112
|
-
className="text-base-content placeholder:text-base-600 min-w-0 flex-1 bg-transparent text-base outline-none sm:text-sm"
|
|
113
|
-
onChange={(event) => onQueryChange(event.target.value)}
|
|
114
|
-
onKeyDown={(event) => {
|
|
115
|
-
if (event.nativeEvent.isComposing) return;
|
|
116
|
-
|
|
117
|
-
if (event.key === 'Backspace' && !query && scope) {
|
|
118
|
-
event.preventDefault();
|
|
119
|
-
scope.onLeave();
|
|
120
|
-
} else if ((event.key === 'ArrowDown' || event.key === 'ArrowUp') && options.length) {
|
|
121
|
-
event.preventDefault();
|
|
122
|
-
|
|
123
|
-
const index = activeValue ? options.indexOf(activeValue) : -1;
|
|
124
|
-
const step = event.key === 'ArrowDown' ? 1 : -1;
|
|
125
|
-
|
|
126
|
-
setActiveValue(options[(index + step + options.length) % options.length]!);
|
|
127
|
-
} else if (event.key === 'Enter' && activeValue) {
|
|
128
|
-
event.preventDefault();
|
|
129
|
-
onSelect(activeValue);
|
|
130
|
-
}
|
|
131
|
-
}}
|
|
132
|
-
/>
|
|
133
210
|
<span className="flex sm:hidden">
|
|
134
211
|
<IconButton aria-label={labels.close} icon={<XIcon />} onClick={onClose} variant="header" />
|
|
135
212
|
</span>
|
|
136
213
|
</div>
|
|
137
214
|
<div ref={setViewport} className={`min-h-0 flex-1 overflow-y-auto overscroll-contain p-2 ${className}`}>
|
|
138
215
|
<div ref={setContent} className="flow-root">
|
|
139
|
-
<OptionContext.Provider value={{ listId, activeValue, setActiveValue }}>
|
|
216
|
+
<OptionContext.Provider value={{ listId, activeValue, setActiveValue, select }}>
|
|
140
217
|
<ResultList id={listId} label={listLabel} density="compact" aria-busy={busy}>
|
|
141
218
|
{children}
|
|
142
219
|
</ResultList>
|
|
143
220
|
</OptionContext.Provider>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
221
|
+
<div role="status">
|
|
222
|
+
{(busy && busyLabel) || (options.length === 0 && empty) ? (
|
|
223
|
+
<Text as="div" tone="muted" className="px-3 py-8 text-center">
|
|
224
|
+
{busy && busyLabel ? busyLabel : empty}
|
|
225
|
+
</Text>
|
|
226
|
+
) : null}
|
|
227
|
+
</div>
|
|
149
228
|
</div>
|
|
150
229
|
</div>
|
|
151
230
|
{footer ? (
|
|
@@ -178,6 +257,57 @@ export function CommandPaletteGroup({ label, children }: { label: string; childr
|
|
|
178
257
|
);
|
|
179
258
|
}
|
|
180
259
|
|
|
260
|
+
/** One line of the palette's list: an icon, the name, an optional hint (searched with the name) and meta (on wider
|
|
261
|
+
* screens), and a tick while `checked`, as a filter that is on. Choosing it keeps the focus in the field. */
|
|
262
|
+
export function CommandPaletteOption({
|
|
263
|
+
value,
|
|
264
|
+
icon,
|
|
265
|
+
name,
|
|
266
|
+
hint,
|
|
267
|
+
meta,
|
|
268
|
+
checked,
|
|
269
|
+
}: {
|
|
270
|
+
value: string;
|
|
271
|
+
icon: ReactNode;
|
|
272
|
+
name: string;
|
|
273
|
+
hint?: string;
|
|
274
|
+
meta?: string;
|
|
275
|
+
/** Whether a filter's option is on; other options leave it out. */
|
|
276
|
+
checked?: boolean;
|
|
277
|
+
}) {
|
|
278
|
+
const context = useContext(OptionContext);
|
|
279
|
+
|
|
280
|
+
if (!context) throw new Error('CommandPaletteOption must be used inside CommandPalette');
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div
|
|
284
|
+
id={optionId(context.listId, value)}
|
|
285
|
+
role="option"
|
|
286
|
+
aria-selected={context.activeValue === value}
|
|
287
|
+
aria-checked={checked}
|
|
288
|
+
tabIndex={-1}
|
|
289
|
+
onPointerMove={() => {
|
|
290
|
+
if (context.activeValue !== value) context.setActiveValue(value);
|
|
291
|
+
}}
|
|
292
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
293
|
+
onClick={() => context.select(value)}
|
|
294
|
+
className="text-base-800 aria-selected:bg-primary/10 aria-selected:ring-primary/30 dark:text-base-content flex h-8 cursor-pointer items-center gap-2 rounded-md px-2 text-sm aria-selected:ring-1 aria-selected:ring-inset pointer-coarse:h-11"
|
|
295
|
+
>
|
|
296
|
+
<span className="text-base-700 grid size-4 shrink-0 place-items-center [&>svg]:size-3.5">{icon}</span>
|
|
297
|
+
<span title={name} className="min-w-0 flex-1 truncate">
|
|
298
|
+
{name}
|
|
299
|
+
</span>
|
|
300
|
+
{hint ? (
|
|
301
|
+
<span className="text-base-700 max-w-40 shrink-0 truncate text-xs tabular-nums">{hint}</span>
|
|
302
|
+
) : null}
|
|
303
|
+
{meta ? (
|
|
304
|
+
<span className="text-base-700 max-w-56 shrink-0 truncate text-xs max-sm:hidden">{meta}</span>
|
|
305
|
+
) : null}
|
|
306
|
+
{checked ? <CheckIcon aria-hidden="true" className="text-primary size-3.5 shrink-0" /> : null}
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
181
311
|
/** Props for a palette option, such as a `ResultRow`: its id, highlight and pointer tracking. */
|
|
182
312
|
export function useCommandPaletteOption(value: string) {
|
|
183
313
|
const context = useContext(OptionContext);
|
|
@@ -195,5 +325,5 @@ export function useCommandPaletteOption(value: string) {
|
|
|
195
325
|
}
|
|
196
326
|
|
|
197
327
|
function optionId(listId: string, value: string) {
|
|
198
|
-
return `${listId}-${value
|
|
328
|
+
return `${listId}-${encodeURIComponent(value)}`;
|
|
199
329
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type DropdownMenuRadioGroupProps,
|
|
9
9
|
type DropdownMenuRadioItemProps,
|
|
10
10
|
} from '@ninna-ui/overlays';
|
|
11
|
-
import { type ComponentProps, forwardRef } from 'react';
|
|
11
|
+
import { type ComponentProps, forwardRef, type ReactNode } from 'react';
|
|
12
12
|
|
|
13
13
|
interface DropdownMenuContentPolicyProps extends Omit<DropdownMenuContentProps, 'className'> {
|
|
14
14
|
width?: 'sm' | 'md';
|
|
@@ -109,6 +109,16 @@ const DropdownMenuRadioItem = forwardRef<HTMLDivElement, DropdownMenuRadioItemPo
|
|
|
109
109
|
|
|
110
110
|
DropdownMenuRadioItem.displayName = 'DropdownMenu.RadioItem';
|
|
111
111
|
|
|
112
|
+
/** An entry's label, truncating to the menu's width, with an optional smaller `detail` line under it. */
|
|
113
|
+
function DropdownMenuItemText({ children, detail }: { children: ReactNode; detail?: ReactNode }) {
|
|
114
|
+
return (
|
|
115
|
+
<span className="min-w-0 flex-1 truncate">
|
|
116
|
+
{children}
|
|
117
|
+
{detail && <span className="block truncate text-xs">{detail}</span>}
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
function DropdownMenuRoot(props: ComponentProps<typeof NinnaDropdownMenu>) {
|
|
113
123
|
return <NinnaDropdownMenu {...props} />;
|
|
114
124
|
}
|
|
@@ -119,4 +129,5 @@ export const DropdownMenu = Object.assign(DropdownMenuRoot, NinnaDropdownMenu, {
|
|
|
119
129
|
Label: DropdownMenuLabel,
|
|
120
130
|
RadioGroup: DropdownMenuRadioGroup,
|
|
121
131
|
RadioItem: DropdownMenuRadioItem,
|
|
132
|
+
ItemText: DropdownMenuItemText,
|
|
122
133
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CircleHelpIcon } from 'lucide-react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { tabbable } from '../foundations/KeyboardFocus';
|
|
5
|
+
import { Popover } from './Popover';
|
|
6
|
+
|
|
7
|
+
/** A small help button after a setting's name that opens what the setting does in a popover. `label` is what it
|
|
8
|
+
* explains; the button reads "About {label}". */
|
|
9
|
+
export function HelpTip({ label, children }: { label: string; children: ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<Popover>
|
|
12
|
+
<Popover.Trigger asChild>
|
|
13
|
+
<button
|
|
14
|
+
{...tabbable}
|
|
15
|
+
type="button"
|
|
16
|
+
aria-label={`About ${label}`}
|
|
17
|
+
className="text-base-700 hover:text-base-content inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-full"
|
|
18
|
+
>
|
|
19
|
+
<CircleHelpIcon className="size-3.5" />
|
|
20
|
+
</button>
|
|
21
|
+
</Popover.Trigger>
|
|
22
|
+
<Popover.Content width="sm">
|
|
23
|
+
<div className="max-w-full text-xs leading-5 wrap-anywhere" data-help-text>
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
</Popover.Content>
|
|
27
|
+
</Popover>
|
|
28
|
+
);
|
|
29
|
+
}
|
package/src/overlays/Modal.tsx
CHANGED
|
@@ -33,9 +33,27 @@ interface ModalContentPolicyProps extends Omit<ModalContentProps, 'className' |
|
|
|
33
33
|
sidebar?: ReactNode;
|
|
34
34
|
/** The dialog's size; a dialog with a sidebar is `paned` unless it says otherwise. */
|
|
35
35
|
presentation?: 'default' | 'paned' | 'workspace';
|
|
36
|
+
/** Grows the dialog to the window, less a small margin, under a 52px title bar; see ModalExpandButton. */
|
|
37
|
+
expanded?: boolean;
|
|
38
|
+
/** A paned dialog that keeps its width but takes its content's height, such as a short pane; a long list in it
|
|
39
|
+
* scrolls inside. Expanding wins. */
|
|
40
|
+
fitsContent?: boolean;
|
|
36
41
|
title: string;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
/** The expanded dialog's size, inline so it wins over every presentation's size classes. */
|
|
45
|
+
const expandedStyle = {
|
|
46
|
+
width: 'calc(100vw - 2rem)',
|
|
47
|
+
maxWidth: 'none',
|
|
48
|
+
height: 'calc(100dvh - (52px + 1rem))',
|
|
49
|
+
maxHeight: 'none',
|
|
50
|
+
top: '52px',
|
|
51
|
+
translate: '-50% 0',
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
/** A dialog that takes its content's height, inline so it wins over the paned size classes. */
|
|
55
|
+
const fitsContentStyle = { height: 'auto' } as const;
|
|
56
|
+
|
|
39
57
|
const ModalContent = forwardRef<HTMLDivElement, ModalContentPolicyProps>(
|
|
40
58
|
(
|
|
41
59
|
{
|
|
@@ -49,6 +67,8 @@ const ModalContent = forwardRef<HTMLDivElement, ModalContentPolicyProps>(
|
|
|
49
67
|
onPointerDownOutside,
|
|
50
68
|
sidebar,
|
|
51
69
|
presentation = sidebar ? 'paned' : 'default',
|
|
70
|
+
expanded = false,
|
|
71
|
+
fitsContent = false,
|
|
52
72
|
title,
|
|
53
73
|
...props
|
|
54
74
|
},
|
|
@@ -62,6 +82,9 @@ const ModalContent = forwardRef<HTMLDivElement, ModalContentPolicyProps>(
|
|
|
62
82
|
ref={ref}
|
|
63
83
|
{...props}
|
|
64
84
|
{...focusProps}
|
|
85
|
+
{...(expanded
|
|
86
|
+
? { 'data-expanded': true, style: expandedStyle }
|
|
87
|
+
: fitsContent && { style: fitsContentStyle })}
|
|
65
88
|
title={accessibleTitle ?? title}
|
|
66
89
|
description={description}
|
|
67
90
|
closeOnEscape={loading ? false : closeOnEscape}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Maximize2Icon, Minimize2Icon } from 'lucide-react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { IconButton } from '../actions/IconButton';
|
|
5
|
+
import { useDesignSystemLabels } from '../foundations/DesignSystemProvider';
|
|
6
|
+
import { Tooltip, TooltipPopup, TooltipTrigger } from './Tooltip';
|
|
7
|
+
|
|
8
|
+
interface ModalExpandButtonProps {
|
|
9
|
+
expanded: boolean;
|
|
10
|
+
onToggle: () => void;
|
|
11
|
+
/** The shortcut that toggles it too, as shown (⌘⇧E) and as `aria-keyshortcuts` reads it (Meta+Shift+E). */
|
|
12
|
+
shortcut?: { label: string; keys: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The control a dialog's header shows (in Modal.Content's `headerActions`) to grow the dialog to the window and back,
|
|
17
|
+
* with its shortcut in a tooltip. The tooltip opens on hover only: a dialog that opens with no field focuses this
|
|
18
|
+
* button, and a tooltip opened by that focus would take the dialog's first Escape. `aria-keyshortcuts` still tells
|
|
19
|
+
* assistive tech the shortcut.
|
|
20
|
+
*/
|
|
21
|
+
export function ModalExpandButton({ expanded, onToggle, shortcut }: ModalExpandButtonProps) {
|
|
22
|
+
const labels = useDesignSystemLabels().dialog;
|
|
23
|
+
const label = expanded ? labels.restoreSize : labels.expand;
|
|
24
|
+
const [hint, setHint] = useState(false);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Tooltip
|
|
28
|
+
open={hint}
|
|
29
|
+
onOpenChange={(open) => setHint(open && document.querySelector('button[data-expand]:hover') !== null)}
|
|
30
|
+
>
|
|
31
|
+
<TooltipTrigger asChild>
|
|
32
|
+
<IconButton
|
|
33
|
+
data-expand
|
|
34
|
+
variant="header"
|
|
35
|
+
aria-label={label}
|
|
36
|
+
aria-pressed={expanded}
|
|
37
|
+
aria-keyshortcuts={shortcut?.keys}
|
|
38
|
+
icon={expanded ? <Minimize2Icon /> : <Maximize2Icon />}
|
|
39
|
+
onClick={onToggle}
|
|
40
|
+
/>
|
|
41
|
+
</TooltipTrigger>
|
|
42
|
+
<TooltipPopup side="bottom">
|
|
43
|
+
{label}
|
|
44
|
+
{shortcut ? <span className="text-base-500 ms-2">{shortcut.label}</span> : null}
|
|
45
|
+
</TooltipPopup>
|
|
46
|
+
</Tooltip>
|
|
47
|
+
);
|
|
48
|
+
}
|
package/src/overlays/Popover.tsx
CHANGED
|
@@ -8,12 +8,14 @@ interface PopoverContentPolicyProps extends Omit<PopoverContentProps, 'className
|
|
|
8
8
|
onOpenAutoFocus?: (event: Event) => void;
|
|
9
9
|
onCloseAutoFocus?: (event: Event) => void;
|
|
10
10
|
presentation?: 'default' | 'menu';
|
|
11
|
-
|
|
11
|
+
/** `sm` is 16rem and `md` 22rem, such as a chooser with a list; either stays inside the window. */
|
|
12
|
+
width?: 'content' | 'sm' | 'md' | 'trigger';
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const popoverWidthClasses: Record<NonNullable<PopoverContentPolicyProps['width']>, string | undefined> = {
|
|
15
16
|
content: undefined,
|
|
16
17
|
sm: 'w-64',
|
|
18
|
+
md: 'w-88',
|
|
17
19
|
trigger: 'w-(--radix-popover-trigger-width)',
|
|
18
20
|
};
|
|
19
21
|
|
package/src/overlays/index.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
export { ActionModal } from './ActionModal';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
CommandPalette,
|
|
4
|
+
CommandPaletteGroup,
|
|
5
|
+
CommandPaletteOption,
|
|
6
|
+
type CommandPaletteToken,
|
|
7
|
+
useCommandPaletteOption,
|
|
8
|
+
} from './CommandPalette';
|
|
3
9
|
export { Drawer } from './Drawer';
|
|
4
10
|
export { DropdownMenu } from './DropdownMenu';
|
|
11
|
+
export { HelpTip } from './HelpTip';
|
|
5
12
|
export { ImageViewer } from './ImageViewer';
|
|
6
13
|
export { Modal } from './Modal';
|
|
14
|
+
export { ModalExpandButton } from './ModalExpandButton';
|
|
7
15
|
export { Popover } from './Popover';
|
|
8
16
|
export { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from './Tooltip';
|
package/src/showcase.css
CHANGED
|
@@ -5,3 +5,9 @@
|
|
|
5
5
|
@import './showcase/server-unit.css';
|
|
6
6
|
|
|
7
7
|
@source './';
|
|
8
|
+
|
|
9
|
+
/* ninna-ui's own @source paths assume npm's flat node_modules and find nothing under pnpm, so its components'
|
|
10
|
+
classes are scanned from here: next to this package once installed (pnpm's store or npm's tree), and in this
|
|
11
|
+
package's own node_modules inside the lar-web workspace. */
|
|
12
|
+
@source '../../../@ninna-ui/*/dist/**/*.js';
|
|
13
|
+
@source '../node_modules/@ninna-ui/*/dist/**/*.js';
|
package/src/styles.css
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/* Everything an app needs from the design system in one import: Tailwind, ninna-ui's preset, Lar's tokens and
|
|
2
2
|
base styles, and the components' utilities, which Tailwind finds by scanning this package's sources. The
|
|
3
|
-
marketing showcase
|
|
3
|
+
marketing showcase and Lar Factory's board are left out; a site that uses the showcase imports showcase.css, and
|
|
4
|
+
the board imports board.css, instead. */
|
|
4
5
|
@import './foundations/theme.css';
|
|
5
6
|
|
|
6
7
|
@source './';
|
|
7
8
|
@source not './showcase';
|
|
9
|
+
@source not './board';
|
|
10
|
+
|
|
11
|
+
/* ninna-ui's own @source paths assume npm's flat node_modules and find nothing under pnpm, so its components'
|
|
12
|
+
classes are scanned from here: next to this package once installed (pnpm's store or npm's tree), and in this
|
|
13
|
+
package's own node_modules inside the lar-web workspace. */
|
|
14
|
+
@source '../../../@ninna-ui/*/dist/**/*.js';
|
|
15
|
+
@source '../node_modules/@ninna-ui/*/dist/**/*.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Code as NinnaCode, type CodeProps as NinnaCodeProps } from '@ninna-ui/primitives';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
|
|
4
|
+
const wrapping = { anywhere: 'break-all', never: 'whitespace-nowrap' } as const;
|
|
5
|
+
|
|
6
|
+
export type CodeProps = NinnaCodeProps & {
|
|
7
|
+
/** A path or command inside a sentence, in the page's plain inline code with no chip: a path may break `anywhere`,
|
|
8
|
+
* a command `never`. Without it, the code chip. */
|
|
9
|
+
wrap?: keyof typeof wrapping;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Inline code: ninna-ui's code chip, or with `wrap`, plain inline code that breaks anywhere or never. */
|
|
13
|
+
export const Code = forwardRef<HTMLElement, CodeProps>(({ wrap, ...props }, ref) => {
|
|
14
|
+
if (wrap)
|
|
15
|
+
return (
|
|
16
|
+
<code ref={ref} className={wrapping[wrap]}>
|
|
17
|
+
{props.children}
|
|
18
|
+
</code>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return <NinnaCode ref={ref} {...props} />;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Code.displayName = 'Code';
|
package/src/typography/Link.tsx
CHANGED
|
@@ -89,17 +89,26 @@ function navigationClick({
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const inline =
|
|
93
|
+
'focus-visible:outline-primary text-inherit underline decoration-current/40 underline-offset-2 transition-colors hover:decoration-current focus-visible:outline-2';
|
|
94
|
+
|
|
95
|
+
/** A link inside running text or a note: the surrounding size and colour, underlined. With `onClick` in place of
|
|
96
|
+
* `href` it is a button with the same look, such as "Change" after what was kept, and can be `disabled`. */
|
|
97
|
+
export function InlineLink(
|
|
98
|
+
props:
|
|
99
|
+
| { href: string; children?: ReactNode }
|
|
100
|
+
| { onClick: () => void; disabled?: boolean | undefined; children?: ReactNode },
|
|
101
|
+
) {
|
|
102
|
+
if ('onClick' in props)
|
|
103
|
+
return (
|
|
104
|
+
<button {...tabbable} type="button" disabled={props.disabled} onClick={props.onClick} className={inline}>
|
|
105
|
+
{props.children}
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
|
|
94
109
|
return (
|
|
95
|
-
<a
|
|
96
|
-
{
|
|
97
|
-
href={href}
|
|
98
|
-
target="_blank"
|
|
99
|
-
rel="noreferrer"
|
|
100
|
-
className="focus-visible:outline-primary text-inherit underline decoration-current/40 underline-offset-2 transition-colors hover:decoration-current focus-visible:outline-2"
|
|
101
|
-
>
|
|
102
|
-
{children}
|
|
110
|
+
<a {...tabbable} href={props.href} target="_blank" rel="noreferrer" className={inline}>
|
|
111
|
+
{props.children}
|
|
103
112
|
</a>
|
|
104
113
|
);
|
|
105
114
|
}
|