@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
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Lar Factory's Add item: the title bar's button that opens it, the New work pane's fields beside their details panel,
|
|
2
|
+
// the panel's labelled fields and ticket part, the Create issue choice, and the Find a ticket pane with its search.
|
|
3
|
+
|
|
4
|
+
import { SquarePenIcon } from 'lucide-react';
|
|
5
|
+
import type { KeyboardEvent, ReactNode, Ref } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Button } from '../actions/Button';
|
|
8
|
+
import { FormResult } from '../feedback/FormResult';
|
|
9
|
+
import { FormFieldset } from '../forms/FormSection';
|
|
10
|
+
import { Input } from '../forms/Input';
|
|
11
|
+
import { tabbable } from '../foundations/KeyboardFocus';
|
|
12
|
+
import { buttonKind } from './ButtonKinds';
|
|
13
|
+
import { DetailsHeading } from './ItemDetails';
|
|
14
|
+
|
|
15
|
+
/** The title bar's Add item button, a Main action named by its `label`, which folds away, for screen readers only, on a
|
|
16
|
+
* title bar under 70rem; `title` gives it whole with its shortcut, which `keyShortcuts` names. */
|
|
17
|
+
export function AddItemButton({
|
|
18
|
+
label,
|
|
19
|
+
title,
|
|
20
|
+
keyShortcuts,
|
|
21
|
+
onOpen,
|
|
22
|
+
}: {
|
|
23
|
+
label: string;
|
|
24
|
+
title: string;
|
|
25
|
+
keyShortcuts?: string;
|
|
26
|
+
onOpen: () => void;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<Button
|
|
30
|
+
{...buttonKind('main')}
|
|
31
|
+
className="add-item px-2"
|
|
32
|
+
leftIcon={<SquarePenIcon />}
|
|
33
|
+
title={title}
|
|
34
|
+
aria-keyshortcuts={keyShortcuts}
|
|
35
|
+
onClick={onOpen}
|
|
36
|
+
>
|
|
37
|
+
<span className="@max-[70rem]:sr-only">{label}</span>
|
|
38
|
+
</Button>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A draft's fields as one group, which `disabled` locks: the `main` column (its title, editor and notes) beside the
|
|
44
|
+
* `details` panel, which `detailsLabel` names. Narrower than 40rem the panel stacks under the main column, which then
|
|
45
|
+
* grows with its text, and the two scroll as one; each row sizes to its content, so a long text pushes the panel down
|
|
46
|
+
* rather than running under it.
|
|
47
|
+
*/
|
|
48
|
+
export function DraftFields({
|
|
49
|
+
id,
|
|
50
|
+
disabled,
|
|
51
|
+
detailsLabel,
|
|
52
|
+
main,
|
|
53
|
+
details,
|
|
54
|
+
}: {
|
|
55
|
+
id: string;
|
|
56
|
+
disabled: boolean;
|
|
57
|
+
detailsLabel: string;
|
|
58
|
+
main: ReactNode;
|
|
59
|
+
details: ReactNode;
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="@container flex min-h-0 flex-1 flex-col">
|
|
63
|
+
<FormFieldset
|
|
64
|
+
id={id}
|
|
65
|
+
presentation="group"
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
className="border-base-300 grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_16rem] border-t @max-[40rem]:auto-rows-max @max-[40rem]:grid-cols-[minmax(0,1fr)] @max-[40rem]:content-start @max-[40rem]:overflow-y-auto"
|
|
68
|
+
>
|
|
69
|
+
<div className="flex min-h-0 flex-col gap-3 overflow-y-auto px-5 pt-4 pb-5 sm:px-6 @max-[40rem]:min-h-80 @max-[40rem]:overflow-visible">
|
|
70
|
+
{main}
|
|
71
|
+
</div>
|
|
72
|
+
<aside
|
|
73
|
+
aria-label={detailsLabel}
|
|
74
|
+
className="border-base-300 bg-base-50/60 grid content-start gap-5 overflow-y-auto border-l px-4 py-4 @max-[40rem]:overflow-visible @max-[40rem]:border-t @max-[40rem]:border-l-0"
|
|
75
|
+
>
|
|
76
|
+
{details}
|
|
77
|
+
</aside>
|
|
78
|
+
</FormFieldset>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A field of a details panel under its small uppercase `heading`, which labels the field `htmlFor` names; without
|
|
85
|
+
* `htmlFor`, the field is a group of controls that the heading names, such as a ticket's labels (`id` and `ref` reach
|
|
86
|
+
* the group).
|
|
87
|
+
*/
|
|
88
|
+
export function PanelField({
|
|
89
|
+
heading,
|
|
90
|
+
htmlFor,
|
|
91
|
+
id,
|
|
92
|
+
ref,
|
|
93
|
+
children,
|
|
94
|
+
}: {
|
|
95
|
+
heading: string;
|
|
96
|
+
htmlFor?: string;
|
|
97
|
+
id?: string;
|
|
98
|
+
ref?: Ref<HTMLDivElement>;
|
|
99
|
+
children: ReactNode;
|
|
100
|
+
}) {
|
|
101
|
+
if (htmlFor)
|
|
102
|
+
return (
|
|
103
|
+
<div className="grid gap-1.5">
|
|
104
|
+
<DetailsHeading presentation="label" as="label" htmlFor={htmlFor}>
|
|
105
|
+
{heading}
|
|
106
|
+
</DetailsHeading>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div ref={ref} id={id} role="group" aria-label={heading} className="grid gap-1.5">
|
|
113
|
+
<DetailsHeading presentation="label" as="span">
|
|
114
|
+
{heading}
|
|
115
|
+
</DetailsHeading>
|
|
116
|
+
{children}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The details panel's ticket part, ruled off from the fields above it: a ticket's own fields, or what a draft without
|
|
122
|
+
* one keeps. */
|
|
123
|
+
export function DraftTicketPart({ children }: { children: ReactNode }) {
|
|
124
|
+
return <div className="border-base-300 grid gap-3 border-t pt-4">{children}</div>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Create issue in the footer: its `checkbox`, then the `reason` it can't be ticked, kept to a readable width, and any
|
|
128
|
+
* `error`. */
|
|
129
|
+
export function CreateIssueChoice({
|
|
130
|
+
checkbox,
|
|
131
|
+
reason,
|
|
132
|
+
error,
|
|
133
|
+
}: {
|
|
134
|
+
checkbox: ReactNode;
|
|
135
|
+
reason?: ReactNode;
|
|
136
|
+
error?: ReactNode;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<div className="grid gap-1">
|
|
140
|
+
{checkbox}
|
|
141
|
+
{reason && <div className="max-w-md">{reason}</div>}
|
|
142
|
+
{error}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The Find a ticket pane, ruled off from the header: its notes and fields, one under the other, scrolling above the
|
|
148
|
+
* footer when a long result list needs it. */
|
|
149
|
+
export function TicketSearchPane({ id, children }: { id: string; children: ReactNode }) {
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
id={id}
|
|
153
|
+
className="border-base-300 grid min-h-0 content-start gap-4 overflow-y-auto border-t px-5 pt-4 pb-5 sm:px-6"
|
|
154
|
+
>
|
|
155
|
+
{children}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A ticket a search found: its number, its title and a short `note`, such as that it is on the board already. */
|
|
161
|
+
export interface TicketSearchMatch {
|
|
162
|
+
number: number;
|
|
163
|
+
title: string;
|
|
164
|
+
note?: string | undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A search of a tracker's open tickets: the field (`fieldLabel` names it) with its Search button, which Enter also
|
|
169
|
+
* runs, the matches, each a button that picks it, and the search's `result`, announced. `id` prefixes the field's,
|
|
170
|
+
* the button's and the result's ids. `busy` stops a pick while a search or a pick runs, and `picking` marks the list
|
|
171
|
+
* as busy.
|
|
172
|
+
*/
|
|
173
|
+
export function TicketSearch({
|
|
174
|
+
id,
|
|
175
|
+
query,
|
|
176
|
+
onQueryChange,
|
|
177
|
+
onSearch,
|
|
178
|
+
fieldLabel,
|
|
179
|
+
placeholder,
|
|
180
|
+
searchLabel,
|
|
181
|
+
disabled = false,
|
|
182
|
+
searchDisabled = false,
|
|
183
|
+
tickets,
|
|
184
|
+
listLabel,
|
|
185
|
+
busy = false,
|
|
186
|
+
picking = false,
|
|
187
|
+
onPick,
|
|
188
|
+
result,
|
|
189
|
+
}: {
|
|
190
|
+
id: string;
|
|
191
|
+
query: string;
|
|
192
|
+
onQueryChange: (query: string) => void;
|
|
193
|
+
onSearch: () => void;
|
|
194
|
+
fieldLabel: string;
|
|
195
|
+
placeholder: string;
|
|
196
|
+
searchLabel: string;
|
|
197
|
+
disabled?: boolean | undefined;
|
|
198
|
+
searchDisabled?: boolean;
|
|
199
|
+
tickets: readonly TicketSearchMatch[];
|
|
200
|
+
listLabel: string;
|
|
201
|
+
busy?: boolean;
|
|
202
|
+
picking?: boolean;
|
|
203
|
+
onPick: (number: number) => void;
|
|
204
|
+
result: string;
|
|
205
|
+
}) {
|
|
206
|
+
const onKey = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
207
|
+
if (event.key !== 'Enter') return;
|
|
208
|
+
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
onSearch();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="grid gap-2" data-ticket-picker>
|
|
215
|
+
<div className="flex gap-2">
|
|
216
|
+
<Input
|
|
217
|
+
id={`${id}-query`}
|
|
218
|
+
type="search"
|
|
219
|
+
value={query}
|
|
220
|
+
onChange={(event) => onQueryChange(event.currentTarget.value)}
|
|
221
|
+
onKeyDown={onKey}
|
|
222
|
+
disabled={disabled}
|
|
223
|
+
autoComplete="off"
|
|
224
|
+
aria-label={fieldLabel}
|
|
225
|
+
placeholder={placeholder}
|
|
226
|
+
className="min-w-0 flex-1"
|
|
227
|
+
/>
|
|
228
|
+
<Button {...buttonKind('other')} id={`${id}-search`} disabled={searchDisabled} onClick={onSearch}>
|
|
229
|
+
{searchLabel}
|
|
230
|
+
</Button>
|
|
231
|
+
</div>
|
|
232
|
+
{tickets.length > 0 && (
|
|
233
|
+
<ul className="m-0 grid list-none gap-0.5 p-0" aria-label={listLabel} aria-busy={picking}>
|
|
234
|
+
{tickets.map((ticket) => (
|
|
235
|
+
<li key={ticket.number}>
|
|
236
|
+
<button
|
|
237
|
+
{...tabbable}
|
|
238
|
+
type="button"
|
|
239
|
+
data-ticket={ticket.number}
|
|
240
|
+
disabled={busy}
|
|
241
|
+
onClick={() => onPick(ticket.number)}
|
|
242
|
+
className="hover:bg-base-200/70 focus-visible:outline-primary flex w-full min-w-0 items-baseline gap-2 rounded-md px-2 py-1.5 text-left text-sm focus-visible:outline-2 disabled:opacity-60"
|
|
243
|
+
>
|
|
244
|
+
<span className="shrink-0 text-(--muted-foreground) tabular-nums">
|
|
245
|
+
#{ticket.number}
|
|
246
|
+
</span>
|
|
247
|
+
<span className="min-w-0 flex-1 truncate">{ticket.title}</span>
|
|
248
|
+
{ticket.note && (
|
|
249
|
+
<span className="shrink-0 text-xs text-(--muted-foreground)">{ticket.note}</span>
|
|
250
|
+
)}
|
|
251
|
+
</button>
|
|
252
|
+
</li>
|
|
253
|
+
))}
|
|
254
|
+
</ul>
|
|
255
|
+
)}
|
|
256
|
+
<FormResult id={`${id}-result`} text={result} />
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Text } from '../typography/Typography';
|
|
4
|
+
|
|
5
|
+
/** A row's actions at its right, with what they last did and an error under them. */
|
|
6
|
+
export function ActionStack({
|
|
7
|
+
children,
|
|
8
|
+
below,
|
|
9
|
+
error,
|
|
10
|
+
}: {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
below?: ReactNode;
|
|
13
|
+
error?: string | null;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<span className="flex flex-col items-end gap-1">
|
|
17
|
+
<span className="flex flex-wrap items-center gap-2">{children}</span>
|
|
18
|
+
{below}
|
|
19
|
+
{error && (
|
|
20
|
+
<Text variant="caption" tone="danger" role="alert">
|
|
21
|
+
{error}
|
|
22
|
+
</Text>
|
|
23
|
+
)}
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A setting's confirmation, inline under it, before it saves: what it would do now, the work it names, and the
|
|
29
|
+
* buttons that cancel or go ahead. */
|
|
30
|
+
export function SettingConfirm({
|
|
31
|
+
label,
|
|
32
|
+
message,
|
|
33
|
+
items,
|
|
34
|
+
actions,
|
|
35
|
+
}: {
|
|
36
|
+
label: string;
|
|
37
|
+
message: string;
|
|
38
|
+
items: readonly string[];
|
|
39
|
+
actions: ReactNode;
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className="grid gap-2 rounded-lg border border-(--border) p-3 text-sm"
|
|
44
|
+
role="alertdialog"
|
|
45
|
+
aria-label={label}
|
|
46
|
+
data-delete-confirm
|
|
47
|
+
>
|
|
48
|
+
<p className="m-0">{message}</p>
|
|
49
|
+
{items.length > 0 && (
|
|
50
|
+
<ul className="m-0 grid gap-0.5 pl-4">
|
|
51
|
+
{items.map((item, index) => (
|
|
52
|
+
<li key={index}>{item}</li>
|
|
53
|
+
))}
|
|
54
|
+
</ul>
|
|
55
|
+
)}
|
|
56
|
+
<span className="flex gap-2">{actions}</span>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** What a setting's last run did, and each thing it kept with why. */
|
|
62
|
+
export function SettingLastRun({ summary, kept }: { summary: string; kept: readonly string[] }) {
|
|
63
|
+
return (
|
|
64
|
+
<div className="grid gap-1 text-xs text-(--muted-foreground)" data-last-delete>
|
|
65
|
+
<p className="m-0">{summary}</p>
|
|
66
|
+
{kept.length > 0 && (
|
|
67
|
+
<ul className="m-0 grid gap-0.5 pl-4">
|
|
68
|
+
{kept.map((line, index) => (
|
|
69
|
+
<li key={index}>{line}</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// An expanded dossier's attention list: what keeps its work moving, as labelled rows of entries, and the parts and
|
|
2
|
+
// references its entries name.
|
|
3
|
+
|
|
4
|
+
import { BanIcon, CheckIcon, CircleHelpIcon } from 'lucide-react';
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { DataAttributes } from './DataAttributes';
|
|
8
|
+
import { ROW_OPENS } from './RowStyles';
|
|
9
|
+
|
|
10
|
+
const TONE = {
|
|
11
|
+
yours: 'text-(--warning-foreground)',
|
|
12
|
+
decision: 'text-(--primary)',
|
|
13
|
+
blocked: 'text-(--st-review)',
|
|
14
|
+
waiting: 'text-(--muted-foreground)',
|
|
15
|
+
ready: 'text-(--st-now)',
|
|
16
|
+
fog: 'text-(--st-amber)',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/** An attention row's tone, which colours its label. */
|
|
20
|
+
export type AttentionTone = keyof typeof TONE;
|
|
21
|
+
|
|
22
|
+
/** The attention rows, each a label in its tone with how many entries it has, then its entries. */
|
|
23
|
+
export function AttentionList({
|
|
24
|
+
rows,
|
|
25
|
+
}: {
|
|
26
|
+
rows: { key: string; label: string; tone: AttentionTone; entries: ReactNode[] }[];
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
// The labels' column is 7.5rem, or as wide as the longest label so none wraps ("To verify or merge" needs more).
|
|
30
|
+
<dl className="m-0 grid grid-cols-[minmax(7.5rem,max-content)_minmax(0,1fr)] gap-x-4 gap-y-2" data-attention>
|
|
31
|
+
{rows.map((r) => (
|
|
32
|
+
<div key={r.key} className="contents" data-attention-row={r.key}>
|
|
33
|
+
<dt
|
|
34
|
+
className={`pt-2 text-[11px] font-medium tracking-wide whitespace-nowrap uppercase ${TONE[r.tone]}`}
|
|
35
|
+
>
|
|
36
|
+
{r.label} <span className="tabular-nums">{r.entries.length}</span>
|
|
37
|
+
</dt>
|
|
38
|
+
<dd className="m-0 grid gap-1">{r.entries}</dd>
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</dl>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What the attention list says when nothing holds the work up. */
|
|
46
|
+
export function AttentionNone({ children }: { children: ReactNode }) {
|
|
47
|
+
return (
|
|
48
|
+
<p className="m-0 text-[13px] text-(--muted-foreground)" data-attention-none>
|
|
49
|
+
{children}
|
|
50
|
+
</p>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** One entry of an attention row, with what it waits on or its next action at the right. */
|
|
55
|
+
export function AttentionEntry({ main, side }: { main: ReactNode; side?: ReactNode }) {
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex min-h-9 min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 rounded-md px-2 py-1.5 hover:bg-(--soft)">
|
|
58
|
+
<span className="min-w-0 flex-1 basis-64">{main}</span>
|
|
59
|
+
{side && <span className="ml-auto shrink-0 text-xs text-(--muted-foreground)">{side}</span>}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** An entry's text as a plain button that opens what it names. */
|
|
65
|
+
export function EntryButton({ onOpen, children }: { onOpen: () => void; children: ReactNode }) {
|
|
66
|
+
return (
|
|
67
|
+
<button type="button" onClick={onOpen}>
|
|
68
|
+
{children}
|
|
69
|
+
</button>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A reference to an item, such as `#12`, as a button that opens it; `title` names it. */
|
|
74
|
+
export function RefButton({ title, onOpen, children }: { title: string; onOpen: () => void; children: ReactNode }) {
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={onOpen}
|
|
79
|
+
title={title}
|
|
80
|
+
className="rounded px-0.5 font-medium text-(--primary) tabular-nums hover:bg-(--accent)"
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</button>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const MARKS = {
|
|
88
|
+
blocked: <BanIcon className="mt-0.5 size-3.5 shrink-0 text-(--st-review)" />,
|
|
89
|
+
met: <CheckIcon className="mt-0.5 size-3.5 shrink-0 text-(--st-done)" />,
|
|
90
|
+
fog: <CircleHelpIcon className="mt-0.5 size-3.5 shrink-0 text-(--st-amber)" />,
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
/** An entry's text after a mark: blocked, met, or an open question (fog). `clamp` keeps it to two lines. */
|
|
94
|
+
export function AttentionMark({
|
|
95
|
+
mark,
|
|
96
|
+
clamp = false,
|
|
97
|
+
children,
|
|
98
|
+
...data
|
|
99
|
+
}: {
|
|
100
|
+
mark: keyof typeof MARKS;
|
|
101
|
+
clamp?: boolean;
|
|
102
|
+
children: ReactNode;
|
|
103
|
+
} & DataAttributes) {
|
|
104
|
+
return (
|
|
105
|
+
<span {...data} className={`flex ${clamp ? 'min-w-0 ' : ''}items-start gap-1.5 text-[13px]`}>
|
|
106
|
+
{MARKS[mark]}
|
|
107
|
+
<span className={clamp ? 'line-clamp-2' : undefined}>{children}</span>
|
|
108
|
+
</span>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A part's source, status glyph and title, up to two lines. */
|
|
113
|
+
export function PartName({ source, glyph, children }: { source: ReactNode; glyph: ReactNode; children: ReactNode }) {
|
|
114
|
+
return (
|
|
115
|
+
<span className="flex min-w-0 items-start gap-1.5 text-sm">
|
|
116
|
+
{source}
|
|
117
|
+
<span className="mt-0.5 inline-flex">{glyph}</span>
|
|
118
|
+
<span className="line-clamp-2">{children}</span>
|
|
119
|
+
</span>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A part's PartName as a button that opens it; `title` names it. */
|
|
124
|
+
export function PartLink({ title, onOpen, children }: { title: string; onOpen: () => void; children: ReactNode }) {
|
|
125
|
+
return (
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={onOpen}
|
|
129
|
+
title={title}
|
|
130
|
+
className={`min-w-0 rounded text-left hover:underline ${ROW_OPENS}`}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</button>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Loading } from '../feedback/Loading';
|
|
4
|
+
|
|
5
|
+
/** Lar Factory's window: the board's main area filling the window, and beside it, outside `main`, what sits over the
|
|
6
|
+
* board (its dialogs, live regions and notifications), so their announcements sit apart from the page. */
|
|
7
|
+
export function BoardShell({ children, overlays }: { children: ReactNode; overlays?: ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="fixed inset-0 flex flex-col bg-(--background) text-(--foreground)">
|
|
10
|
+
<main className="flex min-h-0 min-w-0 flex-1 flex-col">{children}</main>
|
|
11
|
+
{overlays}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Everything under the title bar, which scrolls as one page; nothing in it shrinks to fit. */
|
|
17
|
+
export function BoardContent({ children }: { children: ReactNode }) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-6 pt-3 pb-4 *:shrink-0">{children}</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A line that stands in for content the board is still reading, centred in its area. */
|
|
24
|
+
export function ReadingLine({ label }: { label: string }) {
|
|
25
|
+
return (
|
|
26
|
+
<p className="text-base-700 m-0 flex items-center justify-center gap-2 p-6 text-sm" data-reading>
|
|
27
|
+
<Loading label={label} presentation="line" />
|
|
28
|
+
</p>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { BrandMark } from '../display/BrandMark';
|
|
4
|
+
import { TitleBar } from '../layout/TitleBar';
|
|
5
|
+
import { Tooltip, TooltipPopup, TooltipTrigger } from '../overlays/Tooltip';
|
|
6
|
+
|
|
7
|
+
/** The window drags from the bar, but never from a control in it. */
|
|
8
|
+
const drag = '[-webkit-app-region:drag] [&_:is(button,a,[tabindex])]:[-webkit-app-region:no-drag]';
|
|
9
|
+
|
|
10
|
+
interface BoardTitleBarProps {
|
|
11
|
+
/** The lockup on the left, such as BoardBrand. */
|
|
12
|
+
brand: ReactNode;
|
|
13
|
+
/** The page's heading, for screen readers only, as the brand is hidden from them. A closing dialog whose opener
|
|
14
|
+
* went away can put the focus on it. */
|
|
15
|
+
heading?: string;
|
|
16
|
+
/** How the board is doing, in the status strip: the Refreshed time, then the counters after a divider. */
|
|
17
|
+
refreshed?: ReactNode;
|
|
18
|
+
counters?: ReactNode;
|
|
19
|
+
/** The search field, up to 26rem wide beside the actions. */
|
|
20
|
+
search?: ReactNode;
|
|
21
|
+
actions: ReactNode;
|
|
22
|
+
/** In the Mac app, where macOS draws the traffic lights; in a browser, grey stand-ins hold their place. */
|
|
23
|
+
nativeWindow: boolean;
|
|
24
|
+
/** Starts the bar past the traffic lights; off while full screen hides them. */
|
|
25
|
+
pastLights: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Lar Factory's title bar, laid out as lar-server's on the design system's TitleBar: the brand and how the board is
|
|
30
|
+
* doing on the left, the search field and what you can do on the right. The bar drags the window, except on its
|
|
31
|
+
* controls, and folds by its own width: below 48rem the actions take their own row and the status strip goes.
|
|
32
|
+
*/
|
|
33
|
+
export function BoardTitleBar({
|
|
34
|
+
brand,
|
|
35
|
+
heading,
|
|
36
|
+
refreshed,
|
|
37
|
+
counters,
|
|
38
|
+
search,
|
|
39
|
+
actions,
|
|
40
|
+
nativeWindow,
|
|
41
|
+
pastLights,
|
|
42
|
+
}: BoardTitleBarProps) {
|
|
43
|
+
const status = refreshed || counters;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
data-native-window={nativeWindow || undefined}
|
|
48
|
+
className={`title-bar border-base-300 shrink-0 border-b select-none ${pastLights ? 'pl-[74px]' : ''} ${drag}`}
|
|
49
|
+
>
|
|
50
|
+
{nativeWindow ? null : <TrafficLights />}
|
|
51
|
+
{heading ? (
|
|
52
|
+
<h1 className="sr-only" tabIndex={-1}>
|
|
53
|
+
{heading}
|
|
54
|
+
</h1>
|
|
55
|
+
) : null}
|
|
56
|
+
<TitleBar
|
|
57
|
+
brand={brand}
|
|
58
|
+
title={
|
|
59
|
+
status || search ? (
|
|
60
|
+
<>
|
|
61
|
+
{status ? (
|
|
62
|
+
<div className="status-strip border-base-300 bg-base-200/60 flex shrink-0 items-center gap-0.5 rounded-lg border pr-0.5 pl-1.5">
|
|
63
|
+
{refreshed}
|
|
64
|
+
{refreshed && counters ? (
|
|
65
|
+
<span
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
className="bg-base-300 mx-0.5 h-4 w-px @max-[40rem]:hidden"
|
|
68
|
+
/>
|
|
69
|
+
) : null}
|
|
70
|
+
{counters}
|
|
71
|
+
</div>
|
|
72
|
+
) : null}
|
|
73
|
+
{search}
|
|
74
|
+
</>
|
|
75
|
+
) : null
|
|
76
|
+
}
|
|
77
|
+
actions={actions}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** In a browser, the traffic lights' grey stand-ins, 16 px in and centred in the 52 px bar, where macOS draws them. */
|
|
84
|
+
function TrafficLights() {
|
|
85
|
+
return (
|
|
86
|
+
<div className="browser-window-controls pointer-events-none fixed top-0 left-0 z-50 flex h-13 items-center">
|
|
87
|
+
<span className="traffic-lights absolute left-4 flex gap-2" aria-hidden="true">
|
|
88
|
+
{['close', 'minimize', 'zoom'].map((light) => (
|
|
89
|
+
<span key={light} className="size-3 rounded-full bg-(--traffic-stand-in)" />
|
|
90
|
+
))}
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The product's lockup in the title bar: the Lar mark, "ar" and the product's name. Screen readers skip it: the
|
|
97
|
+
* page's heading names the page. */
|
|
98
|
+
export function BoardBrand({ product }: { product: string }) {
|
|
99
|
+
return (
|
|
100
|
+
<span className="brand inline-flex shrink-0" aria-hidden="true">
|
|
101
|
+
<BrandMark label={`Lar ${product}`} product={product} />
|
|
102
|
+
</span>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A divider between groups of the title bar's actions. */
|
|
107
|
+
export function TitleBarDivider() {
|
|
108
|
+
return <span aria-hidden="true" className="app-divider bg-base-300 mx-1 h-5 w-px shrink-0" />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type RefreshHealth = 'waiting' | 'live' | 'stale' | 'disconnected';
|
|
112
|
+
|
|
113
|
+
const dots: Record<RefreshHealth, string> = {
|
|
114
|
+
waiting: 'bg-base-500',
|
|
115
|
+
live: 'bg-success',
|
|
116
|
+
stale: 'bg-amber-500',
|
|
117
|
+
disconnected: 'bg-danger',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
interface RefreshIndicatorProps {
|
|
121
|
+
health: RefreshHealth;
|
|
122
|
+
/** What the time says, such as "12s" or "Stale · 2m"; hidden on a narrow bar, where the dot speaks alone. */
|
|
123
|
+
label: ReactNode;
|
|
124
|
+
/** A count of reads landed while this page was open: each new one pings the dot once, while live. */
|
|
125
|
+
landed: number;
|
|
126
|
+
/** The tooltip's two lines: when the board last read, then how it reads or what went wrong. */
|
|
127
|
+
detail: string;
|
|
128
|
+
secondaryDetail: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* How fresh the board is, in the title bar's status strip: a dot in the health's colour (amber when stale, red while
|
|
133
|
+
* the page has lost the board server) and the time since the last read, with more in a tooltip. A tab stop, so
|
|
134
|
+
* keyboard users reach its tooltip.
|
|
135
|
+
*/
|
|
136
|
+
export function RefreshIndicator({ health, label, landed, detail, secondaryDetail }: RefreshIndicatorProps) {
|
|
137
|
+
return (
|
|
138
|
+
<Tooltip>
|
|
139
|
+
<TooltipTrigger asChild>
|
|
140
|
+
<span
|
|
141
|
+
tabIndex={0}
|
|
142
|
+
className="refreshed text-base-700 focus-visible:ring-primary inline-flex h-6 shrink-0 items-center gap-1.5 rounded-sm px-1 text-xs whitespace-nowrap tabular-nums outline-none focus-visible:ring-2 @max-[40rem]:hidden @min-[48rem]:min-w-30"
|
|
143
|
+
>
|
|
144
|
+
<span className="relative grid size-2 place-items-center">
|
|
145
|
+
{landed > 0 && health === 'live' && (
|
|
146
|
+
<span
|
|
147
|
+
key={landed}
|
|
148
|
+
data-ping
|
|
149
|
+
className="bg-success absolute size-1.5 rounded-full motion-safe:animate-[refreshed-ping_1.2s_ease-out_forwards]"
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
<span data-dot className={`size-1.5 rounded-full ${dots[health]}`} />
|
|
153
|
+
</span>
|
|
154
|
+
<span className="@max-[48rem]:sr-only">{label}</span>
|
|
155
|
+
</span>
|
|
156
|
+
</TooltipTrigger>
|
|
157
|
+
<TooltipPopup side="bottom">
|
|
158
|
+
<span className="flex flex-col gap-0.5">
|
|
159
|
+
<span data-line>{detail}</span>
|
|
160
|
+
<span data-line className="text-base-700">
|
|
161
|
+
{secondaryDetail}
|
|
162
|
+
</span>
|
|
163
|
+
</span>
|
|
164
|
+
</TooltipPopup>
|
|
165
|
+
</Tooltip>
|
|
166
|
+
);
|
|
167
|
+
}
|