From 1338ad2f5705710faae119e8b3442d7022c0b4f0 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Thu, 14 Sep 2023 09:49:51 -0400 Subject: [PATCH] feat: standardize class names (#873) --- packages/core/package.json | 12 +- packages/core/src/__mocks__/react-polyglot.ts | 2 + packages/core/src/components/App.css | 3 + packages/core/src/components/App.tsx | 9 +- .../core/src/components/ErrorBoundary.css | 43 + .../core/src/components/ErrorBoundary.tsx | 56 +- packages/core/src/components/MainView.css | 30 + packages/core/src/components/MainView.tsx | 26 +- .../collections/Collection.classes.ts | 17 + .../src/components/collections/Collection.css | 87 + .../collections/CollectionControls.tsx | 16 +- .../collections/CollectionHeader.tsx | 64 +- .../collections/CollectionSearch.css | 80 + .../collections/CollectionSearch.tsx | 146 +- .../components/collections/CollectionView.tsx | 13 +- .../components/collections/FilterControl.css | 42 + .../components/collections/FilterControl.tsx | 53 +- .../components/collections/GroupControl.css | 47 + .../components/collections/GroupControl.tsx | 47 +- .../collections/NestedCollection.css | 62 + .../collections/NestedCollection.tsx | 135 +- .../components/collections/SortControl.css | 50 + .../components/collections/SortControl.tsx | 55 +- .../collections/entries/Entries.classes.ts | 22 + .../collections/entries/Entries.css | 104 + .../collections/entries/Entries.tsx | 18 +- .../collections/entries/EntriesCollection.tsx | 26 +- .../collections/entries/EntryCard.css | 40 + .../collections/entries/EntryCard.tsx | 38 +- .../collections/entries/EntryListing.tsx | 3 +- .../entries/EntryListingCardGrid.tsx | 24 +- .../collections/entries/EntryListingGrid.tsx | 18 +- .../collections/entries/EntryListingTable.tsx | 45 +- .../collections/entries/EntryRow.tsx | 16 +- .../MobileCollectionControls.classes.ts | 10 + .../mobile/MobileCollectionControls.css | 37 + .../mobile/MobileCollectionControls.tsx | 11 +- .../mobile/MobileCollectionControlsDrawer.tsx | 31 +- .../src/components/common/alert/Alert.css | 26 + .../src/components/common/alert/Alert.tsx | 56 +- .../common/autocomplete/Autocomplete.css | 123 ++ .../common/autocomplete/Autocomplete.tsx | 436 ++-- .../src/components/common/button/Button.css | 11 + .../src/components/common/button/Button.tsx | 10 +- .../components/common/button/IconButton.css | 9 + .../components/common/button/IconButton.tsx | 14 +- .../common/button/useButtonClassNames.tsx | 63 +- .../components/common/card/Card.classes.ts | 5 + .../core/src/components/common/card/Card.css | 55 + .../core/src/components/common/card/Card.tsx | 23 +- .../components/common/card/CardActionArea.tsx | 21 +- .../components/common/card/CardContent.tsx | 4 +- .../src/components/common/card/CardHeader.tsx | 8 +- .../src/components/common/card/CardMedia.tsx | 3 +- .../components/common/checkbox/Checkbox.css | 66 + .../components/common/checkbox/Checkbox.tsx | 70 +- .../src/components/common/confirm/Confirm.css | 26 + .../src/components/common/confirm/Confirm.tsx | 64 +- .../components/common/field/ErrorMessage.css | 8 + .../components/common/field/ErrorMessage.tsx | 21 +- .../src/components/common/field/Field.css | 59 + .../src/components/common/field/Field.tsx | 82 +- .../core/src/components/common/field/Hint.css | 44 + .../core/src/components/common/field/Hint.tsx | 46 +- .../src/components/common/field/Label.css | 45 + .../src/components/common/field/Label.tsx | 47 +- .../src/components/common/image/Image.css | 16 + .../src/components/common/image/Image.tsx | 24 +- .../core/src/components/common/menu/Menu.css | 69 + .../core/src/components/common/menu/Menu.tsx | 119 +- .../src/components/common/menu/MenuGroup.css | 6 + .../src/components/common/menu/MenuGroup.tsx | 19 +- .../components/common/menu/MenuItemButton.css | 84 + .../components/common/menu/MenuItemButton.tsx | 72 +- .../components/common/menu/MenuItemLink.css | 36 + .../components/common/menu/MenuItemLink.tsx | 60 +- .../src/components/common/modal/Backdrop.tsx | 14 +- .../components/common/modal/Modal.classes.ts | 5 + .../src/components/common/modal/Modal.css | 34 + .../src/components/common/modal/Modal.tsx | 43 +- .../core/src/components/common/pill/Pill.css | 42 + .../core/src/components/common/pill/Pill.tsx | 53 +- .../common/progress/CircularProgress.css | 21 + .../common/progress/CircularProgress.tsx | 35 +- .../src/components/common/progress/Loader.css | 11 + .../src/components/common/progress/Loader.tsx | 19 +- .../src/components/common/select/Option.css | 29 + .../src/components/common/select/Option.tsx | 29 +- .../src/components/common/select/Select.css | 81 + .../src/components/common/select/Select.tsx | 202 +- .../src/components/common/switch/Switch.css | 65 + .../src/components/common/switch/Switch.tsx | 82 +- .../components/common/table/Table.classes.ts | 20 + .../src/components/common/table/Table.css | 96 + .../src/components/common/table/Table.tsx | 17 +- .../src/components/common/table/TableCell.tsx | 35 +- .../common/table/TableHeaderCell.tsx | 32 +- .../src/components/common/table/TableRow.tsx | 18 +- .../components/common/text-field/TextArea.css | 18 + .../components/common/text-field/TextArea.tsx | 61 +- .../common/text-field/TextField.css | 51 + .../common/text-field/TextField.tsx | 68 +- .../common/view-style/ViewStyleControl.css | 18 + .../common/view-style/ViewStyleControl.tsx | 15 +- .../components/common/widget/widgetFor.css | 4 + .../components/common/widget/widgetFor.tsx | 14 +- .../entry-editor/EditorInterface.css | 66 + .../entry-editor/EditorInterface.tsx | 108 +- .../components/entry-editor/EditorToolbar.css | 51 + .../components/entry-editor/EditorToolbar.tsx | 50 +- .../editor-control-pane/EditorControl.css | 6 + .../editor-control-pane/EditorControl.tsx | 7 +- .../editor-control-pane/EditorControlPane.css | 15 + .../editor-control-pane/EditorControlPane.tsx | 31 +- .../editor-control-pane/LocaleDropdown.css | 9 + .../editor-control-pane/LocaleDropdown.tsx | 18 +- .../editor-preview-pane/EditorPreviewPane.css | 37 + .../editor-preview-pane/EditorPreviewPane.tsx | 59 +- .../PreviewFrameContent.css | 4 + .../PreviewFrameContent.tsx | 14 +- .../widgets/Unknown/UnknownControl.css | 7 + .../widgets/Unknown/UnknownControl.tsx | 18 +- .../widgets/Unknown/UnknownPreview.tsx | 8 +- packages/core/src/components/login/Login.css | 56 + packages/core/src/components/login/Login.tsx | 35 +- .../media-library/MediaLibraryModal.css | 20 + .../media-library/MediaLibraryModal.tsx | 31 +- .../common/CopyToClipBoardButton.tsx | 11 +- .../common/CurrentMediaDetails.tsx | 58 +- .../media-library/common/EmptyMessage.tsx | 17 +- .../media-library/common/FileUploadButton.tsx | 7 +- .../common/FolderCreationDialog.css | 39 + .../common/FolderCreationDialog.tsx | 81 +- .../common/InlineEditTextField.css | 73 + .../common/InlineEditTextField.tsx | 88 +- .../common/MediaLibrary.classes.ts | 43 + .../media-library/common/MediaLibrary.css | 300 +++ .../media-library/common/MediaLibrary.tsx | 110 +- .../media-library/common/MediaLibraryCard.css | 156 ++ .../media-library/common/MediaLibraryCard.tsx | 189 +- .../common/MediaLibraryCardGrid.tsx | 22 +- .../common/MediaLibrarySearch.tsx | 39 +- .../components/navbar/BottomNavigation.css | 49 + .../components/navbar/BottomNavigation.tsx | 48 +- .../src/components/navbar/Breadcrumbs.css | 102 + .../src/components/navbar/Breadcrumbs.tsx | 97 +- .../core/src/components/navbar/NavLink.css | 47 + .../core/src/components/navbar/NavLink.tsx | 50 +- .../core/src/components/navbar/Navbar.css | 102 + .../core/src/components/navbar/Navbar.tsx | 76 +- .../components/navbar/NavigationDrawer.css | 9 + .../components/navbar/NavigationDrawer.tsx | 14 +- .../components/navbar/SettingsDropdown.css | 27 + .../components/navbar/SettingsDropdown.tsx | 23 +- .../src/components/navbar/Sidebar.classes.ts | 6 + .../core/src/components/navbar/Sidebar.css | 26 + .../core/src/components/navbar/Sidebar.tsx | 9 +- .../src/components/navbar/SidebarContent.tsx | 23 +- .../src/components/snackbar/SnackbarAlert.css | 85 + .../src/components/snackbar/SnackbarAlert.tsx | 109 +- .../src/components/snackbar/Snackbars.tsx | 1 - packages/core/src/lib/hooks/useIcon.css | 12 + packages/core/src/lib/hooks/useIcon.tsx | 10 +- .../core/src/lib/hooks/useMediaPersist.ts | 4 +- packages/core/src/lib/i18n.ts | 57 +- packages/core/src/lib/util/theming.util.ts | 11 + packages/core/src/locales/en/index.ts | 1 + packages/core/src/styles/main.css | 81 +- .../src/widgets/boolean/BooleanControl.tsx | 27 +- .../boolean/__tests__/BooleanControl.spec.ts | 30 - .../core/src/widgets/code/CodeControl.css | 82 + .../core/src/widgets/code/CodeControl.tsx | 97 +- .../core/src/widgets/code/CodePreview.tsx | 6 +- .../core/src/widgets/code/SettingsButton.css | 7 + .../core/src/widgets/code/SettingsButton.tsx | 19 +- .../core/src/widgets/code/SettingsPane.css | 19 + .../core/src/widgets/code/SettingsPane.tsx | 28 +- .../src/widgets/colorstring/ColorControl.css | 65 + .../src/widgets/colorstring/ColorControl.tsx | 85 +- .../src/widgets/colorstring/ColorPreview.tsx | 6 +- .../__tests__/ColorControl.spec.ts | 30 - .../src/widgets/datetime/DateTimeControl.css | 16 + .../src/widgets/datetime/DateTimeControl.tsx | 30 +- .../src/widgets/datetime/DateTimePreview.tsx | 6 +- .../__tests__/DateTimeControl.spec.ts | 31 - .../widgets/datetime/components/NowButton.css | 7 + .../widgets/datetime/components/NowButton.tsx | 12 +- .../widgets/file/FileImageControl.classes.ts | 21 + .../src/widgets/file/FileImageControl.css | 79 + .../widgets/file/__test__/FileControl.spec.ts | 28 - .../widgets/file/components/SortableImage.css | 88 + .../widgets/file/components/SortableImage.tsx | 107 +- .../widgets/file/components/SortableLink.css | 48 + .../widgets/file/components/SortableLink.tsx | 65 +- .../core/src/widgets/file/withFileControl.tsx | 78 +- .../src/widgets/keyvalue/KeyValueControl.css | 51 + .../src/widgets/keyvalue/KeyValueControl.tsx | 57 +- .../src/widgets/keyvalue/KeyValuePreview.tsx | 6 +- .../__tests__/KeyValueControl.spec.ts | 31 - .../src/widgets/list/DelimitedListControl.tsx | 12 + .../src/widgets/list/ListControl.classes.ts | 27 + .../core/src/widgets/list/ListControl.css | 114 ++ .../core/src/widgets/list/ListControl.tsx | 179 +- .../core/src/widgets/list/ListPreview.tsx | 5 +- .../list/__tests__/ListControl.spec.tsx | 26 - .../list/components/ListFieldWrapper.tsx | 86 +- .../src/widgets/list/components/ListItem.tsx | 22 +- .../list/components/ListItemWrapper.css | 155 ++ .../list/components/ListItemWrapper.tsx | 173 +- packages/core/src/widgets/map/MapControl.css | 8 + packages/core/src/widgets/map/MapPreview.tsx | 6 +- .../core/src/widgets/map/withMapControl.tsx | 31 +- .../markdown/MarkdownControl.classes.ts | 16 + .../src/widgets/markdown/MarkdownControl.css | 28 + .../src/widgets/markdown/MarkdownPreview.tsx | 5 +- .../widgets/markdown/plate/PlateEditor.tsx | 25 +- .../balloon-toolbar/BalloonToolbar.css | 28 + .../balloon-toolbar/BalloonToolbar.tsx | 117 +- .../__tests__/BalloonToolbar.spec.tsx | 11 +- .../components/buttons/FontTypeSelect.css | 82 + .../components/buttons/FontTypeSelect.tsx | 119 +- .../buttons/ShortcodeToolbarButton.css | 11 + .../buttons/ShortcodeToolbarButton.tsx | 19 +- .../common/ColorPickerToolbarDropdown.tsx | 13 +- .../buttons/common/ToolbarButton.css | 20 + .../buttons/common/ToolbarButton.tsx | 27 +- .../components/color-picker/ColorButton.css | 23 + .../components/color-picker/ColorButton.tsx | 26 +- .../components/color-picker/ColorPicker.tsx | 2 +- .../plate/components/color-picker/Colors.tsx | 2 +- .../components/color-picker/CustomColors.tsx | 2 +- .../components/color-picker/constants.ts | 438 ++++ .../plate/components/color-picker/types.ts | 5 + .../plate/components/common/MediaPopover.css | 35 + .../plate/components/common/MediaPopover.tsx | 59 +- .../nodes/blockquote/BlockquoteElement.css | 6 + .../nodes/blockquote/BlockquoteElement.tsx | 16 +- .../nodes/code-block/CodeBlockElement.css | 18 + .../nodes/code-block/CodeBlockElement.tsx | 24 +- .../components/nodes/headings/Heading1.css | 6 + .../components/nodes/headings/Heading1.tsx | 17 +- .../components/nodes/headings/Heading2.css | 6 + .../components/nodes/headings/Heading2.tsx | 19 +- .../components/nodes/headings/Heading3.css | 6 + .../components/nodes/headings/Heading3.tsx | 17 +- .../components/nodes/headings/Heading4.css | 5 + .../components/nodes/headings/Heading4.tsx | 16 +- .../components/nodes/headings/Heading5.css | 6 + .../components/nodes/headings/Heading5.tsx | 17 +- .../components/nodes/headings/Heading6.css | 6 + .../components/nodes/headings/Heading6.tsx | 17 +- .../components/nodes/link/LinkElement.css | 5 + .../components/nodes/link/withLinkElement.tsx | 11 +- .../components/nodes/list/ListItemElement.css | 4 + .../components/nodes/list/ListItemElement.tsx | 16 +- .../nodes/list/OrderedListElement.css | 4 + .../nodes/list/OrderedListElement.tsx | 17 +- .../nodes/list/UnorderedListElement.css | 4 + .../nodes/list/UnorderedListElement.tsx | 17 +- .../nodes/paragraph/ParagraphElement.css | 4 + .../nodes/paragraph/ParagraphElement.tsx | 14 +- .../components/nodes/table/Table.classes.ts | 12 + .../plate/components/nodes/table/Table.css | 51 + .../TableCellElement/TableCellElement.tsx | 17 +- .../TableHeaderCellElement.tsx | 19 +- .../nodes/table/TableElement/TableElement.tsx | 33 +- .../table/TableRowElement/TableRowElement.tsx | 15 +- .../plate/components/toolbar/Toolbar.css | 23 + .../plate/components/toolbar/Toolbar.tsx | 53 +- .../__tests__/useMarkdownToSlate.ispec.ts | 6 +- .../plate/hooks/useMarkdownToSlate.ts | 6 +- .../plate/hooks/useToolbarButtons.css | 14 + .../plate/hooks/useToolbarButtons.tsx | 15 +- .../cursor-overlay/CursorOverlayContainer.tsx | 4 +- .../markdown/plate/plugins/list/withList.ts | 2 +- .../widgets/markdown/withMarkdownControl.tsx | 26 +- .../core/src/widgets/number/NumberControl.tsx | 20 + .../core/src/widgets/number/NumberPreview.tsx | 6 +- .../number/__tests__/NumberControl.spec.ts | 30 - .../widgets/object/ObjectControl.classes.ts | 18 + .../core/src/widgets/object/ObjectControl.css | 91 + .../core/src/widgets/object/ObjectControl.tsx | 6 +- .../src/widgets/object/ObjectFieldWrapper.tsx | 81 +- .../core/src/widgets/object/ObjectPreview.tsx | 6 +- .../object/__tests__/ObjectControl.spec.tsx | 34 +- .../src/widgets/relation/RelationControl.css | 20 + .../src/widgets/relation/RelationControl.tsx | 29 +- .../src/widgets/relation/RelationPreview.tsx | 6 +- .../__tests__/RelationControl.spec.ts | 113 +- .../core/src/widgets/select/SelectControl.css | 6 + .../core/src/widgets/select/SelectControl.tsx | 24 +- .../core/src/widgets/select/SelectPreview.tsx | 6 +- .../select/__tests__/SelectControl.spec.ts | 204 +- .../core/src/widgets/string/StringControl.tsx | 20 + .../core/src/widgets/string/StringPreview.tsx | 6 +- .../string/__tests__/StringControl.spec.ts | 24 - .../core/src/widgets/text/TextControl.tsx | 26 +- .../core/src/widgets/text/TextPreview.tsx | 6 +- .../text/__tests__/TextControl.spec.ts | 24 - .../core/src/widgets/uuid/UUIDControl.css | 14 + .../core/src/widgets/uuid/UUIDControl.tsx | 28 +- .../core/src/widgets/uuid/UUIDPreview.tsx | 6 +- .../uuid/__tests__/UUIDControl.spec.ts | 24 - tailwind.base.config.js | 1 + yarn.lock | 1774 +++++++---------- 305 files changed, 8691 insertions(+), 5457 deletions(-) create mode 100644 packages/core/src/components/App.css create mode 100644 packages/core/src/components/ErrorBoundary.css create mode 100644 packages/core/src/components/MainView.css create mode 100644 packages/core/src/components/collections/Collection.classes.ts create mode 100644 packages/core/src/components/collections/Collection.css create mode 100644 packages/core/src/components/collections/CollectionSearch.css create mode 100644 packages/core/src/components/collections/FilterControl.css create mode 100644 packages/core/src/components/collections/GroupControl.css create mode 100644 packages/core/src/components/collections/NestedCollection.css create mode 100644 packages/core/src/components/collections/SortControl.css create mode 100644 packages/core/src/components/collections/entries/Entries.classes.ts create mode 100644 packages/core/src/components/collections/entries/Entries.css create mode 100644 packages/core/src/components/collections/entries/EntryCard.css create mode 100644 packages/core/src/components/collections/mobile/MobileCollectionControls.classes.ts create mode 100644 packages/core/src/components/collections/mobile/MobileCollectionControls.css create mode 100644 packages/core/src/components/common/alert/Alert.css create mode 100644 packages/core/src/components/common/autocomplete/Autocomplete.css create mode 100644 packages/core/src/components/common/button/Button.css create mode 100644 packages/core/src/components/common/button/IconButton.css create mode 100644 packages/core/src/components/common/card/Card.classes.ts create mode 100644 packages/core/src/components/common/card/Card.css create mode 100644 packages/core/src/components/common/checkbox/Checkbox.css create mode 100644 packages/core/src/components/common/confirm/Confirm.css create mode 100644 packages/core/src/components/common/field/ErrorMessage.css create mode 100644 packages/core/src/components/common/field/Field.css create mode 100644 packages/core/src/components/common/field/Hint.css create mode 100644 packages/core/src/components/common/field/Label.css create mode 100644 packages/core/src/components/common/image/Image.css create mode 100644 packages/core/src/components/common/menu/Menu.css create mode 100644 packages/core/src/components/common/menu/MenuGroup.css create mode 100644 packages/core/src/components/common/menu/MenuItemButton.css create mode 100644 packages/core/src/components/common/menu/MenuItemLink.css create mode 100644 packages/core/src/components/common/modal/Modal.classes.ts create mode 100644 packages/core/src/components/common/modal/Modal.css create mode 100644 packages/core/src/components/common/pill/Pill.css create mode 100644 packages/core/src/components/common/progress/CircularProgress.css create mode 100644 packages/core/src/components/common/progress/Loader.css create mode 100644 packages/core/src/components/common/select/Option.css create mode 100644 packages/core/src/components/common/select/Select.css create mode 100644 packages/core/src/components/common/switch/Switch.css create mode 100644 packages/core/src/components/common/table/Table.classes.ts create mode 100644 packages/core/src/components/common/table/Table.css create mode 100644 packages/core/src/components/common/text-field/TextArea.css create mode 100644 packages/core/src/components/common/text-field/TextField.css create mode 100644 packages/core/src/components/common/view-style/ViewStyleControl.css create mode 100644 packages/core/src/components/common/widget/widgetFor.css create mode 100644 packages/core/src/components/entry-editor/EditorInterface.css create mode 100644 packages/core/src/components/entry-editor/EditorToolbar.css create mode 100644 packages/core/src/components/entry-editor/editor-control-pane/EditorControl.css create mode 100644 packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.css create mode 100644 packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.css create mode 100644 packages/core/src/components/entry-editor/editor-preview-pane/EditorPreviewPane.css create mode 100644 packages/core/src/components/entry-editor/editor-preview-pane/PreviewFrameContent.css create mode 100644 packages/core/src/components/entry-editor/widgets/Unknown/UnknownControl.css create mode 100644 packages/core/src/components/login/Login.css create mode 100644 packages/core/src/components/media-library/MediaLibraryModal.css create mode 100644 packages/core/src/components/media-library/common/FolderCreationDialog.css create mode 100644 packages/core/src/components/media-library/common/InlineEditTextField.css create mode 100644 packages/core/src/components/media-library/common/MediaLibrary.classes.ts create mode 100644 packages/core/src/components/media-library/common/MediaLibrary.css create mode 100644 packages/core/src/components/media-library/common/MediaLibraryCard.css create mode 100644 packages/core/src/components/navbar/BottomNavigation.css create mode 100644 packages/core/src/components/navbar/Breadcrumbs.css create mode 100644 packages/core/src/components/navbar/NavLink.css create mode 100644 packages/core/src/components/navbar/Navbar.css create mode 100644 packages/core/src/components/navbar/NavigationDrawer.css create mode 100644 packages/core/src/components/navbar/SettingsDropdown.css create mode 100644 packages/core/src/components/navbar/Sidebar.classes.ts create mode 100644 packages/core/src/components/navbar/Sidebar.css create mode 100644 packages/core/src/components/snackbar/SnackbarAlert.css create mode 100644 packages/core/src/lib/hooks/useIcon.css create mode 100644 packages/core/src/lib/util/theming.util.ts create mode 100644 packages/core/src/widgets/code/CodeControl.css create mode 100644 packages/core/src/widgets/code/SettingsButton.css create mode 100644 packages/core/src/widgets/code/SettingsPane.css create mode 100644 packages/core/src/widgets/colorstring/ColorControl.css create mode 100644 packages/core/src/widgets/datetime/DateTimeControl.css create mode 100644 packages/core/src/widgets/datetime/components/NowButton.css create mode 100644 packages/core/src/widgets/file/FileImageControl.classes.ts create mode 100644 packages/core/src/widgets/file/FileImageControl.css create mode 100644 packages/core/src/widgets/file/components/SortableImage.css create mode 100644 packages/core/src/widgets/file/components/SortableLink.css create mode 100644 packages/core/src/widgets/keyvalue/KeyValueControl.css create mode 100644 packages/core/src/widgets/list/ListControl.classes.ts create mode 100644 packages/core/src/widgets/list/ListControl.css create mode 100644 packages/core/src/widgets/list/components/ListItemWrapper.css create mode 100644 packages/core/src/widgets/map/MapControl.css create mode 100644 packages/core/src/widgets/markdown/MarkdownControl.classes.ts create mode 100644 packages/core/src/widgets/markdown/MarkdownControl.css create mode 100644 packages/core/src/widgets/markdown/plate/components/balloon-toolbar/BalloonToolbar.css create mode 100644 packages/core/src/widgets/markdown/plate/components/buttons/FontTypeSelect.css create mode 100644 packages/core/src/widgets/markdown/plate/components/buttons/ShortcodeToolbarButton.css create mode 100644 packages/core/src/widgets/markdown/plate/components/buttons/common/ToolbarButton.css create mode 100644 packages/core/src/widgets/markdown/plate/components/color-picker/ColorButton.css create mode 100644 packages/core/src/widgets/markdown/plate/components/color-picker/constants.ts create mode 100644 packages/core/src/widgets/markdown/plate/components/color-picker/types.ts create mode 100644 packages/core/src/widgets/markdown/plate/components/common/MediaPopover.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/blockquote/BlockquoteElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/code-block/CodeBlockElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading1.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading2.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading3.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading4.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading5.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/headings/Heading6.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/link/LinkElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/list/ListItemElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/list/OrderedListElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/list/UnorderedListElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/paragraph/ParagraphElement.css create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/table/Table.classes.ts create mode 100644 packages/core/src/widgets/markdown/plate/components/nodes/table/Table.css create mode 100644 packages/core/src/widgets/markdown/plate/components/toolbar/Toolbar.css create mode 100644 packages/core/src/widgets/markdown/plate/hooks/useToolbarButtons.css create mode 100644 packages/core/src/widgets/object/ObjectControl.classes.ts create mode 100644 packages/core/src/widgets/object/ObjectControl.css create mode 100644 packages/core/src/widgets/relation/RelationControl.css create mode 100644 packages/core/src/widgets/select/SelectControl.css create mode 100644 packages/core/src/widgets/uuid/UUIDControl.css diff --git a/packages/core/package.json b/packages/core/package.json index a55f57cb8..9b7e6e855 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -66,11 +66,10 @@ "@emotion/css": "11.10.6", "@emotion/react": "11.10.6", "@emotion/styled": "11.10.6", - "@headlessui/react": "1.7.7", "@lezer/common": "1.0.2", "@mdx-js/mdx": "2.3.0", "@mdx-js/react": "2.3.0", - "@mui/base": "5.0.0-alpha.124", + "@mui/base": "5.0.0-beta.14", "@mui/material": "5.11.16", "@mui/system": "5.11.16", "@mui/x-date-pickers": "5.0.20", @@ -84,9 +83,10 @@ "@styled-icons/material-rounded": "10.47.0", "@styled-icons/remix-editor": "10.46.0", "@styled-icons/simple-icons": "10.46.0", - "@udecode/plate": "21.3.2", - "@udecode/plate-juice": "21.3.2", - "@udecode/plate-serializer-md": "21.3.2", + "@udecode/plate": "23.7.4", + "@udecode/plate-cursor": "23.7.4", + "@udecode/plate-juice": "23.7.4", + "@udecode/plate-serializer-md": "23.7.4", "@uiw/codemirror-extensions-langs": "4.19.16", "@uiw/react-codemirror": "4.19.16", "ajv": "8.12.0", @@ -159,7 +159,7 @@ "slate": "0.94.1", "slate-history": "0.93.0", "slate-hyperscript": "0.77.0", - "slate-react": "0.95.0", + "slate-react": "0.98.3", "stream-browserify": "3.0.0", "styled-components": "5.3.10", "symbol-observable": "4.0.0", diff --git a/packages/core/src/__mocks__/react-polyglot.ts b/packages/core/src/__mocks__/react-polyglot.ts index 06339c717..21d52ae52 100644 --- a/packages/core/src/__mocks__/react-polyglot.ts +++ b/packages/core/src/__mocks__/react-polyglot.ts @@ -12,3 +12,5 @@ export const translate = () => (Component: FC) => { return React.createElement(Component, { t, ...props }); }; }; + +export const useTranslate = () => (key: string, _options: unknown) => key; diff --git a/packages/core/src/components/App.css b/packages/core/src/components/App.css new file mode 100644 index 000000000..3249639ee --- /dev/null +++ b/packages/core/src/components/App.css @@ -0,0 +1,3 @@ +.CMS_App_root { + @apply h-full; +} diff --git a/packages/core/src/components/App.tsx b/packages/core/src/components/App.tsx index a969301d0..6ddf30cf1 100644 --- a/packages/core/src/components/App.tsx +++ b/packages/core/src/components/App.tsx @@ -20,6 +20,7 @@ import { currentBackend } from '@staticcms/core/backend'; import { changeTheme } from '../actions/globalUI'; import { invokeEvent } from '../lib/registry'; import { getDefaultPath } from '../lib/util/collection.util'; +import { generateClassNames } from '../lib/util/theming.util'; import { selectTheme } from '../reducers/selectors/globalUI'; import { useAppDispatch, useAppSelector } from '../store/hooks'; import CollectionRoute from './collections/CollectionRoute'; @@ -37,6 +38,10 @@ import type { RootState } from '@staticcms/core/store'; import type { ComponentType } from 'react'; import type { ConnectedProps } from 'react-redux'; +import './App.css'; + +export const classes = generateClassNames('App', ['root', 'content']); + TopBarProgress.config({ barColors: { 0: '#000', @@ -267,8 +272,8 @@ const App = ({ <>
-
-
+
+
{content} diff --git a/packages/core/src/components/ErrorBoundary.css b/packages/core/src/components/ErrorBoundary.css new file mode 100644 index 000000000..52d0b32fb --- /dev/null +++ b/packages/core/src/components/ErrorBoundary.css @@ -0,0 +1,43 @@ +.CMS_ErrorBoundary_root { + @apply flex + flex-col + bg-slate-50 + dark:bg-slate-900 + min-h-screen + gap-2; +} + +.CMS_ErrorBoundary_header { + @apply flex + flex-col + py-2 + px-4 + gap-2; +} + +.CMS_ErrorBoundary_title { + @apply text-2xl + font-bold; +} + +.CMS_ErrorBoundary_report-link { + @apply text-blue-500 + hover:underline; +} + +.CMS_ErrorBoundary_content { + @apply flex + flex-col + py-2 + px-4 + gap-2; +} + +.CMS_ErrorBoundary_details-title { + @apply text-xl + font-bold; +} + +.CMS_ErrorBoundary_error-line { + @apply whitespace-pre; +} diff --git a/packages/core/src/components/ErrorBoundary.tsx b/packages/core/src/components/ErrorBoundary.tsx index bb6f2cb0a..1d54d6349 100644 --- a/packages/core/src/components/ErrorBoundary.tsx +++ b/packages/core/src/components/ErrorBoundary.tsx @@ -6,10 +6,23 @@ import { translate } from 'react-polyglot'; import yaml from 'yaml'; import { localForage } from '@staticcms/core/lib/util'; +import { generateClassNames } from '../lib/util/theming.util'; import type { Config, TranslatedProps } from '@staticcms/core/interface'; import type { ComponentClass, ReactNode } from 'react'; +import './ErrorBoundary.css'; + +export const classes = generateClassNames('ErrorBoundary', [ + 'root', + 'header', + 'title', + 'report-link', + 'content', + 'details-title', + 'error-line', +]); + const ISSUE_URL = 'https://github.com/StaticJsCMS/static-cms/issues/new?'; function getIssueTemplate(version: string, provider: string, browser: string, config: string) { @@ -147,27 +160,9 @@ class ErrorBoundary extends Component, Error return this.props.children; } return ( -
-
-

{t('ui.errorBoundary.title')}

+
+
+

{t('ui.errorBoundary.title')}

{t('ui.errorBoundary.details')} , Error target="_blank" rel="noopener noreferrer" data-testid="issue-url" - className=" - text-blue-500 - hover:underline - " + className={classes['report-link']} > {t('ui.errorBoundary.reportIt')} @@ -193,19 +185,11 @@ class ErrorBoundary extends Component, Error


-
-

{t('ui.errorBoundary.detailsHeading')}

+
+

{t('ui.errorBoundary.detailsHeading')}

{errorMessage.split('\n').map((item, index) => [ - + {item} ,
, diff --git a/packages/core/src/components/MainView.css b/packages/core/src/components/MainView.css new file mode 100644 index 000000000..48826349d --- /dev/null +++ b/packages/core/src/components/MainView.css @@ -0,0 +1,30 @@ +.CMS_MainView_root { + @apply flex + bg-slate-50 + dark:bg-slate-900; +} + +.CMS_MainView_body { + @apply h-main-mobile + md:h-main + relative + w-full; + + &.CMS_MainView_show-left-nav { + @apply left-0 + md:w-main; + } + + &:not(.CMS_MainView_no-margin) { + @apply px-5 + py-4; + } + + &.CMS_MainView_no-scroll { + @apply overflow-hidden; + } + + &:not(.CMS_MainView_no-scroll) { + @apply overflow-y-auto; + } +} diff --git a/packages/core/src/components/MainView.tsx b/packages/core/src/components/MainView.tsx index fb813785a..4a678e824 100644 --- a/packages/core/src/components/MainView.tsx +++ b/packages/core/src/components/MainView.tsx @@ -2,6 +2,7 @@ import React from 'react'; import TopBarProgress from 'react-topbar-progress-indicator'; import classNames from '../lib/util/classNames.util'; +import { generateClassNames } from '../lib/util/theming.util'; import BottomNavigation from './navbar/BottomNavigation'; import Navbar from './navbar/Navbar'; import Sidebar from './navbar/Sidebar'; @@ -9,6 +10,16 @@ import Sidebar from './navbar/Sidebar'; import type { ReactNode } from 'react'; import type { Breadcrumb, Collection } from '../interface'; +import './MainView.css'; + +export const classes = generateClassNames('MainView', [ + 'root', + 'body', + 'show-left-nav', + 'no-margin', + 'no-scroll', +]); + TopBarProgress.config({ barColors: { 0: '#000', @@ -46,20 +57,15 @@ const MainView = ({ showQuickCreate={showQuickCreate} navbarActions={navbarActions} /> -

+
{showLeftNav ? : null}
{children} diff --git a/packages/core/src/components/collections/Collection.classes.ts b/packages/core/src/components/collections/Collection.classes.ts new file mode 100644 index 000000000..5a8ad50e8 --- /dev/null +++ b/packages/core/src/components/collections/Collection.classes.ts @@ -0,0 +1,17 @@ +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + +const collectionClasses = generateClassNames('Collection', [ + 'root', + 'content', + 'search-query', + 'description', + 'description-card', + 'controls', + 'header-wrapper', + 'header', + 'header-icon', + 'header-label', + 'new-entry-button', +]); + +export default collectionClasses; diff --git a/packages/core/src/components/collections/Collection.css b/packages/core/src/components/collections/Collection.css new file mode 100644 index 000000000..2ee0aa6e4 --- /dev/null +++ b/packages/core/src/components/collections/Collection.css @@ -0,0 +1,87 @@ +.CMS_Collection_root { + @apply flex + flex-col + h-full + px-5 + pt-4 + overflow-hidden; +} + +.CMS_Collection_content { + @apply flex + items-center + mb-4 + flex-row + gap-4 + sm:gap-0 + flex-wrap + md:flex-nowrap; +} + +.CMS_Collection_search-query { + @apply flex-grow; +} + +.CMS_Collection_description { + @apply flex + mb-4; +} + +.CMS_Collection_description-card { + @apply flex-grow + px-3.5 + py-2.5 + text-sm; +} + +.CMS_Collection_controls { + @apply flex + items-center + relative + z-20 + md:flex-grow-0 + flex-grow + justify-end + gap-1.5 + sm:w-auto + lg:gap-2; +} + +.CMS_Collection_header-wrapper { + @apply flex + gap-2 + sm:gap-2 + md:gap-4 + md:w-full + justify-normal + xs:justify-between + sm:justify-normal + truncate; +} + +.CMS_Collection_header { + @apply text-xl + font-semibold + flex + items-center + text-gray-800 + dark:text-gray-300 + gap-2 + md:w-auto; +} + +.CMS_Collection_header-icon { + @apply flex + items-center; +} + +.CMS_Collection_header-label { + @apply max-w-collection-header + md:flex-grow + truncate; +} + +.CMS_Collection_new-entry-button { + @apply hidden + md:flex; +} diff --git a/packages/core/src/components/collections/CollectionControls.tsx b/packages/core/src/components/collections/CollectionControls.tsx index aa2ebd240..7af6b09a1 100644 --- a/packages/core/src/components/collections/CollectionControls.tsx +++ b/packages/core/src/components/collections/CollectionControls.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import ViewStyleControl from '../common/view-style/ViewStyleControl'; +import collectionClasses from './Collection.classes'; import FilterControl from './FilterControl'; import GroupControl from './GroupControl'; import MobileCollectionControls from './mobile/MobileCollectionControls'; @@ -61,20 +62,7 @@ const CollectionControls = ({ return ( <> -
+
{showGroupControl || showFilterControl || showFilterControl ? ( > = ({ collecti }, [collection, collectionLabel, entries, filterTerm]); return ( - <> -
-

-
{icon}
-
- {pluralLabel} -
-

- {newEntryUrl ? ( - - ) : null} -
- +
+

+
{icon}
+
{pluralLabel}
+

+ {newEntryUrl ? ( + + ) : null} +
); }; diff --git a/packages/core/src/components/collections/CollectionSearch.css b/packages/core/src/components/collections/CollectionSearch.css new file mode 100644 index 000000000..eba7a555e --- /dev/null +++ b/packages/core/src/components/collections/CollectionSearch.css @@ -0,0 +1,80 @@ +.CMS_CollectionSearch_content { + @apply relative; +} + +.CMS_CollectionSearch_icon-wrapper { + @apply absolute + inset-y-0 + left-0 + flex + items-center + pl-3 + pointer-events-none; +} + +.CMS_CollectionSearch_icon { + @apply w-5 + h-5 + text-gray-500 + dark:text-gray-400; +} + +.CMS_CollectionSearch_input { + @apply block + w-full + p-1.5 + pl-10 + text-sm + text-gray-800 + border + border-gray-300 + rounded-lg + bg-gray-50 + focus-visible:outline-none + focus:ring-4 + focus:ring-gray-200 + dark:bg-gray-700 + dark:border-gray-600 + dark:placeholder-gray-400 + dark:text-white + dark:focus:ring-slate-700; +} + +.CMS_CollectionSearch_search-in { + @apply absolute + overflow-auto + rounded-md + bg-white + text-base + shadow-md + ring-1 + ring-black + ring-opacity-5 + focus:outline-none + sm:text-sm + z-[1300] + dark:bg-slate-700 + dark:shadow-lg; +} + +.CMS_CollectionSearch_search-in-content { + @apply flex + flex-col + min-w-[200px]; +} + +.CMS_CollectionSearch_search-in-label { + @apply text-base + text-slate-500 + dark:text-slate-400 + py-2 + px-3; +} + +.CMS_CollectionSearch_search-in-option { + @apply cursor-pointer + hover:bg-blue-500 + hover:text-gray-100 + py-2 + px-3; +} diff --git a/packages/core/src/components/collections/CollectionSearch.tsx b/packages/core/src/components/collections/CollectionSearch.tsx index 533614887..b63843fbb 100644 --- a/packages/core/src/components/collections/CollectionSearch.tsx +++ b/packages/core/src/components/collections/CollectionSearch.tsx @@ -1,11 +1,27 @@ -import PopperUnstyled from '@mui/base/PopperUnstyled'; +import { Popper as BasePopper } from '@mui/base/Popper'; import { Search as SearchIcon } from '@styled-icons/material/Search'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { translate } from 'react-polyglot'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + import type { Collection, Collections, TranslatedProps } from '@staticcms/core/interface'; import type { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent } from 'react'; +import './CollectionSearch.css'; + +export const classes = generateClassNames('CollectionSearch', [ + 'root', + 'content', + 'icon-wrapper', + 'icon', + 'input', + 'search-in', + 'search-in-content', + 'search-in-label', + 'search-in-option', +]); + interface CollectionSearchProps { collections: Collections; collection?: Collection; @@ -136,39 +152,21 @@ const CollectionSearch = ({ [submitSearch], ); - const handleClick = useCallback((event: MouseEvent) => { + const handleClick = useCallback((event: MouseEvent) => { event.stopPropagation(); + setAnchorEl(event.currentTarget); }, []); return ( -
-
-
- +
+
+
+
- -
+
+
{t('collection.sidebar.searchIn')}
- {t('collection.sidebar.searchIn')} -
-
handleSuggestionClick(e, -1)} onMouseDown={e => e.preventDefault()} > @@ -239,52 +200,13 @@ const CollectionSearch = ({ key={idx} onClick={e => handleSuggestionClick(e, idx)} onMouseDown={e => e.preventDefault()} - className=" - cursor-pointer - hover:bg-blue-500 - hover:color-gray-100 - py-2 - px-3 - " - > - {collection.label} -
- ))} -
- - {/* -
-
{t('collection.sidebar.searchIn')}
-
handleSuggestionClick(e, -1)} onMouseDown={e => e.preventDefault()}> - {t('collection.sidebar.allCollections')} -
- {collections.map((collection, idx) => ( -
handleSuggestionClick(e, idx)} - onMouseDown={e => e.preventDefault()} + className={classes['search-in-option']} > {collection.label}
))}
-
*/} +
); }; diff --git a/packages/core/src/components/collections/CollectionView.tsx b/packages/core/src/components/collections/CollectionView.tsx index 6da17489a..ae049db16 100644 --- a/packages/core/src/components/collections/CollectionView.tsx +++ b/packages/core/src/components/collections/CollectionView.tsx @@ -21,6 +21,7 @@ import { selectViewStyle, } from '@staticcms/core/reducers/selectors/entries'; import Card from '../common/card/Card'; +import collectionClasses from './Collection.classes'; import CollectionControls from './CollectionControls'; import CollectionHeader from './CollectionHeader'; import EntriesCollection from './entries/EntriesCollection'; @@ -37,6 +38,8 @@ import type { RootState } from '@staticcms/core/store'; import type { ComponentType } from 'react'; import type { ConnectedProps } from 'react-redux'; +import './Collection.css'; + const CollectionView = ({ collection, collections, @@ -183,11 +186,11 @@ const CollectionView = ({ const collectionDescription = collection?.description; return ( -
-
+
+
{isSearchResults ? ( <> -
+
{t(searchResultKey, { searchTerm, collection: collection?.label })}
@@ -212,8 +215,8 @@ const CollectionView = ({ )}
{collectionDescription ? ( -
- {collectionDescription} +
+ {collectionDescription}
) : null} {entries} diff --git a/packages/core/src/components/collections/FilterControl.css b/packages/core/src/components/collections/FilterControl.css new file mode 100644 index 000000000..638f4aca1 --- /dev/null +++ b/packages/core/src/components/collections/FilterControl.css @@ -0,0 +1,42 @@ +.CMS_FilterControl_root { + @apply hidden + lg:block; +} + +.CMS_FilterControl_filter-label { + @apply ml-2 + text-sm + font-medium + text-gray-800 + dark:text-gray-300; +} + +.CMS_FilterControl_list-root { + @apply flex + flex-col + gap-2; +} + +.CMS_FilterControl_list-label { + @apply text-lg + font-bold + text-gray-800 + dark:text-white; +} + +.CMS_FilterControl_list-filter { + @apply ml-1.5 + font-medium + flex + items-center + text-gray-800 + dark:text-gray-300; +} + +.CMS_FilterControl_list-filter-label { + @apply ml-2 + text-base + font-medium + text-gray-800 + dark:text-gray-300; +} diff --git a/packages/core/src/components/collections/FilterControl.tsx b/packages/core/src/components/collections/FilterControl.tsx index bbdc31eb8..df699feac 100644 --- a/packages/core/src/components/collections/FilterControl.tsx +++ b/packages/core/src/components/collections/FilterControl.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { translate } from 'react-polyglot'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import Menu from '../common/menu/Menu'; import MenuGroup from '../common/menu/MenuGroup'; import MenuItemButton from '../common/menu/MenuItemButton'; @@ -8,6 +9,18 @@ import MenuItemButton from '../common/menu/MenuItemButton'; import type { FilterMap, TranslatedProps, ViewFilter } from '@staticcms/core/interface'; import type { FC, MouseEvent } from 'react'; +import './FilterControl.css'; + +export const classes = generateClassNames('FilterControl', [ + 'root', + 'filter', + 'filter-label', + 'list-root', + 'list-label', + 'list-filter', + 'list-filter-label', +]); + export interface FilterControlProps { filter: Record | undefined; viewFilters: ViewFilter[] | undefined; @@ -35,31 +48,15 @@ const FilterControl = ({ if (variant === 'list') { return ( -
-

- {t('collection.collectionTop.filterBy')} -

+
+

{t('collection.collectionTop.filterBy')}

{viewFilters.map(viewFilter => { const checked = Boolean(viewFilter.id && filter[viewFilter?.id]?.active) ?? false; const labelId = `filter-list-label-${viewFilter.label}`; return (
- +
); })} @@ -86,26 +80,27 @@ const FilterControl = ({ key="filter-by-menu" label={t('collection.collectionTop.filterBy')} variant={anyActive ? 'contained' : 'outlined'} - rootClassName="hidden lg:block" + rootClassName={classes.root} > {viewFilters.map(viewFilter => { const checked = Boolean(viewFilter.id && filter[viewFilter?.id]?.active) ?? false; const labelId = `filter-list-label-${viewFilter.label}`; return ( - + - + ); })} diff --git a/packages/core/src/components/collections/GroupControl.css b/packages/core/src/components/collections/GroupControl.css new file mode 100644 index 000000000..82aa37944 --- /dev/null +++ b/packages/core/src/components/collections/GroupControl.css @@ -0,0 +1,47 @@ +.CMS_GroupControl_root { + @apply hidden + lg:block; +} + +.CMS_GroupControl_list { + @apply flex + flex-col + gap-2; +} + +.CMS_GroupControl_list-label { + @apply text-lg + font-bold + text-gray-800 + dark:text-white; +} + +.CMS_GroupControl_list-option { + @apply ml-0.5 + font-medium + flex + items-center + text-gray-800 + dark:text-gray-300; +} + +.CMS_GroupControl_list-option-label { + @apply ml-2 + text-base + font-medium + text-gray-800 + dark:text-gray-300; +} + +.CMS_GroupControl_list-option-checked-icon { + @apply ml-2 + w-6 + h-6 + text-blue-500; +} + +.CMS_GroupControl_list-option-not-checked { + @apply ml-2 + w-6 + h-6; +} diff --git a/packages/core/src/components/collections/GroupControl.tsx b/packages/core/src/components/collections/GroupControl.tsx index 6eb87935c..c4c5381a1 100644 --- a/packages/core/src/components/collections/GroupControl.tsx +++ b/packages/core/src/components/collections/GroupControl.tsx @@ -2,6 +2,7 @@ import { Check as CheckIcon } from '@styled-icons/material/Check'; import React, { useCallback, useMemo } from 'react'; import { translate } from 'react-polyglot'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import Menu from '../common/menu/Menu'; import MenuGroup from '../common/menu/MenuGroup'; import MenuItemButton from '../common/menu/MenuItemButton'; @@ -9,6 +10,19 @@ import MenuItemButton from '../common/menu/MenuItemButton'; import type { GroupMap, TranslatedProps, ViewGroup } from '@staticcms/core/interface'; import type { FC, MouseEvent } from 'react'; +import './GroupControl.css'; + +export const classes = generateClassNames('GroupControl', [ + 'root', + 'option', + 'list', + 'list-label', + 'list-option', + 'list-option-label', + 'list-option-checked-icon', + 'list-option-not-checked', +]); + export interface GroupControlProps { group: Record | undefined; viewGroups: ViewGroup[] | undefined; @@ -36,39 +50,21 @@ const GroupControl = ({ if (variant === 'list') { return ( -
-

- {t('collection.collectionTop.groupBy')} -

+
+

{t('collection.collectionTop.groupBy')}

{viewGroups.map(viewGroup => { const active = Boolean(viewGroup.id && group[viewGroup?.id]?.active) ?? false; return (
- + {active ? ( - + ) : ( -
+
)}
); @@ -81,7 +77,7 @@ const GroupControl = ({ {viewGroups.map(viewGroup => ( @@ -89,6 +85,7 @@ const GroupControl = ({ key={viewGroup.id} onClick={() => onGroupClick?.(viewGroup)} endIcon={viewGroup.id === activeGroup?.id ? CheckIcon : undefined} + className={classes.option} > {viewGroup.label} diff --git a/packages/core/src/components/collections/NestedCollection.css b/packages/core/src/components/collections/NestedCollection.css new file mode 100644 index 000000000..6ad6026f8 --- /dev/null +++ b/packages/core/src/components/collections/NestedCollection.css @@ -0,0 +1,62 @@ +.CMS_NestedCollection_root-node { + &.CMS_NestedCollection_active { + &.CMS_NestedCollection_expanded { + & > .CMS_NestedCollection_link { + & .CMS_NestedCollection_node-children-icon { + @apply rotate-90 + transform; + } + } + } + } +} + +.CMS_NestedCollection_active { +} + +.CMS_NestedCollection_expanded { +} + +.CMS_NestedCollection_root-node-icon { + @apply h-6 + w-6; +} + +.CMS_NestedCollection_node { + @apply ml-8; + + &.CMS_NestedCollection_expanded { + & > .CMS_NestedCollection_link { + & .CMS_NestedCollection_node-children-icon { + @apply rotate-90 + transform; + } + } + } +} + +.CMS_NestedCollection_node-icon { + @apply h-5 + w-5; +} + +.CMS_NestedCollection_node-content { + @apply flex + w-full + gap-2 + items-center + justify-between; +} + +.CMS_NestedCollection_node-children-icon { + @apply transition-transform + h-5 + w-5 + group-focus-within/active-list:text-blue-500 + group-hover/active-list:text-blue-500; +} + +.CMS_NestedCollection_node-children { + @apply mt-2 + space-y-1.5; +} diff --git a/packages/core/src/components/collections/NestedCollection.tsx b/packages/core/src/components/collections/NestedCollection.tsx index c2126f6dd..9eca254e1 100644 --- a/packages/core/src/components/collections/NestedCollection.tsx +++ b/packages/core/src/components/collections/NestedCollection.tsx @@ -1,18 +1,35 @@ import { Article as ArticleIcon } from '@styled-icons/material/Article'; import { ChevronRight as ChevronRightIcon } from '@styled-icons/material/ChevronRight'; import sortBy from 'lodash/sortBy'; -import React, { Fragment, useCallback, useEffect, useState } from 'react'; +import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import useEntries from '@staticcms/core/lib/hooks/useEntries'; import classNames from '@staticcms/core/lib/util/classNames.util'; import { getTreeData } from '@staticcms/core/lib/util/nested.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import NavLink from '../navbar/NavLink'; import type { Collection, Entry } from '@staticcms/core/interface'; import type { TreeNodeData } from '@staticcms/core/lib/util/nested.util'; import type { MouseEvent } from 'react'; +import './NestedCollection.css'; + +export const classes = generateClassNames('NestedCollection', [ + 'root', + 'active', + 'expanded', + 'root-node', + 'root-node-icon', + 'link', + 'node', + 'node-icon', + 'node-content', + 'node-children-icon', + 'node-children', +]); + function getNodeTitle(node: TreeNodeData) { const title = node.isRoot ? node.title @@ -23,28 +40,46 @@ function getNodeTitle(node: TreeNodeData) { interface TreeNodeProps { collection: Collection; treeData: TreeNodeData[]; + rootIsActive: boolean; + path: string; depth?: number; onToggle: ({ node, expanded }: { node: TreeNodeData; expanded: boolean }) => void; } -const TreeNode = ({ collection, treeData, depth = 0, onToggle }: TreeNodeProps) => { +const TreeNode = ({ + collection, + treeData, + rootIsActive, + path, + depth = 0, + onToggle, +}: TreeNodeProps) => { const collectionName = collection.name; const handleClick = useCallback( (event: MouseEvent | undefined, node: TreeNodeData, expanded: boolean) => { + if (!rootIsActive) { + return; + } + event?.stopPropagation(); event?.preventDefault(); if (event) { onToggle({ node, expanded }); } else { - onToggle({ node, expanded: true }); + onToggle({ node, expanded: path === node.path ? expanded : true }); } }, - [onToggle], + [onToggle, path, rootIsActive], ); const sortedData = sortBy(treeData, getNodeTitle); + + if (depth !== 0 && !rootIsActive) { + return null; + } + return ( <> {sortedData.map(node => { @@ -62,36 +97,42 @@ const TreeNode = ({ collection, treeData, depth = 0, onToggle }: TreeNodeProps) return ( -
+
handleClick(undefined, node, !node.expanded)} data-testid={node.path} - icon={} + className={classes.link} + icon={ + + } > -
+
{title}
{hasChildren && ( handleClick(event, node, !node.expanded)} - className={classNames( - node.expanded && 'rotate-90 transform', - ` - transition-transform - h-5 - w-5 - group-focus-within/active-list:text-blue-500 - group-hover/active-list:text-blue-500 - `, - )} + className={classes['node-children-icon']} /> )}
-
+
{node.expanded && ( const [treeData, setTreeData] = useState(getTreeData(collection, entries)); const [useFilter, setUseFilter] = useState(true); + const [prevRootIsActive, setPrevRootIsActive] = useState(false); const [prevCollection, setPrevCollection] = useState(null); const [prevEntries, setPrevEntries] = useState(null); - const [prevFilterTerm, setPrevFilterTerm] = useState(null); + const [prevPath, setPrevPath] = useState(null); const { pathname } = useLocation(); + const rootIsActive = useMemo( + () => pathname.startsWith(`/collections/${collection.name}`), + [collection.name, pathname], + ); + + const path = useMemo(() => `/${filterTerm}`, [filterTerm]); + useEffect(() => { - if (collection !== prevCollection || entries !== prevEntries || filterTerm !== prevFilterTerm) { + if ( + rootIsActive !== prevRootIsActive || + collection !== prevCollection || + entries !== prevEntries || + path !== prevPath + ) { const expanded: Record = {}; walk(treeData, node => { + if (!rootIsActive) { + expanded[node.path] = false; + return; + } + if (node.expanded) { expanded[node.path] = true; } }); const newTreeData = getTreeData(collection, entries); - const path = `/${filterTerm}`; walk(newTreeData, node => { - if ( - expanded[node.path] || - (useFilter && - path.startsWith(node.path) && - pathname.startsWith(`/collections/${collection.name}`)) - ) { + if (!rootIsActive) { + node.expanded = false; + return; + } + + if (node.isRoot) { + node.expanded = true; + return; + } + + if (expanded[node.path] || (useFilter && path.startsWith(node.path))) { node.expanded = true; } }); @@ -184,17 +247,21 @@ const NestedCollection = ({ collection, filterTerm }: NestedCollectionProps) => setTreeData(newTreeData); } + setPrevRootIsActive(rootIsActive); setPrevCollection(collection); setPrevEntries(entries); - setPrevFilterTerm(filterTerm); + setPrevPath(path); }, [ collection, entries, filterTerm, + path, pathname, prevCollection, prevEntries, - prevFilterTerm, + prevPath, + prevRootIsActive, + rootIsActive, treeData, useFilter, ]); @@ -212,7 +279,15 @@ const NestedCollection = ({ collection, filterTerm }: NestedCollectionProps) => [treeData], ); - return ; + return ( + + ); }; export default NestedCollection; diff --git a/packages/core/src/components/collections/SortControl.css b/packages/core/src/components/collections/SortControl.css new file mode 100644 index 000000000..fa42eeb48 --- /dev/null +++ b/packages/core/src/components/collections/SortControl.css @@ -0,0 +1,50 @@ +.CMS_SortControl_root { + @apply hidden + lg:block; +} + +.CMS_SortControl_option { +} + +.CMS_SortControl_list { + @apply flex + flex-col + gap-2; +} + +.CMS_SortControl_list-label { + @apply text-lg + font-bold + text-gray-800 + dark:text-white; +} + +.CMS_SortControl_list-option { + @apply ml-0.5 + font-medium + flex + items-center + text-gray-800 + dark:text-gray-300; +} + +.CMS_SortControl_list-option-label { + @apply ml-2 + text-base + font-medium + text-gray-800 + dark:text-gray-300; +} + +.CMS_SortControl_list-option-sorted-icon { + @apply ml-2 + w-6 + h-6 + text-blue-500; +} + +.CMS_SortControl_list-option-not-sorted { + @apply ml-2 + w-6 + h-6; +} diff --git a/packages/core/src/components/collections/SortControl.tsx b/packages/core/src/components/collections/SortControl.tsx index 7bd69d72e..b8f4c1674 100644 --- a/packages/core/src/components/collections/SortControl.tsx +++ b/packages/core/src/components/collections/SortControl.tsx @@ -8,6 +8,7 @@ import { SORT_DIRECTION_DESCENDING, SORT_DIRECTION_NONE, } from '@staticcms/core/constants'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import Menu from '../common/menu/Menu'; import MenuGroup from '../common/menu/MenuGroup'; import MenuItemButton from '../common/menu/MenuItemButton'; @@ -20,6 +21,19 @@ import type { } from '@staticcms/core/interface'; import type { FC, MouseEvent } from 'react'; +import './SortControl.css'; + +export const classes = generateClassNames('SortControl', [ + 'root', + 'option', + 'list', + 'list-label', + 'list-option', + 'list-option-label', + 'list-option-sorted-icon', + 'list-option-not-sorted', +]); + function nextSortDirection(direction: SortDirection) { switch (direction) { case SORT_DIRECTION_ASCENDING: @@ -69,44 +83,32 @@ const SortControl = ({ if (variant === 'list') { return ( -
-

- {t('collection.collectionTop.sortBy')} -

+
+

{t('collection.collectionTop.sortBy')}

{fields.map(field => { const sortDir = sort?.[field.name]?.direction ?? SORT_DIRECTION_NONE; const nextSortDir = nextSortDirection(sortDir); return (
- + {field.name === selectedSort.key ? ( selectedSort.direction === SORT_DIRECTION_ASCENDING ? ( - + ) : ( - + ) ) : ( -
+
)}
); @@ -119,7 +121,7 @@ const SortControl = ({ {fields.map(field => { @@ -137,6 +139,7 @@ const SortControl = ({ : KeyboardArrowDownIcon : undefined } + className={classes.option} > {field.label ?? field.name} diff --git a/packages/core/src/components/collections/entries/Entries.classes.ts b/packages/core/src/components/collections/entries/Entries.classes.ts new file mode 100644 index 000000000..7533c834f --- /dev/null +++ b/packages/core/src/components/collections/entries/Entries.classes.ts @@ -0,0 +1,22 @@ +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + +const entriesClasses = generateClassNames('Entries', [ + 'root', + 'group', + 'group-content-wrapper', + 'group-content', + 'group-button', + 'entry-listing', + 'entry-listing-loading', + 'entry-listing-grid', + 'entry-listing-grid-container', + 'entry-listing-cards', + 'entry-listing-cards-grid-wrapper', + 'entry-listing-cards-grid', + 'entry-listing-table', + 'entry-listing-table-content', + 'entry-listing-table-row', + 'entry-listing-local-backup', +]); + +export default entriesClasses; diff --git a/packages/core/src/components/collections/entries/Entries.css b/packages/core/src/components/collections/entries/Entries.css new file mode 100644 index 000000000..7f7050800 --- /dev/null +++ b/packages/core/src/components/collections/entries/Entries.css @@ -0,0 +1,104 @@ +.CMS_Entries_root { + @apply py-2 + px-3 + rounded-md + bg-yellow-300/75 + dark:bg-yellow-800/75 + text-sm; +} + +.CMS_Entries_group { + @apply pb-3; +} + +.CMS_Entries_group-content-wrapper { + @apply -m-1; +} + +.CMS_Entries_group-content { + @apply flex + gap-2 + p-1 + overflow-x-auto; +} + +.CMS_Entries_group-button { + @apply whitespace-nowrap; +} + +.CMS_Entries_entry-listing { + @apply pb-3 + overflow-hidden; +} + +.CMS_Entries_entry-listing-loading { + @apply absolute + inset-0 + flex + items-center + justify-center + bg-slate-50/50 + dark:bg-slate-900/50; +} + +.CMS_Entries_entry-listing-grid { + @apply relative + h-full + flex-grow; +} + +.CMS_Entries_entry-listing-grid-container { + @apply relative + h-full; +} + +.CMS_Entries_entry-listing-cards { + @apply relative + w-card-grid + h-full + overflow-hidden + -ml-1; +} + +.CMS_Entries_entry-listing-cards-grid-wrapper { + @apply overflow-hidden; +} + +.CMS_Entries_entry-listing-cards-grid { + @apply !overflow-x-hidden + overflow-y-auto; +} + +.CMS_Entries_entry-listing-table { + @apply relative + max-h-full + h-full + overflow-hidden + p-1.5 + bg-white + shadow-sm + border + border-gray-100 + dark:bg-slate-800 + dark:border-gray-700/40 + dark:shadow-md + rounded-xl; +} + +.CMS_Entries_entry-listing-table-content { + @apply relative + h-full + overflow-auto; +} + +.CMS_Entries_entry-listing-table-row { + @apply hover:bg-gray-200 + dark:hover:bg-slate-700/70; +} + +.CMS_Entries_entry-listing-local-backup { + @apply w-5 + h-5 + text-blue-600 + dark:text-blue-300; +} diff --git a/packages/core/src/components/collections/entries/Entries.tsx b/packages/core/src/components/collections/entries/Entries.tsx index 8c2112435..8454b3296 100644 --- a/packages/core/src/components/collections/entries/Entries.tsx +++ b/packages/core/src/components/collections/entries/Entries.tsx @@ -2,12 +2,15 @@ import React, { useMemo } from 'react'; import { translate } from 'react-polyglot'; import Loader from '@staticcms/core/components/common/progress/Loader'; +import entriesClasses from './Entries.classes'; import EntryListing from './EntryListing'; import type { ViewStyle } from '@staticcms/core/constants/views'; import type { Collection, Collections, Entry, TranslatedProps } from '@staticcms/core/interface'; import type Cursor from '@staticcms/core/lib/util/Cursor'; +import './Entries.css'; + export interface BaseEntriesProps { entries: Entry[]; page?: number; @@ -81,20 +84,7 @@ const Entries = ({ ); } - return ( -
- {t('collection.entries.noEntries')} -
- ); + return
{t('collection.entries.noEntries')}
; }; export default translate()(Entries); diff --git a/packages/core/src/components/collections/entries/EntriesCollection.tsx b/packages/core/src/components/collections/entries/EntriesCollection.tsx index d7664615d..8599d9842 100644 --- a/packages/core/src/components/collections/entries/EntriesCollection.tsx +++ b/packages/core/src/components/collections/entries/EntriesCollection.tsx @@ -6,11 +6,13 @@ import { loadEntries, traverseCollectionCursor } from '@staticcms/core/actions/e import useEntries from '@staticcms/core/lib/hooks/useEntries'; import useGroups from '@staticcms/core/lib/hooks/useGroups'; import { Cursor } from '@staticcms/core/lib/util'; +import classNames from '@staticcms/core/lib/util/classNames.util'; import { selectCollectionEntriesCursor } from '@staticcms/core/reducers/selectors/cursors'; import { selectEntriesLoaded, selectIsFetching } from '@staticcms/core/reducers/selectors/entries'; import { useAppDispatch } from '@staticcms/core/store/hooks'; import Button from '../../common/button/Button'; import Entries from './Entries'; +import entriesClasses from './Entries.classes'; import type { ViewStyle } from '@staticcms/core/constants/views'; import type { Collection, Entry, GroupOfEntries, TranslatedProps } from '@staticcms/core/interface'; @@ -115,25 +117,9 @@ const EntriesCollection = ({ if (groups && groups.length > 0) { return ( <> -
-
-
+
+
+
{groups.map((group, index) => { const title = getGroupTitle(group, t); return ( @@ -141,7 +127,7 @@ const EntriesCollection = ({ key={index} variant={index === selectedGroup ? 'contained' : 'text'} onClick={handleGroupClick(index)} - className="whitespace-nowrap" + className={entriesClasses['group-button']} > {title} diff --git a/packages/core/src/components/collections/entries/EntryCard.css b/packages/core/src/components/collections/entries/EntryCard.css new file mode 100644 index 000000000..1c6cc7722 --- /dev/null +++ b/packages/core/src/components/collections/entries/EntryCard.css @@ -0,0 +1,40 @@ +.CMS_EntryCard_root { + @apply h-full + w-full + relative + overflow-visible; +} + +.CMS_EntryCard_content-wrapper { + @apply absolute + -inset-1 + pr-2; +} + +.CMS_EntryCard_content { + @apply p-1 + h-full + w-full; +} + +.CMS_EntryCard_card { + @apply h-full; +} + +.CMS_EntryCard_card-content { + @apply flex + w-full + items-center + justify-between; +} + +.CMS_EntryCard_card-summary { + @apply truncate; +} + +.CMS_EntryCard_local-backup-icon { + @apply w-5 + h-5 + text-blue-600 + dark:text-blue-300; +} diff --git a/packages/core/src/components/collections/entries/EntryCard.tsx b/packages/core/src/components/collections/entries/EntryCard.tsx index 628b39e29..1fdce8159 100644 --- a/packages/core/src/components/collections/entries/EntryCard.tsx +++ b/packages/core/src/components/collections/entries/EntryCard.tsx @@ -9,6 +9,7 @@ import { selectTemplateName, } from '@staticcms/core/lib/util/collection.util'; import localForage from '@staticcms/core/lib/util/localForage'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import { selectConfig } from '@staticcms/core/reducers/selectors/config'; import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI'; import { useAppSelector } from '@staticcms/core/store/hooks'; @@ -27,6 +28,18 @@ import type { } from '@staticcms/core/interface'; import type { FC } from 'react'; +import './EntryCard.css'; + +export const classes = generateClassNames('EntryCard', [ + 'root', + 'content-wrapper', + 'content', + 'card', + 'card-content', + 'card-summary', + 'local-backup-icon', +]); + export interface EntryCardProps { entry: Entry; imageFieldName?: string | null | undefined; @@ -113,9 +126,9 @@ const EntryCard: FC> = ({ if (PreviewCardComponent) { return ( -
-
-
+
+
+
> = ({ } return ( -
-
-
- +
+
+
+ {image && imageField ? ( > = ({ /> ) : null} -
-
{summary}
+
+
{summary}
{hasLocalBackup ? ( ) : null} diff --git a/packages/core/src/components/collections/entries/EntryListing.tsx b/packages/core/src/components/collections/entries/EntryListing.tsx index 692f81bfd..2b4802d4e 100644 --- a/packages/core/src/components/collections/entries/EntryListing.tsx +++ b/packages/core/src/components/collections/entries/EntryListing.tsx @@ -4,6 +4,7 @@ import { translate } from 'react-polyglot'; import { VIEW_STYLE_TABLE } from '@staticcms/core/constants/views'; import { selectFields, selectInferredField } from '@staticcms/core/lib/util/collection.util'; import { toTitleCaseFromKey } from '@staticcms/core/lib/util/string.util'; +import entriesClasses from './Entries.classes'; import EntryListingGrid from './EntryListingGrid'; import EntryListingTable from './EntryListingTable'; @@ -154,7 +155,7 @@ const EntryListing: FC> = ({ if (viewStyle === VIEW_STYLE_TABLE) { return ( -
+
= ({ }, [cardHeights, prevCardHeights.length]); return ( -
+
{({ height = 0, width = 0 }) => { const calculatedWidth = width - 4; @@ -161,11 +154,7 @@ const EntryListingCardGrid: FC = ({ return (
= ({ outerRef={scrollContainerRef} onScroll={onScroll} className={classNames( - ` - !overflow-x-hidden - overflow-y-auto - styled-scrollbars - `, + entriesClasses['entry-listing-cards-grid'], + 'CMS_Scrollbar_root', )} style={{ position: 'unset' }} overscanRowCount={5} diff --git a/packages/core/src/components/collections/entries/EntryListingGrid.tsx b/packages/core/src/components/collections/entries/EntryListingGrid.tsx index 3cab79eae..12e13a2eb 100644 --- a/packages/core/src/components/collections/entries/EntryListingGrid.tsx +++ b/packages/core/src/components/collections/entries/EntryListingGrid.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import { isNotNullish } from '@staticcms/core/lib/util/null.util'; import { selectIsFetching } from '@staticcms/core/reducers/selectors/globalUI'; import { useAppSelector } from '@staticcms/core/store/hooks'; +import entriesClasses from './Entries.classes'; import EntryListingCardGrid from './EntryListingCardGrid'; import type { CollectionEntryData } from '@staticcms/core/interface'; @@ -57,8 +58,8 @@ const EntryListingGrid: FC = ({ }, [handleScroll]); return ( -
-
+
+
= ({ />
{isLoadingEntries ? ( -
+
{t('collection.entries.loadingEntries')}
) : null} diff --git a/packages/core/src/components/collections/entries/EntryListingTable.tsx b/packages/core/src/components/collections/entries/EntryListingTable.tsx index b9ca0969d..b2abd5469 100644 --- a/packages/core/src/components/collections/entries/EntryListingTable.tsx +++ b/packages/core/src/components/collections/entries/EntryListingTable.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { useVirtual } from 'react-virtual'; +import classNames from '@staticcms/core/lib/util/classNames.util'; import { isNotNullish } from '@staticcms/core/lib/util/null.util'; import { selectIsFetching } from '@staticcms/core/reducers/selectors/globalUI'; import { useAppSelector } from '@staticcms/core/store/hooks'; import Table from '../../common/table/Table'; +import entriesClasses from './Entries.classes'; import EntryRow from './EntryRow'; import type { CollectionEntryData } from '@staticcms/core/interface'; @@ -69,32 +71,14 @@ const EntryListingTable: FC = ({ }, [clientHeight, fetchMoreOnBottomReached, scrollHeight, scrollTop]); return ( -
+
= ({
{isLoadingEntries ? ( -
+
{t('collection.entries.loadingEntries')}
) : null} diff --git a/packages/core/src/components/collections/entries/EntryRow.tsx b/packages/core/src/components/collections/entries/EntryRow.tsx index d37f1fb38..b013cedd5 100644 --- a/packages/core/src/components/collections/entries/EntryRow.tsx +++ b/packages/core/src/components/collections/entries/EntryRow.tsx @@ -15,6 +15,7 @@ import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI'; import { useAppSelector } from '@staticcms/core/store/hooks'; import TableCell from '../../common/table/TableCell'; import TableRow from '../../common/table/TableRow'; +import entriesClasses from './Entries.classes'; import type { BackupEntry, Collection, Entry, TranslatedProps } from '@staticcms/core/interface'; import type { FC } from 'react'; @@ -75,13 +76,7 @@ const EntryRow: FC> = ({ }, [collection.name, entry.slug]); return ( - + {collectionLabel ? ( {collectionLabel} @@ -120,12 +115,7 @@ const EntryRow: FC> = ({ {hasLocalBackup ? ( ) : null} diff --git a/packages/core/src/components/collections/mobile/MobileCollectionControls.classes.ts b/packages/core/src/components/collections/mobile/MobileCollectionControls.classes.ts new file mode 100644 index 000000000..f865be382 --- /dev/null +++ b/packages/core/src/components/collections/mobile/MobileCollectionControls.classes.ts @@ -0,0 +1,10 @@ +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + +const mobileCollectionControlsClasses = generateClassNames('MobileCollectionControls', [ + 'root', + 'content', + 'toggle', + 'toggle-icon', +]); + +export default mobileCollectionControlsClasses; diff --git a/packages/core/src/components/collections/mobile/MobileCollectionControls.css b/packages/core/src/components/collections/mobile/MobileCollectionControls.css new file mode 100644 index 000000000..13f067e8e --- /dev/null +++ b/packages/core/src/components/collections/mobile/MobileCollectionControls.css @@ -0,0 +1,37 @@ +.CMS_MobileCollectionControls_root { + @apply w-[80%] + max-w-[240px]; + + & .MuiBackdrop-root { + @apply w-full; + } + + & .MuiDrawer-paper { + @apply box-border + w-[80%] + max-w-[240px]; + } +} + +.CMS_MobileCollectionControls_content { + @apply px-5 + py-4 + flex + flex-col + gap-6 + h-full + w-full + overflow-y-auto + bg-white + dark:bg-slate-800; +} + +.CMS_MobileCollectionControls_toggle { + @apply flex + lg:!hidden; +} + +.CMS_MobileCollectionControls_toggle-icon { + @apply w-5 + h-5; +} diff --git a/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx b/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx index 32127532f..848e5f1a6 100644 --- a/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx +++ b/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx @@ -2,6 +2,7 @@ import { FilterList as FilterListIcon } from '@styled-icons/material/FilterList' import React, { useCallback, useState } from 'react'; import IconButton from '../../common/button/IconButton'; +import mobileCollectionControlsClasses from './MobileCollectionControls.classes'; import MobileCollectionControlsDrawer from './MobileCollectionControlsDrawer'; import type { FC } from 'react'; @@ -9,6 +10,8 @@ import type { FilterControlProps } from '../FilterControl'; import type { GroupControlProps } from '../GroupControl'; import type { SortControlProps } from '../SortControl'; +import './MobileCollectionControls.css'; + export type MobileCollectionControlsProps = Omit & Omit & Omit & { @@ -25,8 +28,12 @@ const MobileCollectionControls: FC = props => { return ( <> - - + + & Omit & Omit & { @@ -53,34 +53,11 @@ const MobileCollectionControlsDrawer = ({ ModalProps={{ keepMounted: true, // Better open performance on mobile. }} - sx={{ - width: '80%', - maxWidth: DRAWER_WIDTH, - '& .MuiBackdrop-root': { - width: '100%', - }, - '& .MuiDrawer-paper': { - boxSizing: 'border-box', - width: '80%', - maxWidth: DRAWER_WIDTH, - }, - }} + slotProps={{ root: { className: mobileCollectionControlsClasses.root } }} >
{showSortControl ? ( diff --git a/packages/core/src/components/common/alert/Alert.css b/packages/core/src/components/common/alert/Alert.css new file mode 100644 index 000000000..5ea9a225b --- /dev/null +++ b/packages/core/src/components/common/alert/Alert.css @@ -0,0 +1,26 @@ +.CMS_Alert_root { + @apply w-[50%] + min-w-[300px] + max-w-[600px]; +} + +.CMS_Alert_title { + @apply px-6 + py-4 + text-xl; +} + +.CMS_Alert_content { + @apply px-6 + pb-4 + text-sm + text-slate-500 + dark:text-slate-400; +} + +.CMS_Alert_actions { + @apply p-2 + flex + justify-end + gap-2; +} diff --git a/packages/core/src/components/common/alert/Alert.tsx b/packages/core/src/components/common/alert/Alert.tsx index 22d561409..fd7e739ac 100644 --- a/packages/core/src/components/common/alert/Alert.tsx +++ b/packages/core/src/components/common/alert/Alert.tsx @@ -2,12 +2,23 @@ import React, { useCallback, useMemo, useState } from 'react'; import { translate } from 'react-polyglot'; import AlertEvent from '@staticcms/core/lib/util/events/AlertEvent'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import { useWindowEvent } from '@staticcms/core/lib/util/window.util'; import Button from '../button/Button'; import Modal from '../modal/Modal'; import type { TranslateProps } from 'react-polyglot'; +import './Alert.css'; + +export const classes = generateClassNames('Alert', [ + 'root', + 'title', + 'content', + 'actions', + 'confirm-button', +]); + interface AlertProps { title: string | { key: string; options?: Record }; body: string | { key: string; options?: Record }; @@ -67,44 +78,19 @@ const AlertDialog = ({ t }: TranslateProps) => { -
- {title} -
-
- {body} -
-
-
diff --git a/packages/core/src/components/common/autocomplete/Autocomplete.css b/packages/core/src/components/common/autocomplete/Autocomplete.css new file mode 100644 index 000000000..6e549e51f --- /dev/null +++ b/packages/core/src/components/common/autocomplete/Autocomplete.css @@ -0,0 +1,123 @@ +.CMS_Autocomplete_root { + @apply relative + w-full; + + &.CMS_Autocomplete_disabled { + & .CMS_Autocomplete_input { + @apply text-gray-300 + dark:text-gray-500; + } + + & .CMS_Autocomplete_button-icon { + @apply text-gray-300/75 + dark:text-gray-600/75; + } + } +} + +.CMS_Autocomplete_input { + @apply w-full + bg-transparent + border-none + py-2 + pl-3 + pr-10 + text-sm + leading-5 + focus:ring-0 + outline-none + flex-grow + truncate + text-gray-800 + dark:text-gray-100; +} + +.CMS_Autocomplete_button-wrapper { + @apply absolute + inset-y-0 + right-0 + flex + items-center + pr-2 + gap-1; +} + +.CMS_Autocomplete_button { +} + +.CMS_Autocomplete_button-icon { + @apply h-5 + w-5 + text-gray-400; +} + +.CMS_Autocomplete_options { + @apply max-h-60 + w-full + overflow-auto + rounded-md + bg-white + py-1 + text-base + shadow-md + ring-1 + ring-black + ring-opacity-5 + focus:outline-none + sm:text-sm + z-30 + dark:bg-slate-700 + dark:shadow-lg; +} + +.CMS_Autocomplete_nothing { + @apply relative + cursor-default + select-none + py-2 + px-4 + text-gray-800 + dark:text-gray-300; +} + +.CMS_Autocomplete_option { + @apply relative + select-none + py-2 + pl-10 + pr-4 + cursor-pointer + text-gray-800 + dark:text-gray-100; +} + +.CMS_Autocomplete_option-selected { + @apply bg-gray-100 + dark:bg-slate-600; +} + +.CMS_Autocomplete_option-selected { + & .CMS_Autocomplete_option-label { + @apply font-medium; + } +} + +.CMS_Autocomplete_option-label { + @apply block + font-normal; +} + +.CMS_Autocomplete_checkmark { + @apply absolute + inset-y-0 + left-0 + flex + items-center + pl-3 + text-blue-500; +} + +.CMS_Autocomplete_checkmark-icon { + @apply h-5 + w-5; +} diff --git a/packages/core/src/components/common/autocomplete/Autocomplete.tsx b/packages/core/src/components/common/autocomplete/Autocomplete.tsx index a61c4377f..7f25fa6c5 100644 --- a/packages/core/src/components/common/autocomplete/Autocomplete.tsx +++ b/packages/core/src/components/common/autocomplete/Autocomplete.tsx @@ -1,262 +1,262 @@ -import { Combobox, Transition } from '@headlessui/react'; +import { Popper } from '@mui/base/Popper'; +import { useAutocomplete } from '@mui/base/useAutocomplete'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { Check as CheckIcon } from '@styled-icons/material/Check'; import { Close as CloseIcon } from '@styled-icons/material/Close'; import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@styled-icons/material/KeyboardArrowDown'; -import React, { Fragment, forwardRef, useCallback } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import useDebouncedCallback from '@staticcms/core/lib/hooks/useDebouncedCallback'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { isNullish } from '@staticcms/core/lib/util/null.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import IconButton from '../button/IconButton'; -import type { ReactNode, Ref } from 'react'; +import type { MouseEvent, ReactNode, Ref } from 'react'; -export interface Option { +import './Autocomplete.css'; + +export const classes = generateClassNames('Autocomplete', [ + 'root', + 'focused', + 'disabled', + 'input', + 'button-wrapper', + 'button', + 'button-icon', + 'options', + 'nothing', + 'option', + 'option-selected', + 'option-label', + 'checkmark', + 'checkmark-icon', +]); + +export interface Option { label: string; - value: T; + value: string; } -function getOptionLabelAndValue(option: T | Option): Option { +function getOptionLabelAndValue(option: string | Option): Option { if (option && typeof option === 'object' && 'label' in option && 'value' in option) { return option; } - return { label: String(option), value: option }; + return { label: option, value: option }; } -export type AutocompleteChangeEventHandler = (value: T | T[]) => void; +export type AutocompleteChangeEventHandler = (value: string | string[]) => void; -export interface AutocompleteProps { +export interface AutocompleteProps { label: ReactNode | ReactNode[]; - value: T | T[] | null; - options: T[] | Option[]; + value: string | string[] | null; + options: string[] | Option[]; disabled?: boolean; required?: boolean; - displayValue: (item: T | T[] | null) => string; + inputRef?: Ref; + displayValue: (item: string | string[] | null) => string; onQuery: (query: string) => void; - onChange: (value: T | T[] | undefined) => void; + onChange: (value: string | string[] | undefined) => void; } -const Autocomplete = function ( - { - label, - value, - options, - disabled, - required, - displayValue, - onQuery, - onChange, - }: AutocompleteProps, - ref: Ref, -) { +const Autocomplete = ({ + label, + value, + options, + inputRef, + disabled, + required, + onChange, + onQuery, +}: AutocompleteProps) => { + const [inputValue, setInputValue] = useState(''); + + const debouncedOnQuery = useDebouncedCallback(onQuery, 200); + + const handleInputChange = useCallback( + (newInputValue: string) => { + setInputValue(newInputValue); + debouncedOnQuery(newInputValue); + }, + [debouncedOnQuery], + ); + const handleChange = useCallback( - (selectedValue: T) => { - if (Array.isArray(value)) { - const newValue = [...value]; - const index = newValue.indexOf(selectedValue); - if (index > -1) { - newValue.splice(index, 1); - } else { - newValue.push(selectedValue); + (selectedValue: Option | readonly Option[] | null) => { + if (selectedValue === null) { + if (Array.isArray(value)) { + onChange([]); + return; } - onChange(newValue); + onChange(undefined); + return; + } + + if ('value' in selectedValue) { + onChange(selectedValue.value); return; } - onChange(selectedValue); + onChange(selectedValue.map(option => option.value)); }, [onChange, value], ); - const clear = useCallback(() => { - onChange(Array.isArray(value) ? [] : undefined); - }, [onChange, value]); + const clear = useCallback( + (event: MouseEvent) => { + event.stopPropagation(); + onChange(Array.isArray(value) ? [] : undefined); + setInputValue(''); + debouncedOnQuery(''); + }, + [debouncedOnQuery, onChange, value], + ); + + const finalOptions = useMemo(() => options.map(getOptionLabelAndValue), [options]); + const optionsByValue = useMemo( + () => + finalOptions.reduce((acc, option) => { + acc[option.value] = option; + + return acc; + }, {} as Record), + [finalOptions], + ); + + const finalValue = useMemo(() => { + if (isNullish(value)) { + return value; + } + + if (typeof value === 'string') { + return optionsByValue[value]; + } + + return value.map(v => optionsByValue[v]).filter(v => Boolean(v)); + }, [optionsByValue, value]); + + const { + getRootProps, + getInputProps, + getListboxProps, + getOptionProps, + groupedOptions, + focused, + popupOpen, + anchorEl, + setAnchorEl, + } = useAutocomplete({ + options: finalOptions, + value: finalValue, + inputValue, + multiple: Array.isArray(value), + disabled, + openOnFocus: true, + onChange: (_event, selectedValue) => handleChange(selectedValue), + onInputChange: (_event, newQueryInput) => handleInputChange(newQueryInput), + filterOptions: options => options, + clearOnBlur: false, + clearOnEscape: false, + }); + + const ref = useRef(); + const rootRef = useForkRef(ref, setAnchorEl); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const localInputRef = (getInputProps() as any) + .ref as React.MutableRefObject; + const finalInputRef = useForkRef(localInputRef, inputRef); + const handleDropdownButtonClick = useCallback(() => { + localInputRef.current?.blur(); + localInputRef.current?.click(); + }, [localInputRef]); + + const width = anchorEl?.clientWidth; return ( -
- -
-
- {label} - - ref={ref} - className={classNames( - ` - w-full - bg-transparent - border-none - py-2 - pl-3 - pr-10 - text-sm - leading-5 - focus:ring-0 - outline-none - flex-grow - truncate - `, - disabled - ? ` - text-gray-300 - dark:text-gray-500 - ` - : ` - text-gray-800 - dark:text-gray-100 - `, - )} - data-testid="autocomplete-input" - displayValue={displayValue} - onChange={event => onQuery(event.target.value)} - /> -
- - - {!required ? ( - - - ) : null} -
-
- onQuery('')} + +
+ {label} + +
+ - + {!required ? ( + - {options.length === 0 ? ( -
- Nothing found. -
- ) : ( - options.map((option, index) => { - const { label: optionLabel, value: optionValue } = getOptionLabelAndValue(option); +
+ ) : null} +
+
+ {anchorEl && ( + +
    + {groupedOptions.length > 0 ? ( + groupedOptions.map((option, index) => { + const { label: optionLabel, value: optionValue } = getOptionLabelAndValue( + option as Option, + ); - const selected = Array.isArray(value) - ? value.includes(optionValue) - : value === optionValue; + const selected = Array.isArray(value) + ? value.includes(optionValue) + : value === optionValue; - return ( - - classNames( - ` - relative - select-none - py-2 - pl-10 - pr-4 - cursor-pointer - text-gray-800 - dark:text-gray-100 - `, - (selected || active) && - ` - bg-gray-100 - dark:bg-slate-600 - `, - ) - } - value={optionValue} - > - - {optionLabel} + return ( +
  • + {optionLabel} + {selected ? ( + + - {selected ? ( - - - ) : null} - - ); - }) - )} - - -
-
-
+ ) : null} + + ); + }) + ) : ( +
Nothing found.
+ )} + + + )} + ); }; -export default forwardRef(Autocomplete) as ( - props: AutocompleteProps & { ref: Ref }, -) => JSX.Element; +export default Autocomplete; diff --git a/packages/core/src/components/common/button/Button.css b/packages/core/src/components/common/button/Button.css new file mode 100644 index 000000000..da60257c9 --- /dev/null +++ b/packages/core/src/components/common/button/Button.css @@ -0,0 +1,11 @@ +.CMS_Button_start-icon { + @apply w-5 + h-5 + mr-2; +} + +.CMS_Button_end-icon { + @apply w-5 + h-5 + ml-2; +} diff --git a/packages/core/src/components/common/button/Button.tsx b/packages/core/src/components/common/button/Button.tsx index 096d4b98a..e61a74c90 100644 --- a/packages/core/src/components/common/button/Button.tsx +++ b/packages/core/src/components/common/button/Button.tsx @@ -2,10 +2,12 @@ import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; import classNames from '@staticcms/core/lib/util/classNames.util'; -import useButtonClassNames from './useButtonClassNames'; +import useButtonClassNames, { buttonClasses } from './useButtonClassNames'; import type { CSSProperties, FC, MouseEventHandler, ReactNode, Ref } from 'react'; +import './Button.css'; + export interface BaseBaseProps { variant?: 'contained' | 'outlined' | 'text'; color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning'; @@ -58,16 +60,16 @@ const Button: FC = ({ const buttonClassName = useButtonClassNames(variant, color, size, rounded); const buttonClassNames = useMemo( - () => classNames(buttonClassName, className), + () => classNames(className, buttonClassName), [buttonClassName, className], ); const content = useMemo( () => ( <> - {StartIcon ? : null} + {StartIcon ? : null} {children} - {EndIcon ? : null} + {EndIcon ? : null} ), [EndIcon, StartIcon, children], diff --git a/packages/core/src/components/common/button/IconButton.css b/packages/core/src/components/common/button/IconButton.css new file mode 100644 index 000000000..3108444eb --- /dev/null +++ b/packages/core/src/components/common/button/IconButton.css @@ -0,0 +1,9 @@ +.CMS_IconButton_root { + &.CMS_IconButton_sm { + @apply px-0.5; + } + + &.CMS_IconButton_md { + @apply px-1.5; + } +} diff --git a/packages/core/src/components/common/button/IconButton.tsx b/packages/core/src/components/common/button/IconButton.tsx index f9344f77d..f7ead57ab 100644 --- a/packages/core/src/components/common/button/IconButton.tsx +++ b/packages/core/src/components/common/button/IconButton.tsx @@ -1,11 +1,16 @@ import React from 'react'; -import Button from './Button'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; +import Button from './Button'; import type { FC } from 'react'; import type { ButtonLinkProps } from './Button'; +import './IconButton.css'; + +export const classes = generateClassNames('IconButton', ['root', 'sm', 'md']); + export type IconButtonProps = Omit & { children: FC<{ className?: string }>; }; @@ -13,7 +18,12 @@ export type IconButtonProps = Omit & { const IconButton = ({ children, size = 'medium', className, ...otherProps }: ButtonLinkProps) => { return ( -
diff --git a/packages/core/src/components/common/field/ErrorMessage.css b/packages/core/src/components/common/field/ErrorMessage.css new file mode 100644 index 000000000..2466cebcf --- /dev/null +++ b/packages/core/src/components/common/field/ErrorMessage.css @@ -0,0 +1,8 @@ +.CMS_ErrorMessage_root { + @apply flex + w-full + text-xs + text-red-500 + px-3 + pt-2; +} diff --git a/packages/core/src/components/common/field/ErrorMessage.tsx b/packages/core/src/components/common/field/ErrorMessage.tsx index 80165d89c..a3ce7f9cc 100644 --- a/packages/core/src/components/common/field/ErrorMessage.tsx +++ b/packages/core/src/components/common/field/ErrorMessage.tsx @@ -1,10 +1,15 @@ import React from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FieldError } from '@staticcms/core/interface'; import type { FC } from 'react'; +import './ErrorMessage.css'; + +export const classes = generateClassNames('ErrorMessage', ['root']); + export interface ErrorMessageProps { errors: FieldError[]; className?: string; @@ -12,21 +17,7 @@ export interface ErrorMessageProps { const ErrorMessage: FC = ({ errors, className }) => { return errors.length ? ( -
+
{errors[0].message}
) : null; diff --git a/packages/core/src/components/common/field/Field.css b/packages/core/src/components/common/field/Field.css new file mode 100644 index 000000000..981059998 --- /dev/null +++ b/packages/core/src/components/common/field/Field.css @@ -0,0 +1,59 @@ +.CMS_Field_root { + @apply relative + flex + items-center + gap-2 + border-b + border-slate-400 + focus-within:border-blue-800 + dark:focus-within:border-blue-100; + + &:not(.CMS_Field_disabled):not(.CMS_Field_no-highlight) { + @apply focus-within:bg-slate-100 + dark:focus-within:bg-slate-800 + hover:bg-slate-100 + dark:hover:bg-slate-800; + } + + &:not(.CMS_Field_no-padding) { + @apply pb-3; + + & > .CMS_Field_end-adornment { + @apply -mb-3; + } + } +} + +.CMS_Field_cursor-pointer { + @apply cursor-pointer; +} + +.CMS_Field_cursor-text { + @apply cursor-text; +} + +.CMS_Field_cursor-default { + @apply cursor-default; +} + +.CMS_Field_wrapper { + @apply flex + flex-col + w-full; + + &.CMS_Field_for-single-list { + @apply mr-14; + } +} + +.CMS_Field_inline-wrapper { + @apply flex + items-center + justify-center + p-3 + pb-0; +} + +.CMS_Field_end-adornment { + @apply pr-2; +} diff --git a/packages/core/src/components/common/field/Field.tsx b/packages/core/src/components/common/field/Field.tsx index 149c8de62..6aa01ee54 100644 --- a/packages/core/src/components/common/field/Field.tsx +++ b/packages/core/src/components/common/field/Field.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import useCursor from '@staticcms/core/lib/hooks/useCursor'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import ErrorMessage from './ErrorMessage'; import Hint from './Hint'; import Label from './Label'; @@ -9,6 +10,25 @@ import Label from './Label'; import type { FieldError } from '@staticcms/core/interface'; import type { FC, MouseEvent, ReactNode } from 'react'; +import './Field.css'; + +export const classes = generateClassNames('Field', [ + 'root', + 'inline', + 'wrapper', + 'inline-wrapper', + 'disabled', + 'no-highlight', + 'no-padding', + 'cursor-pointer', + 'cursor-text', + 'cursor-default', + 'error', + 'valid', + 'for-single-list', + 'end-adornment', +]); + export interface FieldProps { label?: string; inputRef?: React.MutableRefObject; @@ -98,53 +118,34 @@ const Field: FC = ({ const rootClassNames = useMemo( () => classNames( - ` - relative - flex - items-center - gap-2 - border-b - border-slate-400 - focus-within:border-blue-800 - dark:focus-within:border-blue-100 - `, + classes.root, rootClassName, - !noHightlight && - !disabled && - ` - focus-within:bg-slate-100 - dark:focus-within:bg-slate-800 - hover:bg-slate-100 - dark:hover:bg-slate-800 - `, - !noPadding && 'pb-3', - finalCursor === 'pointer' && 'cursor-pointer', - finalCursor === 'text' && 'cursor-text', - finalCursor === 'default' && 'cursor-default', - !hasErrors && 'group/active', + disabled && classes.disabled, + noHightlight && classes['no-highlight'], + noPadding && classes['no-padding'], + finalCursor === 'pointer' && classes['cursor-pointer'], + finalCursor === 'text' && classes['cursor-text'], + finalCursor === 'default' && classes['cursor-default'], + hasErrors ? classes.error : `group/active`, ), [rootClassName, noHightlight, disabled, noPadding, finalCursor, hasErrors], ); const wrapperClassNames = useMemo( () => - classNames( - ` - flex - flex-col - w-full - `, - wrapperClassName, - forSingleList && 'mr-14', - ), + classNames(classes.wrapper, wrapperClassName, forSingleList && classes['for-single-list']), [forSingleList, wrapperClassName], ); if (variant === 'inline') { return ( -
+
-
+
{renderedLabel} {renderedHint} {children} @@ -163,18 +164,7 @@ const Field: FC = ({ {renderedHint} {renderedErrorMessage}
- {endAdornment ? ( -
- {endAdornment} -
- ) : null} + {endAdornment ?
{endAdornment}
: null}
); }; diff --git a/packages/core/src/components/common/field/Hint.css b/packages/core/src/components/common/field/Hint.css new file mode 100644 index 000000000..3428415b4 --- /dev/null +++ b/packages/core/src/components/common/field/Hint.css @@ -0,0 +1,44 @@ +.CMS_Hint_root { + @apply w-full + flex + text-xs + italic; + + &:not(.CMS_Hint_error) { + &.CMS_Hint_disabled { + @apply text-slate-300 + dark:text-slate-600; + } + + &:not(.CMS_Hint_disabled) { + @apply text-slate-500 + dark:text-slate-400; + } + } + + &:not(.CMS_Hint_disabled) { + @apply group-focus-within/active:text-blue-500 + group-hover/active:text-blue-500; + } + + &:not(.CMS_Hint_inline) { + @apply px-3 + pt-1; + } +} + +.CMS_Hint_cursor-pointer { + @apply cursor-pointer; +} + +.CMS_Hint_cursor-text { + @apply cursor-text; +} + +.CMS_Hint_cursor-default { + @apply cursor-default; +} + +.CMS_Hint_error { + @apply text-red-500; +} diff --git a/packages/core/src/components/common/field/Hint.tsx b/packages/core/src/components/common/field/Hint.tsx index 63b88421d..97b8954f5 100644 --- a/packages/core/src/components/common/field/Hint.tsx +++ b/packages/core/src/components/common/field/Hint.tsx @@ -2,9 +2,22 @@ import React from 'react'; import useCursor from '@staticcms/core/lib/hooks/useCursor'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FC } from 'react'; +import './Hint.css'; + +export const classes = generateClassNames('Hint', [ + 'root', + 'inline', + 'disabled', + 'cursor-pointer', + 'cursor-text', + 'cursor-default', + 'error', +]); + export interface HintProps { children: string; hasErrors: boolean; @@ -28,32 +41,13 @@ const Hint: FC = ({
diff --git a/packages/core/src/components/common/field/Label.css b/packages/core/src/components/common/field/Label.css new file mode 100644 index 000000000..9cddad20d --- /dev/null +++ b/packages/core/src/components/common/field/Label.css @@ -0,0 +1,45 @@ +.CMS_Label_root { + @apply w-full + flex + text-xs + font-bold + dark:font-semibold; + + &:not(.CMS_Label_error) { + &.CMS_Label_disabled { + @apply text-slate-300 + dark:text-slate-600; + } + + &:not(.CMS_Label_disabled) { + @apply text-slate-500 + dark:text-slate-400; + } + } + + &:not(.CMS_Label_disabled) { + @apply group-focus-within/active:text-blue-500 + group-hover/active:text-blue-500; + } + + &:not(.CMS_Label_inline) { + @apply px-3 + pt-3; + } +} + +.CMS_Label_cursor-pointer { + @apply cursor-pointer; +} + +.CMS_Label_cursor-text { + @apply cursor-text; +} + +.CMS_Label_cursor-default { + @apply cursor-default; +} + +.CMS_Label_error { + @apply text-red-500; +} diff --git a/packages/core/src/components/common/field/Label.tsx b/packages/core/src/components/common/field/Label.tsx index 91bd9bde8..d6eca0998 100644 --- a/packages/core/src/components/common/field/Label.tsx +++ b/packages/core/src/components/common/field/Label.tsx @@ -2,9 +2,22 @@ import React from 'react'; import useCursor from '@staticcms/core/lib/hooks/useCursor'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FC } from 'react'; +import './Label.css'; + +export const classes = generateClassNames('Label', [ + 'root', + 'disabled', + 'cursor-pointer', + 'cursor-text', + 'cursor-default', + 'error', + 'inline', +]); + export interface LabelProps { htmlFor?: string; children: string; @@ -33,33 +46,13 @@ const Label: FC = ({ htmlFor={htmlFor} data-testid={dataTestId ?? 'label'} className={classNames( - ` - w-full - flex - text-xs - font-bold - dark:font-semibold - `, - !disabled && - ` - group-focus-within/active:text-blue-500 - group-hover/active:text-blue-500 - `, - finalCursor === 'pointer' && 'cursor-pointer', - finalCursor === 'text' && 'cursor-text', - finalCursor === 'default' && 'cursor-default', - hasErrors - ? 'text-red-500' - : disabled - ? ` - text-slate-300 - dark:text-slate-600 - ` - : ` - text-slate-500 - dark:text-slate-400 - `, - variant === 'default' && 'px-3 pt-3', + classes.root, + disabled && classes.disabled, + finalCursor === 'pointer' && classes['cursor-pointer'], + finalCursor === 'text' && classes['cursor-text'], + finalCursor === 'default' && classes['cursor-default'], + hasErrors && classes.error, + variant === 'inline' && classes.inline, className, )} > diff --git a/packages/core/src/components/common/image/Image.css b/packages/core/src/components/common/image/Image.css new file mode 100644 index 000000000..1f9272465 --- /dev/null +++ b/packages/core/src/components/common/image/Image.css @@ -0,0 +1,16 @@ +.CMS_Image_root { + &:not(.CMS_Image_empty) { + @apply object-cover + max-w-full + overflow-hidden; + } + + &.CMS_Image_empty { + @apply p-10 + rounded-md + border + max-w-full + border-gray-200/75 + dark:border-slate-600/75; + } +} diff --git a/packages/core/src/components/common/image/Image.tsx b/packages/core/src/components/common/image/Image.tsx index 315d5698f..8af76c1b8 100644 --- a/packages/core/src/components/common/image/Image.tsx +++ b/packages/core/src/components/common/image/Image.tsx @@ -1,11 +1,12 @@ -import React from 'react'; import { Image as ImageIcon } from '@styled-icons/material-outlined/Image'; +import React from 'react'; import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { isEmpty } from '@staticcms/core/lib/util/string.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft'; import { useAppSelector } from '@staticcms/core/store/hooks'; -import { isEmpty } from '@staticcms/core/lib/util/string.util'; import type { BaseField, @@ -16,6 +17,10 @@ import type { } from '@staticcms/core/interface'; import type { CSSProperties } from 'react'; +import './Image.css'; + +export const classes = generateClassNames('Image', ['root', 'empty']); + export interface ImageProps { src?: string; alt?: string; @@ -42,18 +47,7 @@ const Image = ({ const assetSource = useMediaAsset(src, collection, field, entry ?? editingDraft); if (isEmpty(src)) { - return ( - - ); + return ; } return ( @@ -63,7 +57,7 @@ const Image = ({ src={assetSource} alt={alt} data-testid={dataTestId ?? 'image'} - className={classNames('object-cover max-w-full overflow-hidden', className)} + className={classNames(classes.root, className)} style={style} /> ); diff --git a/packages/core/src/components/common/menu/Menu.css b/packages/core/src/components/common/menu/Menu.css new file mode 100644 index 000000000..d6cfcbaf4 --- /dev/null +++ b/packages/core/src/components/common/menu/Menu.css @@ -0,0 +1,69 @@ +.CMS_Menu_root { + @apply flex; + + &:not(.CMS_Menu_hide-label) { + &:not(.CMS_Menu_hide-dropdown-icon) { + & .CMS_Menu_dropdown { + & .CMS_Menu_dropdown-start-icon { + @apply mr-1.5; + } + } + } + + & .CMS_Menu_dropdown { + & .CMS_Menu_dropdown-icon { + @apply ml-2; + } + } + } + + &.CMS_Menu_hide-dropdown-icon-mobile { + & .CMS_Menu_dropdown { + & .CMS_Menu_dropdown-start-icon { + @apply !mr-0 + md:!mr-1.5; + } + + & .CMS_Menu_dropdown-icon { + @apply !hidden + md:!block; + } + } + } +} + +.CMS_Menu_dropdown { + @apply whitespace-nowrap; +} + +.CMS_Menu_dropdown-start-icon { + @apply -ml-0.5 + h-5 + w-5; +} + +.CMS_Menu_dropdown-icon { + @apply -mr-0.5 + h-5 + w-5; +} + +.CMS_Menu_menu { + @apply absolute + right-0 + z-40 + w-56 + origin-top-right + rounded-md + bg-white + dark:bg-slate-800 + shadow-md + border + border-gray-200 + focus:outline-none + divide-y + divide-gray-100 + dark:border-gray-700 + dark:divide-gray-600 + dark:shadow-lg; +} diff --git a/packages/core/src/components/common/menu/Menu.tsx b/packages/core/src/components/common/menu/Menu.tsx index a605627d5..10ddfd4a7 100644 --- a/packages/core/src/components/common/menu/Menu.tsx +++ b/packages/core/src/components/common/menu/Menu.tsx @@ -1,14 +1,30 @@ -import ClickAwayListener from '@mui/base/ClickAwayListener'; -import MenuUnstyled from '@mui/base/MenuUnstyled'; +import { Dropdown } from '@mui/base/Dropdown'; +import { Menu as BaseMenu } from '@mui/base/Menu'; +import { MenuButton } from '@mui/base/MenuButton'; import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@styled-icons/material/KeyboardArrowDown'; -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import useButtonClassNames from '../button/useButtonClassNames'; import type { FC, ReactNode } from 'react'; import type { BaseBaseProps } from '../button/Button'; +import './Menu.css'; + +export const classes = generateClassNames('Menu', [ + 'root', + 'hide-dropdown-icon', + 'hide-label', + 'hide-dropdown-icon-mobile', + 'dropdown', + 'dropdown-start-icon', + 'dropdown-icon', + 'label', + 'menu', +]); + export interface MenuProps { label: ReactNode; startIcon?: FC<{ className?: string }>; @@ -24,8 +40,8 @@ export interface MenuProps { hideDropdownIcon?: boolean; hideDropdownIconOnMobile?: boolean; hideLabel?: boolean; - keepMounted?: boolean; disabled?: boolean; + keepMounted?: boolean; 'data-testid'?: string; } @@ -44,103 +60,56 @@ const Menu = ({ hideDropdownIcon = false, hideDropdownIconOnMobile = false, hideLabel = false, - keepMounted = false, disabled = false, + keepMounted = false, 'data-testid': dataTestId, }: MenuProps) => { - const [anchorEl, setAnchorEl] = React.useState(null); - const isOpen = Boolean(anchorEl); - - const handleButtonClick = useCallback( - (event: React.MouseEvent) => { - if (isOpen) { - setAnchorEl(null); - } else { - setAnchorEl(event.currentTarget); - } - }, - [isOpen], - ); - - const handleClose = useCallback(() => { - setAnchorEl(null); - }, []); - const calculatedButtonClassName = useButtonClassNames(variant, color, size, rounded); const menuButtonClassNames = useMemo( - () => classNames(calculatedButtonClassName, buttonClassName, 'whitespace-nowrap'), + () => classNames(calculatedButtonClassName, buttonClassName, classes.dropdown), [calculatedButtonClassName, buttonClassName], ); return ( - -
- - + {children} - +
-
+ ); }; diff --git a/packages/core/src/components/common/menu/MenuGroup.css b/packages/core/src/components/common/menu/MenuGroup.css new file mode 100644 index 000000000..116dd229b --- /dev/null +++ b/packages/core/src/components/common/menu/MenuGroup.css @@ -0,0 +1,6 @@ +.CMS_MenuGroup_root { + @apply py-1 + border-b + border-gray-200 + dark:border-slate-700; +} diff --git a/packages/core/src/components/common/menu/MenuGroup.tsx b/packages/core/src/components/common/menu/MenuGroup.tsx index 78bc6be38..25b245fa9 100644 --- a/packages/core/src/components/common/menu/MenuGroup.tsx +++ b/packages/core/src/components/common/menu/MenuGroup.tsx @@ -1,24 +1,19 @@ import React from 'react'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + import type { ReactNode } from 'react'; +import './MenuGroup.css'; + +export const classes = generateClassNames('MenuGroup', ['root']); + export interface MenuGroupProps { children: ReactNode | ReactNode[]; } const MenuGroup = ({ children }: MenuGroupProps) => { - return ( -
- {children} -
- ); + return
{children}
; }; export default MenuGroup; diff --git a/packages/core/src/components/common/menu/MenuItemButton.css b/packages/core/src/components/common/menu/MenuItemButton.css new file mode 100644 index 000000000..1b9d08928 --- /dev/null +++ b/packages/core/src/components/common/menu/MenuItemButton.css @@ -0,0 +1,84 @@ +.CMS_MenuItemButton_root { + @apply px-4 + py-2 + text-sm + w-full + text-left + flex + items-center + justify-between + cursor-pointer; + + &:not(.CMS_MenuItemButton_disabled) { + &.CMS_MenuItemButton_active { + @apply bg-slate-200 + dark:bg-slate-600; + } + } + + &.CMS_MenuItemButton_default { + @apply text-gray-800 + dark:text-gray-300; + + &.CMS_MenuItemButton_disabled { + @apply text-gray-500 + dark:text-gray-700; + } + + &:not(.CMS_MenuItemButton_disabled) { + @apply hover:bg-gray-200 + dark:hover:bg-slate-600; + } + } + + &.CMS_MenuItemButton_warning { + @apply text-yellow-600 + dark:text-yellow-500; + + &.CMS_MenuItemButton_disabled { + @apply text-yellow-300 + dark:hover:bg-yellow-800; + } + + &:not(.CMS_MenuItemButton_disabled) { + @apply hover:text-white + hover:bg-yellow-500 + dark:hover:text-yellow-100 + dark:hover:bg-yellow-600; + } + } + + &.CMS_MenuItemButton_error { + @apply text-red-500 + dark:text-red-500; + + &.CMS_MenuItemButton_disabled { + @apply text-red-200 + dark:hover:bg-red-800; + } + + &:not(.CMS_MenuItemButton_disabled) { + @apply hover:text-white + hover:bg-red-500 + dark:hover:text-red-100 + dark:hover:bg-red-600; + } + } +} + +.CMS_MenuItemButton_content { + @apply flex + items-center + gap-2 + flex-grow; +} + +.CMS_MenuItemButton_start-icon { + @apply h-5 + w-5; +} + +.CMS_MenuItemButton_end-icon { + @apply h-5 + w-5; +} diff --git a/packages/core/src/components/common/menu/MenuItemButton.tsx b/packages/core/src/components/common/menu/MenuItemButton.tsx index 0d422212f..d80e642e0 100644 --- a/packages/core/src/components/common/menu/MenuItemButton.tsx +++ b/packages/core/src/components/common/menu/MenuItemButton.tsx @@ -1,10 +1,25 @@ +import { MenuItem } from '@mui/base/MenuItem'; import React from 'react'; -import MenuItemUnstyled from '@mui/base/MenuItemUnstyled'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FC, MouseEvent, ReactNode } from 'react'; +import './MenuItemButton.css'; + +export const classes = generateClassNames('MenuItemButton', [ + 'root', + 'disabled', + 'active', + 'default', + 'warning', + 'error', + 'content', + 'start-icon', + 'end-icon', +]); + export interface MenuItemButtonProps { active?: boolean; onClick: (event: MouseEvent) => void; @@ -29,50 +44,17 @@ const MenuItemButton = ({ 'data-testid': dataTestId, }: MenuItemButtonProps) => { return ( - -
- {StartIcon ? : null} +
+ {StartIcon ? : null} {children}
- {EndIcon ? : null} - + {EndIcon ? : null} + ); }; diff --git a/packages/core/src/components/common/menu/MenuItemLink.css b/packages/core/src/components/common/menu/MenuItemLink.css new file mode 100644 index 000000000..68d55d77f --- /dev/null +++ b/packages/core/src/components/common/menu/MenuItemLink.css @@ -0,0 +1,36 @@ +.CMS_MenuItemLink_root { + @apply px-4 + py-2 + text-sm + text-gray-800 + dark:text-gray-300 + w-full + text-left + flex + items-center + justify-between + hover:bg-slate-100 + dark:hover:bg-slate-900; + + &.CMS_MenuItemLink_active { + @apply bg-slate-100 + dark:bg-slate-900; + } +} + +.CMS_MenuItemLink_content { + @apply flex + items-center + gap-2 + flex-grow; +} + +.CMS_MenuItemLink_start-icon { + @apply h-5 + w-5; +} + +.CMS_MenuItemLink_end-icon { + @apply h-5 + w-5; +} diff --git a/packages/core/src/components/common/menu/MenuItemLink.tsx b/packages/core/src/components/common/menu/MenuItemLink.tsx index 026a3d4b2..39b06f122 100644 --- a/packages/core/src/components/common/menu/MenuItemLink.tsx +++ b/packages/core/src/components/common/menu/MenuItemLink.tsx @@ -1,11 +1,22 @@ +import { MenuItem } from '@mui/base/MenuItem'; import React from 'react'; import { NavLink } from 'react-router-dom'; -import MenuItemUnstyled from '@mui/base/MenuItemUnstyled'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FC, ReactNode } from 'react'; +import './MenuItemLink.css'; + +export const classes = generateClassNames('MenuItemLink', [ + 'root', + 'active', + 'content', + 'start-icon', + 'end-icon', +]); + export interface MenuItemLinkProps { href: string; children: ReactNode; @@ -24,38 +35,21 @@ const MenuItemLink = ({ endIcon: EndIcon, }: MenuItemLinkProps) => { return ( - -
- {StartIcon ? : null} - {children} -
- {EndIcon ? : null} -
+ + +
+ {StartIcon ? : null} + {children} +
+ {EndIcon ? : null} +
+
); }; diff --git a/packages/core/src/components/common/modal/Backdrop.tsx b/packages/core/src/components/common/modal/Backdrop.tsx index 44ea74cf6..e85a61cfd 100644 --- a/packages/core/src/components/common/modal/Backdrop.tsx +++ b/packages/core/src/components/common/modal/Backdrop.tsx @@ -1,6 +1,7 @@ import React from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import modalClasses from './Modal.classes'; const Backdrop = React.forwardRef< HTMLDivElement, @@ -9,18 +10,7 @@ const Backdrop = React.forwardRef< const { open, className, ownerState: _ownerState, ...other } = props; return (
diff --git a/packages/core/src/components/common/modal/Modal.classes.ts b/packages/core/src/components/common/modal/Modal.classes.ts new file mode 100644 index 000000000..2e8137ee6 --- /dev/null +++ b/packages/core/src/components/common/modal/Modal.classes.ts @@ -0,0 +1,5 @@ +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; + +const modalClasses = generateClassNames('Modal', ['root', 'content', 'backdrop']); + +export default modalClasses; diff --git a/packages/core/src/components/common/modal/Modal.css b/packages/core/src/components/common/modal/Modal.css new file mode 100644 index 000000000..37e694c88 --- /dev/null +++ b/packages/core/src/components/common/modal/Modal.css @@ -0,0 +1,34 @@ +.CMS_Modal_root { + @apply fixed + inset-0 + overflow-y-auto + z-50 + flex + min-h-full + items-center + justify-center + text-center; +} + +.CMS_Modal_backdrop { + @apply fixed + inset-0 + bg-black + bg-opacity-50 + dark:bg-opacity-60 + z-50; +} + +.CMS_Modal_content { + @apply transform + overflow-visible + rounded-lg + text-left + align-middle + shadow-xl + transition-all + bg-white + dark:bg-slate-800 + z-[51] + outline-none; +} diff --git a/packages/core/src/components/common/modal/Modal.tsx b/packages/core/src/components/common/modal/Modal.tsx index e3b783d76..06f2e65b7 100644 --- a/packages/core/src/components/common/modal/Modal.tsx +++ b/packages/core/src/components/common/modal/Modal.tsx @@ -1,11 +1,14 @@ -import ModalUnstyled from '@mui/base/ModalUnstyled'; +import { Modal as BaseModal } from '@mui/base/Modal'; import React, { useCallback } from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; import Backdrop from './Backdrop'; +import modalClasses from './Modal.classes'; import type { FC, ReactNode } from 'react'; +import './Modal.css'; + interface ModalProps { open: boolean; children: ReactNode; @@ -19,7 +22,7 @@ const Modal: FC = ({ open, children, className, onClose }) => { }, [onClose]); return ( - = ({ open, children, className, onClose }) => { }} slotProps={{ root: { - className: ` - fixed - inset-0 - overflow-y-auto - z-50 - flex - min-h-full - items-center - justify-center - text-center - styled-scrollbars - `, + className: modalClasses.root, }, }} > -
- {children} -
-
+
{children}
+ ); }; diff --git a/packages/core/src/components/common/pill/Pill.css b/packages/core/src/components/common/pill/Pill.css new file mode 100644 index 000000000..80ec5451a --- /dev/null +++ b/packages/core/src/components/common/pill/Pill.css @@ -0,0 +1,42 @@ +.CMS_Pill_root { + @apply text-xs + font-medium + px-3 + py-1 + rounded-lg + truncate; + + &.CMS_Pill_no-wrap { + @apply whitespace-nowrap; + } + + &.CMS_Pill_primary { + @apply bg-blue-700 + text-gray-100 + dark:bg-blue-700 + dark:text-gray-100; + } + + &.CMS_Pill_default { + @apply bg-gray-200 + text-gray-800 + dark:bg-gray-700 + dark:text-gray-100; + } + + &.CMS_Pill_disabled { + &.CMS_Pill_primary { + @apply bg-blue-300/75 + text-gray-100/75 + dark:bg-blue-700/25 + dark:text-gray-500; + } + + &.CMS_Pill_default { + @apply bg-gray-100 + text-gray-400/75 + dark:bg-gray-800/75 + dark:text-gray-500; + } + } +} diff --git a/packages/core/src/components/common/pill/Pill.tsx b/packages/core/src/components/common/pill/Pill.tsx index 0c00acb44..91f786695 100644 --- a/packages/core/src/components/common/pill/Pill.tsx +++ b/packages/core/src/components/common/pill/Pill.tsx @@ -1,9 +1,20 @@ import React, { useMemo } from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { FC, ReactNode } from 'react'; +import './Pill.css'; + +export const classes = generateClassNames('Pill', [ + 'root', + 'no-wrap', + 'primary', + 'default', + 'disabled', +]); + interface PillProps { children: ReactNode | ReactNode[]; noWrap?: boolean; @@ -22,48 +33,18 @@ const Pill: FC = ({ const colorClassNames = useMemo(() => { switch (color) { case 'primary': - return disabled - ? ` - bg-blue-300/75 - text-gray-100/75 - dark:bg-blue-700/25 - dark:text-gray-500 - ` - : ` - bg-blue-700 - text-gray-100 - dark:bg-blue-700 - dark:text-gray-100 - `; + return classes.primary; default: - return disabled - ? ` - bg-gray-100 - text-gray-400/75 - dark:bg-gray-800/75 - dark:text-gray-500 - ` - : ` - bg-gray-200 - text-gray-800 - dark:bg-gray-700 - dark:text-gray-100 - `; + return classes.default; } - }, [color, disabled]); + }, [color]); return ( = ({ size = 'medium', }) => { return ( -
+
- Loading... + Loading...
); }; diff --git a/packages/core/src/components/common/progress/Loader.css b/packages/core/src/components/common/progress/Loader.css new file mode 100644 index 000000000..a8928d446 --- /dev/null +++ b/packages/core/src/components/common/progress/Loader.css @@ -0,0 +1,11 @@ +.CMS_Loader_root { + @apply absolute + inset-0 + flex + flex-col + gap-2 + items-center + justify-center + bg-slate-50 + dark:bg-slate-900; +} diff --git a/packages/core/src/components/common/progress/Loader.tsx b/packages/core/src/components/common/progress/Loader.tsx index 3cf02afb7..f3c104918 100644 --- a/packages/core/src/components/common/progress/Loader.tsx +++ b/packages/core/src/components/common/progress/Loader.tsx @@ -1,7 +1,12 @@ import React, { useEffect, useMemo, useState } from 'react'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import CircularProgress from './CircularProgress'; +import './Loader.css'; + +export const classes = generateClassNames('Loader', ['root']); + export interface LoaderProps { children: string | string[] | undefined; } @@ -35,19 +40,7 @@ const Loader = ({ children }: LoaderProps) => { }, [children, currentItem]); return ( -
+
{text}
diff --git a/packages/core/src/components/common/select/Option.css b/packages/core/src/components/common/select/Option.css new file mode 100644 index 000000000..d0e39bb31 --- /dev/null +++ b/packages/core/src/components/common/select/Option.css @@ -0,0 +1,29 @@ +.CMS_SelectOption_root { + @apply relative + select-none + py-2 + px-4 + cursor-pointer + text-gray-800 + hover:bg-blue-500 + dark:text-gray-100; + + &.CMS_SelectOption_selected { + @apply bg-blue-400/75; + + & .CMS_SelectOption_label { + @apply font-medium; + } + } + + &:not(.CMS_SelectOption_selected) { + & .CMS_SelectOption_label { + @apply font-normal; + } + } +} + +.CMS_SelectOption_label { + @apply block + truncate; +} diff --git a/packages/core/src/components/common/select/Option.tsx b/packages/core/src/components/common/select/Option.tsx index 67b7719d7..2e6832851 100644 --- a/packages/core/src/components/common/select/Option.tsx +++ b/packages/core/src/components/common/select/Option.tsx @@ -1,11 +1,16 @@ -import OptionUnstyled from '@mui/base/OptionUnstyled'; +import { Option as BaseOption } from '@mui/base/Option'; import React, { useMemo } from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; import { isNotNullish } from '@staticcms/core/lib/util/null.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { ReactNode } from 'react'; +import './Option.css'; + +export const classes = generateClassNames('SelectOption', ['root', 'selected', 'label']); + export interface OptionProps { selectedValue: T | null | T[]; value: T | null; @@ -28,31 +33,17 @@ const Option = function ({ ); return ( - - - {children} - - + {children} + ); }; diff --git a/packages/core/src/components/common/select/Select.css b/packages/core/src/components/common/select/Select.css new file mode 100644 index 000000000..bcc5b4721 --- /dev/null +++ b/packages/core/src/components/common/select/Select.css @@ -0,0 +1,81 @@ +.CMS_Select_root { + @apply relative + w-full; + + &.CMS_Select_disabled { + & .CMS_Select_input { + @apply text-gray-300/75 + dark:text-gray-600/75; + } + + & .CMS_Select_value { + & .CMS_Select_dropdown { + & .CMS_Select_dropdown-icon { + @apply text-gray-300/75 + dark:text-gray-600/75; + } + } + } + } +} + +.CMS_Select_value { + @apply w-full; +} + +.CMS_Select_label { + @apply flex + w-select-widget-label; +} + +.CMS_Select_label-text { + @apply truncate; +} + +.CMS_Select_dropdown { + @apply pointer-events-none + absolute + inset-y-0 + right-0 + flex + items-center + pr-2; +} + +.CMS_Select_dropdown-icon { + @apply h-5 + w-5 + text-gray-400; +} + +.CMS_Select_input { + @apply flex + items-center + text-sm + font-medium + relative + min-h-8 + px-4 + py-1.5 + w-full + text-gray-800 + dark:text-gray-100; +} + +.CMS_Select_popper { + @apply max-h-60 + overflow-auto + rounded-md + bg-white + py-1 + text-base + shadow-md + ring-1 + ring-black + ring-opacity-5 + focus:outline-none + sm:text-sm + z-[100] + dark:bg-slate-700 + dark:shadow-lg; +} diff --git a/packages/core/src/components/common/select/Select.tsx b/packages/core/src/components/common/select/Select.tsx index bf3cfb702..a8b50732a 100644 --- a/packages/core/src/components/common/select/Select.tsx +++ b/packages/core/src/components/common/select/Select.tsx @@ -1,14 +1,31 @@ -import SelectUnstyled from '@mui/base/SelectUnstyled'; +import { Select as BaseSelect } from '@mui/base/Select'; +import ClickAwayListener from '@mui/material/ClickAwayListener'; import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@styled-icons/material/KeyboardArrowDown'; import React, { forwardRef, useCallback, useState } from 'react'; import useElementSize from '@staticcms/core/lib/hooks/useElementSize'; import classNames from '@staticcms/core/lib/util/classNames.util'; import { isNotEmpty } from '@staticcms/core/lib/util/string.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import Option from './Option'; import type { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, Ref } from 'react'; +import './Select.css'; + +export const classes = generateClassNames('Select', [ + 'root', + 'disabled', + 'input', + 'value', + 'label', + 'label-text', + 'dropdown', + 'dropdown-icon', + 'input', + 'popper', +]); + export interface Option { label: string; value: number | string; @@ -31,6 +48,7 @@ export interface SelectProps { options: (number | string)[] | Option[]; required?: boolean; disabled?: boolean; + rootClassName?: string; onChange: SelectChangeEventHandler; onOpenChange?: (open: boolean) => void; } @@ -44,6 +62,7 @@ const Select = forwardRef( options, required = false, disabled, + rootClassName, onChange, onOpenChange, }: SelectProps, @@ -82,120 +101,79 @@ const Select = forwardRef( [onChange, value], ); + const handleClick = useCallback( + (event: MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + handleOpenChange(!open); + }, + [handleOpenChange, open], + ); + + const handleClickAway = useCallback(() => { + handleOpenChange(false); + }, [handleOpenChange]); + return ( -
- {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - - renderValue={() => { - return ( -
-
- {label ?? placeholder} + +
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + + renderValue={() => { + return ( +
+
+ {label ?? placeholder} +
+ +
- - -
- ); - }} - slotProps={{ - root: { - ref, - className: classNames( - ` - flex - items-center - text-sm - font-medium - relative - min-h-8 - px-4 - py-1.5 - w-full - text-gray-800 - dark:text-gray-100 - `, - disabled && - ` - text-gray-300/75 - dark:text-gray-600/75 - `, - ), - }, - popper: { - className: ` - max-h-60 - overflow-auto - rounded-md - bg-white - py-1 - text-base - shadow-md - ring-1 - ring-black - ring-opacity-5 - focus:outline-none - sm:text-sm - z-[100] - dark:bg-slate-700 - dark:shadow-lg - `, - style: { width: ref ? width : 'auto' }, - disablePortal: false, - }, - }} - value={value} - disabled={disabled} - onChange={handleChange} - listboxOpen={open} - onListboxOpenChange={handleOpenChange} - data-testid="select-input" - > - {!Array.isArray(value) && !required ? ( - - ) : null} - {options.map((option, index) => { - const { label: optionLabel, value: optionValue } = getOptionLabelAndValue(option); - - return ( - - ); - })} - -
+ ) : null} + {options.map((option, index) => { + const { label: optionLabel, value: optionValue } = getOptionLabelAndValue(option); + + return ( + + ); + })} + +
+ ); }, ); diff --git a/packages/core/src/components/common/switch/Switch.css b/packages/core/src/components/common/switch/Switch.css new file mode 100644 index 000000000..c706db245 --- /dev/null +++ b/packages/core/src/components/common/switch/Switch.css @@ -0,0 +1,65 @@ +.CMS_Switch_root { + @apply relative + inline-flex + items-center + cursor-pointer; + + &.CMS_Switch_disabled { + @apply cursor-default; + + & .CMS_Switch_toggle { + @apply peer-checked:bg-blue-600/25 + after:bg-gray-500/75 + after:border-gray-500/75 + peer-checked:after:border-gray-500/75; + } + } + + &:not(.CMS_Switch_disabled) { + & .CMS_Switch_toggle { + @apply after:bg-white + after:border-gray-300; + } + } +} + +.CMS_Switch_input { + @apply sr-only; + + &:focus + .CMS_Switch_toggle { + @apply ring-4 + ring-blue-300 + dark:ring-blue-800; + } + + &:checked + .CMS_Switch_toggle { + @apply bg-blue-600 + after:border-white + after:translate-x-full; + } +} + +.CMS_Switch_toggle { + @apply w-11 + h-6 + bg-slate-200 + rounded-full + dark:bg-slate-700 + after:content-[''] + after:absolute after:top-0.5 + after:left-[2px] + after:border + after:rounded-full + after:h-5 + after:w-5 + after:transition-all + dark:border-gray-600; +} + +.CMS_Switch_label { + @apply ml-3 + text-sm + font-medium + text-gray-800 + dark:text-gray-300; +} diff --git a/packages/core/src/components/common/switch/Switch.tsx b/packages/core/src/components/common/switch/Switch.tsx index f06ddd33b..deee0557c 100644 --- a/packages/core/src/components/common/switch/Switch.tsx +++ b/packages/core/src/components/common/switch/Switch.tsx @@ -1,18 +1,31 @@ import React, { forwardRef, useCallback } from 'react'; import classNames from '@staticcms/core/lib/util/classNames.util'; +import { generateClassNames } from '@staticcms/core/lib/util/theming.util'; import type { ChangeEvent, ChangeEventHandler } from 'react'; +import './Switch.css'; + +export const classes = generateClassNames('Switch', [ + 'root', + 'disabled', + 'input', + 'toggle', + 'label', +]); + export interface SwitchProps { label?: string; value: boolean; disabled?: boolean; + rootClassName?: string; + inputClassName?: string; onChange?: ChangeEventHandler; } const Switch = forwardRef( - ({ label, value, disabled, onChange }, ref) => { + ({ label, value, disabled, rootClassName, inputClassName, onChange }, ref) => { const handleChange = useCallback( (event: ChangeEvent) => { onChange?.(event); @@ -21,78 +34,19 @@ const Switch = forwardRef( ); return ( -