@lar-works/design-system 0.2.1 → 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/styles.css +3 -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
|
@@ -4,6 +4,11 @@ interface StatusIndicatorProps {
|
|
|
4
4
|
className?: string;
|
|
5
5
|
size?: 'sm' | 'md';
|
|
6
6
|
tone: StatusTone;
|
|
7
|
+
/** A colour that comes with data, such as a tracker label's own, in place of the tone's; none keeps the tone. */
|
|
8
|
+
labelColor?: string | undefined;
|
|
9
|
+
/** A short word after the dot in the tone's text colour, such as a check's "Installed"; the neutral tone mutes it,
|
|
10
|
+
* for a check that didn't run. */
|
|
11
|
+
label?: string;
|
|
7
12
|
}
|
|
8
13
|
|
|
9
14
|
const toneClasses: Record<StatusTone, string> = {
|
|
@@ -14,15 +19,41 @@ const toneClasses: Record<StatusTone, string> = {
|
|
|
14
19
|
info: 'bg-info',
|
|
15
20
|
};
|
|
16
21
|
|
|
22
|
+
const labelClasses: Record<StatusTone, string> = {
|
|
23
|
+
neutral: 'text-base-700',
|
|
24
|
+
success: 'text-(--lar-success-text)',
|
|
25
|
+
warning: 'text-(--lar-warning-text)',
|
|
26
|
+
danger: 'text-(--lar-danger-text)',
|
|
27
|
+
info: 'text-(--lar-info-text)',
|
|
28
|
+
};
|
|
29
|
+
|
|
17
30
|
const sizeClasses: Record<NonNullable<StatusIndicatorProps['size']>, string> = {
|
|
18
31
|
sm: 'size-1.5',
|
|
19
32
|
md: 'size-2',
|
|
20
33
|
};
|
|
21
34
|
|
|
22
|
-
export function StatusIndicator({ className, size = 'md', tone }: StatusIndicatorProps) {
|
|
23
|
-
const classes = [
|
|
35
|
+
export function StatusIndicator({ className, size = 'md', tone, labelColor, label }: StatusIndicatorProps) {
|
|
36
|
+
const classes = [
|
|
37
|
+
'inline-block shrink-0 rounded-full',
|
|
38
|
+
sizeClasses[size],
|
|
39
|
+
toneClasses[tone],
|
|
40
|
+
label ? null : className,
|
|
41
|
+
]
|
|
24
42
|
.filter(Boolean)
|
|
25
43
|
.join(' ');
|
|
44
|
+
const dot = (
|
|
45
|
+
<span aria-hidden="true" className={classes} style={labelColor ? { background: labelColor } : undefined} />
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (label === undefined) return dot;
|
|
26
49
|
|
|
27
|
-
return
|
|
50
|
+
return (
|
|
51
|
+
<span
|
|
52
|
+
data-slot="status-indicator"
|
|
53
|
+
className={`inline-flex items-center gap-1.5 text-xs font-medium whitespace-nowrap ${labelClasses[tone]} ${className ?? ''}`}
|
|
54
|
+
>
|
|
55
|
+
{dot}
|
|
56
|
+
{label}
|
|
57
|
+
</span>
|
|
58
|
+
);
|
|
28
59
|
}
|
package/src/feedback/index.ts
CHANGED
|
@@ -6,10 +6,20 @@ export {
|
|
|
6
6
|
showErrorNotification,
|
|
7
7
|
showSuccessNotification,
|
|
8
8
|
} from './ErrorNotification';
|
|
9
|
-
export {
|
|
9
|
+
export { DismissButton } from './DismissButton';
|
|
10
|
+
export { FormResult } from './FormResult';
|
|
11
|
+
export { LiveRegion } from './LiveRegion';
|
|
12
|
+
export {
|
|
13
|
+
clearNotification,
|
|
14
|
+
type Notification,
|
|
15
|
+
Notifications,
|
|
16
|
+
showNotification,
|
|
17
|
+
useNotificationControls,
|
|
18
|
+
} from './Notifications';
|
|
19
|
+
export { Progress } from './Progress';
|
|
10
20
|
export { ProgressStatusRow } from './ProgressStatusRow';
|
|
11
21
|
export { SkeletonLoader } from './SkeletonLoader';
|
|
22
|
+
export { Loading } from './Loading';
|
|
12
23
|
export { StatusIndicator, type StatusTone } from './StatusIndicator';
|
|
13
24
|
export { StatusPopoverContent } from './StatusPopoverContent';
|
|
14
25
|
export { StatusRow } from './StatusRow';
|
|
15
|
-
export { Loading } from '@ninna-ui/feedback';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ColorSwatch {
|
|
4
|
+
/** What picking it sets, such as a hex colour without its #. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** The CSS colour it shows. */
|
|
7
|
+
color: string;
|
|
8
|
+
/** Its accessible name, such as "Colour #D73A4A". */
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A row of round colour swatches, the one matching `value` (ignoring case) pressed and ringed, then `children`, such as
|
|
14
|
+
* a field for any other colour, wrapping when they don't fit.
|
|
15
|
+
*/
|
|
16
|
+
export function ColorSwatches({
|
|
17
|
+
swatches,
|
|
18
|
+
value,
|
|
19
|
+
disabled = false,
|
|
20
|
+
onPick,
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
swatches: readonly ColorSwatch[];
|
|
24
|
+
value: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
onPick: (value: string) => void;
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<span className="flex flex-wrap items-center gap-1.5">
|
|
31
|
+
{swatches.map((swatch) => {
|
|
32
|
+
const picked = swatch.value.toLowerCase() === value.toLowerCase();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
key={swatch.value}
|
|
37
|
+
type="button"
|
|
38
|
+
aria-label={swatch.label}
|
|
39
|
+
aria-pressed={picked}
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
className={`size-5 rounded-full border ${picked ? 'ring-2 ring-(--primary) ring-offset-1 ring-offset-(--card)' : ''}`}
|
|
42
|
+
style={{ background: swatch.color }}
|
|
43
|
+
onClick={() => onPick(swatch.value)}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
{children}
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { ChevronDownIcon } from 'lucide-react';
|
|
2
|
-
import { type ReactNode, useId, useState } from 'react';
|
|
2
|
+
import { type ComponentProps, type ReactNode, type SyntheticEvent, useId, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import { Button, type ButtonProps } from '../actions/Button';
|
|
5
|
+
import { Badge } from '../display/Badge';
|
|
5
6
|
import { InsetPanel } from '../display/InsetPanel';
|
|
6
7
|
import { SkeletonLoader } from '../feedback/SkeletonLoader';
|
|
7
8
|
import { Stack } from '../layout/Flow';
|
|
9
|
+
import { HelpTip } from '../overlays/HelpTip';
|
|
8
10
|
import { Heading, Text } from '../typography/Typography';
|
|
9
11
|
import { Field, type FieldProps } from './Field';
|
|
10
12
|
|
|
13
|
+
type DataAttributes = { [key: `data-${string}`]: string | undefined };
|
|
14
|
+
|
|
11
15
|
const contentSpacing = 'px-4 py-3.5 sm:px-5';
|
|
12
16
|
const rowClasses = `grid min-w-0 gap-x-6 gap-y-3 sm:items-center ${contentSpacing}`;
|
|
13
17
|
|
|
@@ -30,10 +34,68 @@ interface FormSectionProps {
|
|
|
30
34
|
description?: string;
|
|
31
35
|
loading?: boolean;
|
|
32
36
|
loadingLabel?: string;
|
|
37
|
+
presentation?: 'default';
|
|
33
38
|
title: ReactNode;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
/** A dense tool's settings group: its title (with an optional help popover) and description over the content as
|
|
42
|
+
* given, such as one or more settings panels and their notes. */
|
|
43
|
+
interface DenseFormSectionProps extends DataAttributes {
|
|
44
|
+
presentation: 'dense';
|
|
45
|
+
title?: ReactNode;
|
|
46
|
+
/** Explains the group in a popover from a help button after the title; `helpLabel` names that button. */
|
|
47
|
+
help?: ReactNode;
|
|
48
|
+
helpLabel?: string;
|
|
49
|
+
description?: ReactNode;
|
|
50
|
+
/** Names the group when its title doesn't, or when it has none. */
|
|
51
|
+
'aria-label'?: string;
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function FormSection(props: FormSectionProps | DenseFormSectionProps) {
|
|
56
|
+
if (props.presentation === 'dense') return <DenseFormSection {...props} />;
|
|
57
|
+
|
|
58
|
+
return <PanelFormSection {...props} />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DenseFormSection({
|
|
62
|
+
presentation: _presentation,
|
|
63
|
+
title,
|
|
64
|
+
help,
|
|
65
|
+
helpLabel,
|
|
66
|
+
description,
|
|
67
|
+
'aria-label': label,
|
|
68
|
+
children,
|
|
69
|
+
...data
|
|
70
|
+
}: DenseFormSectionProps) {
|
|
71
|
+
const titleId = useId();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<section
|
|
75
|
+
{...data}
|
|
76
|
+
aria-label={label}
|
|
77
|
+
aria-labelledby={label || !title ? undefined : titleId}
|
|
78
|
+
className="grid min-w-0 gap-3"
|
|
79
|
+
>
|
|
80
|
+
{(title || description) && (
|
|
81
|
+
<div>
|
|
82
|
+
{title && (
|
|
83
|
+
<h3 id={titleId} className="m-0 flex items-center gap-1.5 text-[13px] font-semibold">
|
|
84
|
+
{title}
|
|
85
|
+
{help && (
|
|
86
|
+
<HelpTip label={helpLabel ?? (typeof title === 'string' ? title : '')}>{help}</HelpTip>
|
|
87
|
+
)}
|
|
88
|
+
</h3>
|
|
89
|
+
)}
|
|
90
|
+
{description && <p className="text-base-700 m-0 mt-0.5 text-xs">{description}</p>}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
{children}
|
|
94
|
+
</section>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function PanelFormSection({
|
|
37
99
|
action,
|
|
38
100
|
badges,
|
|
39
101
|
children,
|
|
@@ -76,16 +138,40 @@ export function FormSection({
|
|
|
76
138
|
);
|
|
77
139
|
}
|
|
78
140
|
|
|
79
|
-
interface
|
|
141
|
+
interface FormRowColumnsProps {
|
|
80
142
|
badges?: ReactNode;
|
|
81
143
|
children: ReactNode;
|
|
82
144
|
description: string;
|
|
83
145
|
/** The row's element id, for linking or scrolling to it; a row with one can take focus when it is opened. */
|
|
84
146
|
id?: string;
|
|
85
147
|
label: ReactNode;
|
|
148
|
+
layout?: 'columns';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A dense tool's settings row: the label (with an optional help popover) and description, then its controls flowing
|
|
152
|
+
* beside them and wrapping under them when they don't fit, then content below and an error. */
|
|
153
|
+
interface FormRowFlowProps extends DataAttributes {
|
|
154
|
+
layout: 'flow';
|
|
155
|
+
label: ReactNode;
|
|
156
|
+
/** Explains the setting in a popover from a help button after the label; `helpLabel` names that button. */
|
|
157
|
+
help?: ReactNode;
|
|
158
|
+
helpLabel?: string;
|
|
159
|
+
description?: ReactNode;
|
|
160
|
+
children?: ReactNode;
|
|
161
|
+
/** Full-width content under the label and controls, such as a list the setting governs. */
|
|
162
|
+
below?: ReactNode;
|
|
163
|
+
error?: string | undefined;
|
|
164
|
+
/** The error's element id, for a control's aria-describedby. */
|
|
165
|
+
errorId?: string;
|
|
86
166
|
}
|
|
87
167
|
|
|
88
|
-
|
|
168
|
+
type FormRowProps = FormRowColumnsProps | FormRowFlowProps;
|
|
169
|
+
|
|
170
|
+
export function FormRow(props: FormRowProps) {
|
|
171
|
+
if (props.layout === 'flow') return <FormRowFlow {...props} />;
|
|
172
|
+
|
|
173
|
+
const { badges, children, description, id, label } = props;
|
|
174
|
+
|
|
89
175
|
return (
|
|
90
176
|
<div
|
|
91
177
|
id={id}
|
|
@@ -108,6 +194,58 @@ export function FormRow({ badges, children, description, id, label }: FormRowPro
|
|
|
108
194
|
);
|
|
109
195
|
}
|
|
110
196
|
|
|
197
|
+
function FormRowFlow({
|
|
198
|
+
layout: _layout,
|
|
199
|
+
label,
|
|
200
|
+
help,
|
|
201
|
+
helpLabel,
|
|
202
|
+
description,
|
|
203
|
+
children,
|
|
204
|
+
below,
|
|
205
|
+
error,
|
|
206
|
+
errorId,
|
|
207
|
+
...data
|
|
208
|
+
}: FormRowFlowProps) {
|
|
209
|
+
const fallbackErrorId = useId();
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div {...data} className="settings-row grid min-w-0 gap-2 px-4 py-3">
|
|
213
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-6 gap-y-2">
|
|
214
|
+
<div className="min-w-32 flex-1">
|
|
215
|
+
<div className="settings-row-title flex min-w-0 items-center gap-1.5 text-[13px] font-medium wrap-anywhere">
|
|
216
|
+
{label}
|
|
217
|
+
{help && (
|
|
218
|
+
<HelpTip label={helpLabel ?? (typeof label === 'string' ? label : '')}>{help}</HelpTip>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
{description && <div className="text-base-700 text-xs wrap-anywhere">{description}</div>}
|
|
222
|
+
</div>
|
|
223
|
+
{children && (
|
|
224
|
+
<div className="settings-row-control flex max-w-full min-w-0 flex-wrap items-center gap-2 sm:max-w-[60%] [&_button]:max-w-full [&>span]:max-w-full">
|
|
225
|
+
{children}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
{below}
|
|
230
|
+
{error && (
|
|
231
|
+
<p id={errorId ?? fallbackErrorId} role="alert" className="m-0 text-xs text-(--lar-danger-text)">
|
|
232
|
+
{error}
|
|
233
|
+
</p>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A value and its unit beside it in a row's controls, such as a number field and "days". */
|
|
240
|
+
export function FormUnit({ children, unit }: { children: ReactNode; unit: ReactNode }) {
|
|
241
|
+
return (
|
|
242
|
+
<span className="flex items-center gap-2 text-sm [&_input]:w-20">
|
|
243
|
+
{children}
|
|
244
|
+
{unit}
|
|
245
|
+
</span>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
111
249
|
type FormFieldProps = Omit<FieldProps, 'children' | 'className'> & { action?: ReactNode } & (
|
|
112
250
|
| { children: ReactNode; loading?: false; loadingLabel?: never }
|
|
113
251
|
| { children?: never; loading: true; loadingLabel: string }
|
|
@@ -156,20 +294,117 @@ export function FormAction(props: Omit<ButtonProps, 'type' | 'size' | 'variant'
|
|
|
156
294
|
return <Button {...props} type="button" variant="subtle" />;
|
|
157
295
|
}
|
|
158
296
|
|
|
159
|
-
export function Disclosure({
|
|
160
|
-
|
|
297
|
+
export function Disclosure({
|
|
298
|
+
children,
|
|
299
|
+
label,
|
|
300
|
+
lazy = false,
|
|
301
|
+
presentation = 'note',
|
|
302
|
+
defaultOpen = false,
|
|
303
|
+
badge,
|
|
304
|
+
onOpenChange,
|
|
305
|
+
...data
|
|
306
|
+
}: {
|
|
307
|
+
children: ReactNode;
|
|
308
|
+
label: string;
|
|
309
|
+
lazy?: boolean;
|
|
310
|
+
/** `note` folds small muted text behind a chevron; `section` folds a section's own content, such as an earlier
|
|
311
|
+
* version of a text, behind a muted label in the body size; `group` folds a form's optional group behind its
|
|
312
|
+
* heading, such as optional settings; `framed` folds a short list in a frame, such as entries that can't be
|
|
313
|
+
* picked. */
|
|
314
|
+
presentation?: 'note' | 'section' | 'group' | 'framed';
|
|
315
|
+
/** Starts open, such as a group whose kept answers may need a look. */
|
|
316
|
+
defaultOpen?: boolean;
|
|
317
|
+
/** A `group`'s badge after its heading, such as "Optional". */
|
|
318
|
+
badge?: string | undefined;
|
|
319
|
+
/** Hears each time it opens or closes, such as to read what it shows once it opens. */
|
|
320
|
+
onOpenChange?: (open: boolean) => void;
|
|
321
|
+
} & DataAttributes) {
|
|
322
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
323
|
+
const shown = !lazy || open ? children : null;
|
|
324
|
+
const toggle = (event: SyntheticEvent<HTMLDetailsElement>) => {
|
|
325
|
+
setOpen(event.currentTarget.open);
|
|
326
|
+
onOpenChange?.(event.currentTarget.open);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (presentation === 'group')
|
|
330
|
+
return (
|
|
331
|
+
<details {...data} open={open} onToggle={toggle} className="min-w-0">
|
|
332
|
+
<summary className="cursor-pointer text-[13px] font-semibold">
|
|
333
|
+
{label}
|
|
334
|
+
{badge && (
|
|
335
|
+
<>
|
|
336
|
+
{' '}
|
|
337
|
+
<Badge className="ms-1">{badge}</Badge>
|
|
338
|
+
</>
|
|
339
|
+
)}
|
|
340
|
+
</summary>
|
|
341
|
+
{shown && <div className="mt-3 grid min-w-0 gap-3">{shown}</div>}
|
|
342
|
+
</details>
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (presentation === 'framed')
|
|
346
|
+
return (
|
|
347
|
+
<details {...data} open={open} onToggle={toggle} className="min-w-0 rounded-lg border px-3 py-2">
|
|
348
|
+
<summary className="cursor-pointer text-sm font-medium">{label}</summary>
|
|
349
|
+
<div className="mt-2">{shown}</div>
|
|
350
|
+
</details>
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (presentation === 'section')
|
|
354
|
+
return (
|
|
355
|
+
<details {...data} className="grid min-w-0 gap-2" onToggle={toggle}>
|
|
356
|
+
<summary className="text-base-700 cursor-pointer text-sm">{label}</summary>
|
|
357
|
+
<div className="pt-2">{shown}</div>
|
|
358
|
+
</details>
|
|
359
|
+
);
|
|
161
360
|
|
|
162
361
|
return (
|
|
163
|
-
<details className="group px-1" onToggle={
|
|
362
|
+
<details {...data} className="group px-1" onToggle={toggle}>
|
|
164
363
|
<summary className="text-base-700 hover:text-base-content focus-visible:outline-primary flex w-fit cursor-pointer list-none items-center gap-1.5 rounded py-1 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 [&::-webkit-details-marker]:hidden">
|
|
165
364
|
<span aria-hidden="true" className="-rotate-90 transition-transform group-open:rotate-0 [&>svg]:size-3">
|
|
166
365
|
<ChevronDownIcon />
|
|
167
366
|
</span>
|
|
168
367
|
{label}
|
|
169
368
|
</summary>
|
|
170
|
-
<div className="text-base-700 mt-2 space-y-2 text-xs leading-relaxed">
|
|
171
|
-
{!lazy || open ? children : null}
|
|
172
|
-
</div>
|
|
369
|
+
<div className="text-base-700 mt-2 space-y-2 text-xs leading-relaxed">{shown}</div>
|
|
173
370
|
</details>
|
|
174
371
|
);
|
|
175
372
|
}
|
|
373
|
+
|
|
374
|
+
type FormFieldsetProps = Omit<ComponentProps<'fieldset'>, 'style'> & {
|
|
375
|
+
/** The legend over the group. */
|
|
376
|
+
legend?: ReactNode;
|
|
377
|
+
/** Names the group for assistive technology only, where what's around it already says what it is. */
|
|
378
|
+
hiddenLegend?: boolean;
|
|
379
|
+
/** `group` only groups and disables its fields, with no frame. */
|
|
380
|
+
presentation?: 'bordered' | 'group';
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/** The form section's fieldset: a bordered group of controls under a legend, on the native fieldset, which also
|
|
384
|
+
* disables every control inside it. As a `group`, it only groups and disables its fields. */
|
|
385
|
+
export function FormFieldset({
|
|
386
|
+
legend,
|
|
387
|
+
hiddenLegend = false,
|
|
388
|
+
presentation = 'bordered',
|
|
389
|
+
className = '',
|
|
390
|
+
children,
|
|
391
|
+
...props
|
|
392
|
+
}: FormFieldsetProps) {
|
|
393
|
+
return (
|
|
394
|
+
<fieldset
|
|
395
|
+
className={`m-0 min-w-0 ${presentation === 'group' ? 'border-0 p-0' : 'border-base-300 mb-3 rounded-lg border px-3 pt-2 pb-0 [&>legend]:mb-2 [&>legend]:font-semibold'} ${className}`}
|
|
396
|
+
data-slot="fieldset"
|
|
397
|
+
{...props}
|
|
398
|
+
>
|
|
399
|
+
{legend ? (
|
|
400
|
+
<legend
|
|
401
|
+
className={hiddenLegend ? 'sr-only' : 'text-base-content p-0 text-sm leading-5 font-medium'}
|
|
402
|
+
data-slot="fieldset-legend"
|
|
403
|
+
>
|
|
404
|
+
{legend}
|
|
405
|
+
</legend>
|
|
406
|
+
) : null}
|
|
407
|
+
{children}
|
|
408
|
+
</fieldset>
|
|
409
|
+
);
|
|
410
|
+
}
|
package/src/forms/Input.tsx
CHANGED
|
@@ -3,18 +3,40 @@ import { forwardRef } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { formControlClasses, formControlDefaults } from './FormControlStyles';
|
|
5
5
|
|
|
6
|
-
export type InputProps = Omit<NinnaInputProps, 'fullWidth'
|
|
6
|
+
export type InputProps = Omit<NinnaInputProps, 'fullWidth'> & {
|
|
7
|
+
/** A `field` fills its container; `setting` is a settings row's 16rem text field, narrower where the row is; `short`
|
|
8
|
+
* holds a few characters, such as a hex colour; `count` a small whole number; `title` is a document's title typed
|
|
9
|
+
* in place, borderless in large semibold type, such as a new item's. */
|
|
10
|
+
presentation?: 'field' | 'setting' | 'short' | 'count' | 'title';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const presentations = {
|
|
14
|
+
field: '',
|
|
15
|
+
setting: 'w-64 max-w-full',
|
|
16
|
+
short: 'w-24',
|
|
17
|
+
count: 'w-20',
|
|
18
|
+
title: 'text-base-content w-full shrink-0 border-0 bg-transparent p-0 text-xl font-semibold tracking-tight outline-none placeholder:font-medium placeholder:text-(--muted-foreground)',
|
|
19
|
+
} as const;
|
|
7
20
|
|
|
8
21
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
9
|
-
(
|
|
22
|
+
(
|
|
23
|
+
{
|
|
24
|
+
size = formControlDefaults.size,
|
|
25
|
+
variant = formControlDefaults.variant,
|
|
26
|
+
presentation = 'field',
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
},
|
|
30
|
+
ref,
|
|
31
|
+
) => (
|
|
10
32
|
<NinnaInput
|
|
11
33
|
ref={ref}
|
|
12
34
|
color={formControlDefaults.color}
|
|
13
35
|
{...props}
|
|
14
36
|
fullWidth
|
|
15
37
|
size={size}
|
|
16
|
-
variant={variant}
|
|
17
|
-
className={`${variant === 'filled' ? formControlClasses : ''} ${className ?? ''}`}
|
|
38
|
+
variant={presentation === 'title' ? 'unstyled' : variant}
|
|
39
|
+
className={`${variant === 'filled' && presentation !== 'title' ? formControlClasses : ''} ${presentations[presentation]} ${className ?? ''}`}
|
|
18
40
|
/>
|
|
19
41
|
),
|
|
20
42
|
);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { PlusIcon } from 'lucide-react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../actions/Button';
|
|
5
|
+
import { StatusIndicator } from '../feedback/StatusIndicator';
|
|
6
|
+
import { Checkbox } from './Checkbox';
|
|
7
|
+
import { Input } from './Input';
|
|
8
|
+
|
|
9
|
+
export interface LabelChoice {
|
|
10
|
+
name: string;
|
|
11
|
+
/** A CSS colour; a tracker with no label colours gives none. */
|
|
12
|
+
color?: string | undefined;
|
|
13
|
+
description?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Lets a typed name no choice has become a new label. */
|
|
17
|
+
export interface LabelCreation {
|
|
18
|
+
/** Why this person can't add new labels at all, or null when they can. */
|
|
19
|
+
refused: string | null;
|
|
20
|
+
/** Why this name can't be a label, or null when it can. */
|
|
21
|
+
problem: (name: string) => string | null;
|
|
22
|
+
/** What adding one does, under the list. */
|
|
23
|
+
note: string;
|
|
24
|
+
/** The button that adds `name`, such as `Create "name"`. */
|
|
25
|
+
createLabel: (name: string) => string;
|
|
26
|
+
onCreate: (name: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface LabelPickerProps {
|
|
30
|
+
choices: readonly LabelChoice[];
|
|
31
|
+
picked: readonly string[];
|
|
32
|
+
onToggle: (name: string) => void;
|
|
33
|
+
/** The filter field's accessible name and its placeholder. */
|
|
34
|
+
filterLabel: string;
|
|
35
|
+
placeholder: string;
|
|
36
|
+
/** Shown when the filter matches nothing. */
|
|
37
|
+
noMatchLabel: string;
|
|
38
|
+
/** Choices shown ticked or not but not changeable here, each marked with `lockedNote`. */
|
|
39
|
+
locked?: readonly string[];
|
|
40
|
+
lockedNote?: string;
|
|
41
|
+
busy?: boolean;
|
|
42
|
+
create?: LabelCreation;
|
|
43
|
+
/** Whether two names are the same label; trackers compare them ignoring case. */
|
|
44
|
+
same?: (a: string, b: string) => boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ignoringCase = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A searchable checklist of labels, with each label's colour and description. It holds only its search text: the
|
|
51
|
+
* caller gives the choices, what is picked and what a toggle does, so it reads and writes whichever way its surface
|
|
52
|
+
* needs. With `create`, Enter or the create button lets a typed name no choice has in.
|
|
53
|
+
*/
|
|
54
|
+
export function LabelPicker({
|
|
55
|
+
choices,
|
|
56
|
+
picked,
|
|
57
|
+
onToggle,
|
|
58
|
+
filterLabel,
|
|
59
|
+
placeholder,
|
|
60
|
+
noMatchLabel,
|
|
61
|
+
locked = [],
|
|
62
|
+
lockedNote,
|
|
63
|
+
busy = false,
|
|
64
|
+
create,
|
|
65
|
+
same = ignoringCase,
|
|
66
|
+
}: LabelPickerProps) {
|
|
67
|
+
const [query, setQuery] = useState('');
|
|
68
|
+
const typed = query.trim();
|
|
69
|
+
const hits = choices.filter(({ name }) => name.toLowerCase().includes(typed.toLowerCase()));
|
|
70
|
+
const fresh = !!create && !!typed && !choices.some(({ name }) => same(name, typed));
|
|
71
|
+
const refusal = fresh ? (create.refused ?? create.problem(typed)) : null;
|
|
72
|
+
const add = () => {
|
|
73
|
+
create?.onCreate(typed);
|
|
74
|
+
setQuery('');
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="grid w-72 gap-2" data-label-picker aria-busy={busy}>
|
|
79
|
+
<Input
|
|
80
|
+
autoFocus
|
|
81
|
+
placeholder={placeholder}
|
|
82
|
+
aria-label={filterLabel}
|
|
83
|
+
value={query}
|
|
84
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
85
|
+
onKeyDown={(e) => {
|
|
86
|
+
if (e.key !== 'Enter') return;
|
|
87
|
+
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
|
|
90
|
+
if (fresh && !refusal) add();
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
<div className="grid max-h-56 gap-0.5 overflow-y-auto">
|
|
94
|
+
{hits.map(({ name, color, description }) => {
|
|
95
|
+
const fixed = locked.some((label) => same(label, name));
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<label
|
|
99
|
+
key={name}
|
|
100
|
+
data-label-choice
|
|
101
|
+
className="hover:bg-base-200 flex cursor-pointer items-start gap-2 rounded-md px-1.5 py-1 text-xs"
|
|
102
|
+
>
|
|
103
|
+
<Checkbox
|
|
104
|
+
checked={picked.some((label) => same(label, name))}
|
|
105
|
+
disabled={fixed}
|
|
106
|
+
onChange={() => onToggle(name)}
|
|
107
|
+
/>
|
|
108
|
+
{color && (
|
|
109
|
+
<span data-label-dot className="mt-1 flex">
|
|
110
|
+
<StatusIndicator tone="neutral" labelColor={color} />
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
<span className="min-w-0 flex-1">
|
|
114
|
+
<span className="block wrap-anywhere">
|
|
115
|
+
{name}
|
|
116
|
+
{fixed && lockedNote && <span className="text-base-700"> · {lockedNote}</span>}
|
|
117
|
+
</span>
|
|
118
|
+
{description && <span className="text-base-700 block text-[11px]">{description}</span>}
|
|
119
|
+
</span>
|
|
120
|
+
</label>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
{!hits.length && !fresh && <p className="text-base-700 m-0 p-1.5 text-[11px]">{noMatchLabel}</p>}
|
|
124
|
+
</div>
|
|
125
|
+
{fresh && !refusal && (
|
|
126
|
+
<span className="justify-self-start">
|
|
127
|
+
<Button variant="secondary" compact leftIcon={<PlusIcon />} onClick={add}>
|
|
128
|
+
{create.createLabel(typed)}
|
|
129
|
+
</Button>
|
|
130
|
+
</span>
|
|
131
|
+
)}
|
|
132
|
+
{refusal && (
|
|
133
|
+
<p role="alert" className="text-base-700 m-0 text-[11px]">
|
|
134
|
+
{refusal}
|
|
135
|
+
</p>
|
|
136
|
+
)}
|
|
137
|
+
{create && !create.refused && <p className="text-base-700 m-0 text-[11px]">{create.note}</p>}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|