diff --git a/packages/core/dev-test/config.yml b/packages/core/dev-test/config.yml index 7a04fe64..072dfd6e 100644 --- a/packages/core/dev-test/config.yml +++ b/packages/core/dev-test/config.yml @@ -870,7 +870,7 @@ collections: - value: 2 label: Another fancy label - value: c - label: And one more fancy label + label: And one more fancy label test test test test test test test - label: Value and Label With Default name: value_and_label_with_default widget: select diff --git a/packages/core/package.json b/packages/core/package.json index affe2a3f..ccd11ad3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -84,9 +84,9 @@ "@styled-icons/material-rounded": "10.47.0", "@styled-icons/remix-editor": "10.46.0", "@styled-icons/simple-icons": "10.46.0", - "@udecode/plate": "21.1.4", - "@udecode/plate-juice": "21.0.0", - "@udecode/plate-serializer-md": "21.0.0", + "@udecode/plate": "21.3.2", + "@udecode/plate-juice": "21.3.2", + "@udecode/plate-serializer-md": "21.3.2", "@uiw/codemirror-extensions-langs": "4.19.16", "@uiw/react-codemirror": "4.19.16", "ajv": "8.12.0", @@ -156,10 +156,10 @@ "sanitize-filename": "1.6.3", "scheduler": "0.23.0", "semaphore": "1.1.0", - "slate": "0.91.4", - "slate-history": "0.86.0", + "slate": "0.94.1", + "slate-history": "0.93.0", "slate-hyperscript": "0.77.0", - "slate-react": "0.91.10", + "slate-react": "0.95.0", "stream-browserify": "3.0.0", "styled-components": "5.3.10", "symbol-observable": "4.0.0", @@ -271,6 +271,7 @@ "tailwindcss": "3.3.1", "to-string-loader": "1.2.0", "ts-jest": "29.1.0", + "ts-node": "10.9.1", "tsconfig-paths-webpack-plugin": "4.0.1", "typescript": "5.0.4", "webpack": "5.80.0", diff --git a/packages/core/src/bootstrap.tsx b/packages/core/src/bootstrap.tsx index 7ebaa33b..6148ba6d 100644 --- a/packages/core/src/bootstrap.tsx +++ b/packages/core/src/bootstrap.tsx @@ -16,6 +16,7 @@ import addExtensions from './extensions'; import { getPhrases } from './lib/phrases'; import { selectLocale } from './reducers/selectors/config'; import { store } from './store'; +import useMeta from './lib/hooks/useMeta'; import type { AnyAction } from '@reduxjs/toolkit'; import type { ConnectedProps } from 'react-redux'; @@ -45,6 +46,8 @@ import ReactDOM from 'react-dom'; ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = true; const TranslatedApp = ({ locale, config }: AppRootProps) => { + useMeta({ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }); + if (!config) { return null; } diff --git a/packages/core/src/components/MainView.tsx b/packages/core/src/components/MainView.tsx index 55851dbb..fb813785 100644 --- a/packages/core/src/components/MainView.tsx +++ b/packages/core/src/components/MainView.tsx @@ -2,11 +2,12 @@ import React from 'react'; import TopBarProgress from 'react-topbar-progress-indicator'; import classNames from '../lib/util/classNames.util'; +import BottomNavigation from './navbar/BottomNavigation'; import Navbar from './navbar/Navbar'; import Sidebar from './navbar/Sidebar'; import type { ReactNode } from 'react'; -import type { Breadcrumb } from '../interface'; +import type { Breadcrumb, Collection } from '../interface'; TopBarProgress.config({ barColors: { @@ -25,6 +26,7 @@ interface MainViewProps { noMargin?: boolean; noScroll?: boolean; children: ReactNode; + collection?: Collection; } const MainView = ({ @@ -35,6 +37,7 @@ const MainView = ({ noMargin = false, noScroll = false, navbarActions, + collection, }: MainViewProps) => { return ( <> @@ -48,11 +51,12 @@ const MainView = ({
+ ); }; diff --git a/packages/core/src/components/collections/CollectionControls.tsx b/packages/core/src/components/collections/CollectionControls.tsx index 1d249bb5..24694aaf 100644 --- a/packages/core/src/components/collections/CollectionControls.tsx +++ b/packages/core/src/components/collections/CollectionControls.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useMemo } from 'react'; +import ViewStyleControl from '../common/view-style/ViewStyleControl'; import FilterControl from './FilterControl'; import GroupControl from './GroupControl'; +import MobileCollectionControls from './mobile/MobileCollectionControls'; import SortControl from './SortControl'; -import ViewStyleControl from '../common/view-style/ViewStyleControl'; import type { ViewStyle } from '@staticcms/core/constants/views'; import type { @@ -12,7 +13,6 @@ import type { SortableField, SortDirection, SortMap, - TranslatedProps, ViewFilter, ViewGroup, } from '@staticcms/core/interface'; @@ -41,34 +41,69 @@ const CollectionControls = ({ viewGroups, onFilterClick, onGroupClick, - t, filter, group, -}: TranslatedProps) => { +}: CollectionControlsProps) => { + const showGroupControl = useMemo( + () => Boolean(viewGroups && onGroupClick && group && viewGroups.length > 0), + [group, onGroupClick, viewGroups], + ); + + const showFilterControl = useMemo( + () => Boolean(viewFilters && onFilterClick && filter && viewFilters.length > 0), + [filter, onFilterClick, viewFilters], + ); + + const showSortControl = useMemo( + () => Boolean(sortableFields && onSortClick && sort && sortableFields.length > 0), + [onSortClick, sort, sortableFields], + ); + return ( -
- - {viewGroups && onGroupClick && group - ? viewGroups.length > 0 && ( - - ) - : null} - {viewFilters && onFilterClick && filter - ? viewFilters.length > 0 && ( - - ) - : null} - {sortableFields && onSortClick && sort - ? sortableFields.length > 0 && ( - - ) - : null} -
+ <> +
+ + {showGroupControl || showFilterControl || showFilterControl ? ( + + ) : null} + {showGroupControl ? ( + + ) : null} + {showFilterControl ? ( + + ) : null} + {showSortControl ? ( + + ) : null} +
+ ); }; diff --git a/packages/core/src/components/collections/CollectionHeader.tsx b/packages/core/src/components/collections/CollectionHeader.tsx index e2a9160d..6657a87f 100644 --- a/packages/core/src/components/collections/CollectionHeader.tsx +++ b/packages/core/src/components/collections/CollectionHeader.tsx @@ -11,26 +11,25 @@ import { import { isNotEmpty } from '@staticcms/core/lib/util/string.util'; import { addFileTemplateFields } from '@staticcms/core/lib/widgets/stringTemplate'; import Button from '../common/button/Button'; +import useNewEntryUrl from '@staticcms/core/lib/hooks/useNewEntryUrl'; import type { Collection, Entry, TranslatedProps } from '@staticcms/core/interface'; +import type { FC } from 'react'; interface CollectionHeaderProps { collection: Collection; - newEntryUrl?: string; } -const CollectionHeader = ({ - collection, - newEntryUrl, - t, -}: TranslatedProps) => { +const CollectionHeader: FC> = ({ collection, t }) => { const collectionLabel = collection.label; const collectionLabelSingular = collection.label_singular; - const icon = useIcon(collection.icon); - const params = useParams(); const filterTerm = useMemo(() => params['*'], [params]); + const newEntryUrl = useNewEntryUrl(collection, filterTerm); + + const icon = useIcon(collection.icon); + const entries = useEntries(collection); const pluralLabel = useMemo(() => { @@ -65,7 +64,18 @@ const CollectionHeader = ({ return ( <> -
+

{icon}
- {pluralLabel} +
+ {pluralLabel} +

{newEntryUrl ? ( - + ); + })}
- ); - })} +
+ + ); } diff --git a/packages/core/src/components/collections/entries/EntryCard.tsx b/packages/core/src/components/collections/entries/EntryCard.tsx index c69f589d..628b39e2 100644 --- a/packages/core/src/components/collections/entries/EntryCard.tsx +++ b/packages/core/src/components/collections/entries/EntryCard.tsx @@ -113,52 +113,64 @@ const EntryCard: FC> = ({ if (PreviewCardComponent) { return ( - - - - - +
+
+
+ + + + + +
+
+
); } return ( - - - {image && imageField ? ( - - ) : null} - -
-
{summary}
- {hasLocalBackup ? ( - - ) : null} -
-
-
-
+
+
+
+ + + {image && imageField ? ( + + ) : null} + +
+
{summary}
+ {hasLocalBackup ? ( + + ) : null} +
+
+
+
+
+
+
); }; diff --git a/packages/core/src/components/collections/entries/EntryListingCardGrid.tsx b/packages/core/src/components/collections/entries/EntryListingCardGrid.tsx index c2b7847e..d048aa1e 100644 --- a/packages/core/src/components/collections/entries/EntryListingCardGrid.tsx +++ b/packages/core/src/components/collections/entries/EntryListingCardGrid.tsx @@ -47,7 +47,7 @@ const CardWrapper = ({ ? style.left ?? COLLECTION_CARD_MARGIN * columnIndex : style.left }`, - ), + ) + 4, [columnIndex, style.left], ); @@ -138,12 +138,22 @@ const EntryListingCardGrid: FC = ({ }, [cardHeights, prevCardHeights.length]); return ( -
+
{({ height = 0, width = 0 }) => { + const calculatedWidth = width - 4; const columnWidthWithGutter = COLLECTION_CARD_WIDTH + COLLECTION_CARD_MARGIN; - const columnCount = Math.floor(width / columnWidthWithGutter); - const nonGutterSpace = (width - COLLECTION_CARD_MARGIN * columnCount) / width; + const columnCount = Math.max(Math.floor(calculatedWidth / columnWidthWithGutter), 1); + const nonGutterSpace = + (calculatedWidth - COLLECTION_CARD_MARGIN * columnCount) / calculatedWidth; const columnWidth = (1 / columnCount) * nonGutterSpace; const rowCount = Math.ceil(entryData.length / columnCount); @@ -157,7 +167,7 @@ const EntryListingCardGrid: FC = ({ `, )} style={{ - width, + width: calculatedWidth, height, }} > @@ -165,8 +175,8 @@ const EntryListingCardGrid: FC = ({ columnCount={columnCount} columnWidth={index => index + 1 === columnCount - ? width * columnWidth - : width * columnWidth + COLLECTION_CARD_MARGIN + ? calculatedWidth * columnWidth + : calculatedWidth * columnWidth + COLLECTION_CARD_MARGIN } rowCount={rowCount} rowHeight={index => { @@ -189,7 +199,7 @@ const EntryListingCardGrid: FC = ({ return rowHeight; }} - width={width} + width={calculatedWidth} height={height} itemData={ { @@ -203,7 +213,7 @@ const EntryListingCardGrid: FC = ({ onScroll={onScroll} className={classNames( ` - overflow-hidden + !overflow-x-hidden overflow-y-auto styled-scrollbars `, diff --git a/packages/core/src/components/collections/entries/EntryListingGrid.tsx b/packages/core/src/components/collections/entries/EntryListingGrid.tsx index 409afabe..3cab79ea 100644 --- a/packages/core/src/components/collections/entries/EntryListingGrid.tsx +++ b/packages/core/src/components/collections/entries/EntryListingGrid.tsx @@ -57,8 +57,8 @@ const EntryListingGrid: FC = ({ }, [handleScroll]); return ( -
-
+
+
= ({
{t('collection.entries.loadingEntries')}
diff --git a/packages/core/src/components/collections/entries/EntryRow.tsx b/packages/core/src/components/collections/entries/EntryRow.tsx index 668c5197..d37f1fb3 100644 --- a/packages/core/src/components/collections/entries/EntryRow.tsx +++ b/packages/core/src/components/collections/entries/EntryRow.tsx @@ -80,6 +80,7 @@ const EntryRow: FC> = ({ hover:bg-gray-200 dark:hover:bg-slate-700/70 " + to={path} > {collectionLabel ? ( diff --git a/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx b/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx new file mode 100644 index 00000000..32127532 --- /dev/null +++ b/packages/core/src/components/collections/mobile/MobileCollectionControls.tsx @@ -0,0 +1,40 @@ +import { FilterList as FilterListIcon } from '@styled-icons/material/FilterList'; +import React, { useCallback, useState } from 'react'; + +import IconButton from '../../common/button/IconButton'; +import MobileCollectionControlsDrawer from './MobileCollectionControlsDrawer'; + +import type { FC } from 'react'; +import type { FilterControlProps } from '../FilterControl'; +import type { GroupControlProps } from '../GroupControl'; +import type { SortControlProps } from '../SortControl'; + +export type MobileCollectionControlsProps = Omit & + Omit & + Omit & { + showGroupControl: boolean; + showFilterControl: boolean; + showSortControl: boolean; + }; + +const MobileCollectionControls: FC = props => { + const [mobileOpen, setMobileOpen] = useState(false); + const toggleMobileMenu = useCallback(() => { + setMobileOpen(old => !old); + }, []); + + return ( + <> + + + + + + ); +}; + +export default MobileCollectionControls; diff --git a/packages/core/src/components/collections/mobile/MobileCollectionControlsDrawer.tsx b/packages/core/src/components/collections/mobile/MobileCollectionControlsDrawer.tsx new file mode 100644 index 00000000..d43500cf --- /dev/null +++ b/packages/core/src/components/collections/mobile/MobileCollectionControlsDrawer.tsx @@ -0,0 +1,117 @@ +import SwipeableDrawer from '@mui/material/SwipeableDrawer'; +import React, { useMemo } from 'react'; + +import FilterControl from '../FilterControl'; +import GroupControl from '../GroupControl'; +import SortControl from '../SortControl'; + +import type { FilterControlProps } from '../FilterControl'; +import type { GroupControlProps } from '../GroupControl'; +import type { SortControlProps } from '../SortControl'; + +const DRAWER_WIDTH = 240; + +export type MobileCollectionControlsDrawerProps = Omit & + Omit & + Omit & { + mobileOpen: boolean; + onMobileOpenToggle: () => void; + } & { + showGroupControl: boolean; + showFilterControl: boolean; + showSortControl: boolean; + }; + +const MobileCollectionControlsDrawer = ({ + mobileOpen, + onMobileOpenToggle, + showFilterControl, + filter, + viewFilters, + onFilterClick, + showGroupControl, + group, + viewGroups, + onGroupClick, + showSortControl, + sort, + fields, + onSortClick, +}: MobileCollectionControlsDrawerProps) => { + const iOS = useMemo( + () => typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent), + [], + ); + + const container = useMemo( + () => (typeof window !== 'undefined' ? window.document.body : undefined), + [], + ); + + return ( + +
+ {showSortControl ? ( + + ) : null} + {showFilterControl ? ( + + ) : null} + {showGroupControl ? ( + + ) : null} +
+
+ ); +}; + +export default MobileCollectionControlsDrawer; diff --git a/packages/core/src/components/common/autocomplete/Autocomplete.tsx b/packages/core/src/components/common/autocomplete/Autocomplete.tsx index bba78625..fa09cee4 100644 --- a/packages/core/src/components/common/autocomplete/Autocomplete.tsx +++ b/packages/core/src/components/common/autocomplete/Autocomplete.tsx @@ -63,7 +63,8 @@ const Autocomplete = function (
{children} diff --git a/packages/core/src/components/common/checkbox/Checkbox.tsx b/packages/core/src/components/common/checkbox/Checkbox.tsx index 1bef569a..8058742f 100644 --- a/packages/core/src/components/common/checkbox/Checkbox.tsx +++ b/packages/core/src/components/common/checkbox/Checkbox.tsx @@ -52,7 +52,7 @@ const Checkbox: FC = ({ checked, disabled = false, onChange }) => ref={inputRef} type="checkbox" checked={checked} - className="sr-only peer" + className="sr-only peer hide-tap" disabled={disabled} onChange={onChange} onClick={handleNoop} diff --git a/packages/core/src/components/common/field/Field.tsx b/packages/core/src/components/common/field/Field.tsx index a61c3a08..149c8de6 100644 --- a/packages/core/src/components/common/field/Field.tsx +++ b/packages/core/src/components/common/field/Field.tsx @@ -23,6 +23,8 @@ export interface FieldProps { disabled: boolean; disableClick?: boolean; endAdornment?: ReactNode; + rootClassName?: string; + wrapperClassName?: string; } const Field: FC = ({ @@ -39,6 +41,8 @@ const Field: FC = ({ disabled, disableClick = false, endAdornment, + rootClassName, + wrapperClassName, }) => { const finalCursor = useCursor(cursor, disabled); @@ -104,6 +108,7 @@ const Field: FC = ({ focus-within:border-blue-800 dark:focus-within:border-blue-100 `, + rootClassName, !noHightlight && !disabled && ` @@ -118,7 +123,7 @@ const Field: FC = ({ finalCursor === 'default' && 'cursor-default', !hasErrors && 'group/active', ), - [finalCursor, disabled, hasErrors, noHightlight, noPadding], + [rootClassName, noHightlight, disabled, noPadding, finalCursor, hasErrors], ); const wrapperClassNames = useMemo( @@ -129,9 +134,10 @@ const Field: FC = ({ flex-col w-full `, + wrapperClassName, forSingleList && 'mr-14', ), - [forSingleList], + [forSingleList, wrapperClassName], ); if (variant === 'inline') { diff --git a/packages/core/src/components/common/menu/Menu.tsx b/packages/core/src/components/common/menu/Menu.tsx index d7fd1259..a605627d 100644 --- a/packages/core/src/components/common/menu/Menu.tsx +++ b/packages/core/src/components/common/menu/Menu.tsx @@ -16,9 +16,14 @@ export interface MenuProps { color?: BaseBaseProps['color']; size?: BaseBaseProps['size']; rounded?: boolean | 'no-padding'; - className?: string; + rootClassName?: string; + iconClassName?: string; + buttonClassName?: string; + labelClassName?: string; children: ReactNode | ReactNode[]; hideDropdownIcon?: boolean; + hideDropdownIconOnMobile?: boolean; + hideLabel?: boolean; keepMounted?: boolean; disabled?: boolean; 'data-testid'?: string; @@ -31,9 +36,14 @@ const Menu = ({ color = 'primary', size = 'medium', rounded = false, - className, + rootClassName, + iconClassName, + buttonClassName, + labelClassName, children, hideDropdownIcon = false, + hideDropdownIconOnMobile = false, + hideLabel = false, keepMounted = false, disabled = false, 'data-testid': dataTestId, @@ -56,16 +66,16 @@ const Menu = ({ setAnchorEl(null); }, []); - const buttonClassName = useButtonClassNames(variant, color, size, rounded); + const calculatedButtonClassName = useButtonClassNames(variant, color, size, rounded); const menuButtonClassNames = useMemo( - () => classNames(className, buttonClassName), - [buttonClassName, className], + () => classNames(calculatedButtonClassName, buttonClassName, 'whitespace-nowrap'), + [calculatedButtonClassName, buttonClassName], ); return ( -
+
= ({ px-3 py-1 rounded-lg + truncate `, noWrap && 'whitespace-nowrap', colorClassNames, diff --git a/packages/core/src/components/common/select/Select.tsx b/packages/core/src/components/common/select/Select.tsx index e9c819d2..bf3cfb70 100644 --- a/packages/core/src/components/common/select/Select.tsx +++ b/packages/core/src/components/common/select/Select.tsx @@ -59,7 +59,6 @@ const Select = forwardRef( }, [onOpenChange], ); - const handleButtonClick = useCallback(() => handleOpenChange(!open), [handleOpenChange, open]); const handleChange = useCallback( (_event: MouseEvent | KeyboardEvent | FocusEvent | null, selectedValue: number | string) => { @@ -89,8 +88,10 @@ const Select = forwardRef( renderValue={() => { return ( - <> - {label ?? placeholder} +
+
+ {label ?? placeholder} +
- +
); }} slotProps={{ root: { ref, - onClick: handleButtonClick, className: classNames( ` flex @@ -160,11 +160,11 @@ const Select = forwardRef( ring-opacity-5 focus:outline-none sm:text-sm - z-50 + z-[100] dark:bg-slate-700 dark:shadow-lg `, - style: { width }, + style: { width: ref ? width : 'auto' }, disablePortal: false, }, }} diff --git a/packages/core/src/components/common/table/TableCell.tsx b/packages/core/src/components/common/table/TableCell.tsx index 119d60bb..c35af6cb 100644 --- a/packages/core/src/components/common/table/TableCell.tsx +++ b/packages/core/src/components/common/table/TableCell.tsx @@ -26,6 +26,7 @@ const TableCell = ({ children, emphasis = false, to, shrink = false }: TableCell py-3 whitespace-nowrap " + tabIndex={-1} > {children} @@ -50,6 +51,8 @@ const TableCell = ({ children, emphasis = false, to, shrink = false }: TableCell
{content} diff --git a/packages/core/src/components/common/table/TableHeaderCell.tsx b/packages/core/src/components/common/table/TableHeaderCell.tsx index 18c297af..f0cacad7 100644 --- a/packages/core/src/components/common/table/TableHeaderCell.tsx +++ b/packages/core/src/components/common/table/TableHeaderCell.tsx @@ -35,6 +35,8 @@ const TableHeaderCell = ({ children }: TableHeaderCellProps) => { dark:border-gray-700 dark:bg-slate-800 text-[14px] + truncate + w-full " > {typeof children === 'string' && isEmpty(children) ? <>  : children} diff --git a/packages/core/src/components/common/table/TableRow.tsx b/packages/core/src/components/common/table/TableRow.tsx index 336fe35d..6e430fc7 100644 --- a/packages/core/src/components/common/table/TableRow.tsx +++ b/packages/core/src/components/common/table/TableRow.tsx @@ -1,15 +1,31 @@ -import React from 'react'; +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import classNames from '@staticcms/core/lib/util/classNames.util'; -import type { ReactNode } from 'react'; +import type { KeyboardEvent, ReactNode } from 'react'; interface TableRowProps { children: ReactNode; className?: string; + to?: string; } -const TableRow = ({ children, className }: TableRowProps) => { +const TableRow = ({ children, className, to }: TableRowProps) => { + const navigate = useNavigate(); + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (!to) { + return; + } + + if (event.key === 'Enter' || event.key === 'Space') { + navigate(to); + } + }, + [navigate, to], + ); + return ( { hover:bg-slate-50 dark:bg-slate-800 dark:hover:bg-slate-700 + focus:outline-none + focus:bg-gray-100 + focus:dark:bg-slate-700 `, className, )} + tabIndex={to ? 0 : -1} + onKeyDown={handleKeyDown} > {children} diff --git a/packages/core/src/components/common/text-field/TextField.tsx b/packages/core/src/components/common/text-field/TextField.tsx index 8d724239..7a0929aa 100644 --- a/packages/core/src/components/common/text-field/TextField.tsx +++ b/packages/core/src/components/common/text-field/TextField.tsx @@ -19,6 +19,8 @@ export interface BaseTextFieldProps { placeholder?: string; endAdornment?: ReactNode; startAdornment?: ReactNode; + rootClassName?: string; + inputClassName?: string; } export interface NumberTextFieldProps extends BaseTextFieldProps { @@ -49,6 +51,8 @@ const TextField: FC = ({ onClick, startAdornment, endAdornment, + rootClassName, + inputClassName, ...otherProps }) => { const finalCursor = useCursor(cursor, disabled); @@ -67,10 +71,13 @@ const TextField: FC = ({ endAdornment={endAdornment} slotProps={{ root: { - className: ` - flex - w-full - `, + className: classNames( + ` + flex + w-full + `, + rootClassName, + ), }, input: { ref: inputRef, @@ -79,6 +86,7 @@ const TextField: FC = ({ w-full text-sm `, + inputClassName, variant === 'borderless' && ` h-6 diff --git a/packages/core/src/components/common/view-style/ViewStyleControl.tsx b/packages/core/src/components/common/view-style/ViewStyleControl.tsx index f554bfc8..9c7dc9df 100644 --- a/packages/core/src/components/common/view-style/ViewStyleControl.tsx +++ b/packages/core/src/components/common/view-style/ViewStyleControl.tsx @@ -15,7 +15,7 @@ interface ViewStyleControlPros { const ViewStyleControl = ({ viewStyle, onChangeViewStyle }: ViewStyleControlPros) => { return ( -
+
{ + setShowMobilePreview(old => !old); + }, []); + const editor = (