diff --git a/app/src/App.css b/app/src/App.css index 9c52d444..7dee8bee 100644 --- a/app/src/App.css +++ b/app/src/App.css @@ -4,7 +4,12 @@ .wrolpi-navbar { display: flex; align-items: stretch; - flex-wrap: nowrap; + /* + * Wraps rather than clipping. Semantic's menu wrapped, and with this many items the + * bar overflows a 1000px viewport -- `nowrap` cut "Admin" off the right edge, which + * is the one link a user needs to reach the settings that fix a broken WROLPi. + */ + flex-wrap: wrap; min-height: 3.4em; padding: 0 0.25em; } @@ -20,7 +25,8 @@ .wrolpi-navbar-link-button { display: flex; align-items: center; - padding: 0 1.1em; + /* Tightened from 1.1em: the bar has eleven links plus nine indicators. */ + padding: 0 0.8em; color: inherit; text-decoration: none; white-space: nowrap; @@ -32,7 +38,8 @@ .wrolpi-navbar-link:hover, .wrolpi-navbar-link-button:hover { - background: rgba(0, 0, 0, 0.08); + /* A translucent black works on every navbar colour, light or dark. */ + background: rgba(0, 0, 0, 0.12); } .channel-edit { diff --git a/app/src/App.js b/app/src/App.js index 68d69f8a..1e372a53 100644 --- a/app/src/App.js +++ b/app/src/App.js @@ -5,7 +5,6 @@ import {createBrowserRouter, createRoutesFromElements, Link, Outlet, Route, Rout import {VideosTabLayout, VideosPage, VideosSettingsPage, VideosStatistics, VideoWrapper} from "./components/Videos"; import AdminRoute from "./components/admin/AdminRoute"; import {Box, Header} from "./components/ui"; -import 'semantic-ui-offline/semantic.min.css'; import '@mantine/core/styles.css'; import '@mantine/notifications/styles.css'; // Theme tokens load last so they win over both component libraries. diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 1d79990d..ed62c693 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -175,9 +175,6 @@ export function isoDatetimeToString(dt, time = false) { } export function CardLink({to, newTab = false, ...props}) { - const {t} = useContext(ThemeContext); - props = {...props, ...t}; - props = newTab === true ? {...props, target: '_blank', rel: 'noopener noreferrer'} : props; return {props.children} @@ -185,13 +182,11 @@ export function CardLink({to, newTab = false, ...props}) { } export function ExternalCardLink({to, children, ...props}) { - const {t} = useContext(ThemeContext); return {children} @@ -199,10 +194,9 @@ export function ExternalCardLink({to, children, ...props}) { } export function PreviewLink({file, children, className, ...props}) { - const {t} = useContext(ThemeContext); const {setPreviewFile} = React.useContext(FilePreviewContext); className = className ? `clickable ${className}` : `clickable `; - return setPreviewFile(file)} {...props} {...t}> + return setPreviewFile(file)} {...props}> {children} } diff --git a/app/src/components/Theme.test.js b/app/src/components/Theme.test.js deleted file mode 100644 index 8389e509..00000000 --- a/app/src/components/Theme.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import {Button, Modal, Progress} from './Theme'; -import {renderInDarkMode, renderInLightMode} from '../test-utils'; - -// All semantic-ui colors. -const allColors = ['red', 'orange', 'yellow', 'olive', 'green', 'teal', 'blue', 'violet', 'purple', 'pink', 'brown', 'grey']; - -// Light bar colors that always need dark percent text (light bars in any theme). -const lightBarColors = ['yellow', 'olive', 'teal']; -// Bar colors that need dark percent text only in dark mode (inverted makes bar lighter). -const darkModeLightBarColors = ['grey', 'blue']; - -describe('Button ref forwarding', () => { - // Regression: SButton is a class component, so forwarding a ref straight to it resolves to the class instance - // (no `.contains`). Semantic UI's Modal/Popup Portal calls `node.contains(...)` on the trigger's ref during - // handleDocumentClick, throwing "node.contains is not a function". The forwarded ref must be the DOM - - - - ); -}; - -// ---------------------------------------------------------------------------- -// Form Compound Component -// ---------------------------------------------------------------------------- - -type FormFieldComponent = React.FC; -type FormGroupComponent = React.FC; -type FormInputComponent = React.FC; - -interface FormComponent extends React.FC { - Field: FormFieldComponent; - Group: FormGroupComponent; - Input: FormInputComponent; -} - -const FormField: FormFieldComponent = (props) => { - const {i} = useContext(ThemeContext); - const mergedProps = invertedNull({...i, ...props}); - return -}; - -const FormGroup: FormGroupComponent = (props) => { - const {i} = useContext(ThemeContext); - const mergedProps = invertedNull({...i, ...props}); - return -}; - -const FormInput: FormInputComponent = (props) => { - const {i} = useContext(ThemeContext); - return -}; - -const FormBase: React.FC = (props) => { - const {i} = useContext(ThemeContext); - return -}; - -export const Form: FormComponent = Object.assign(FormBase, { - Field: FormField, - Group: FormGroup, - Input: FormInput, -}); - -// ---------------------------------------------------------------------------- -// Card Compound Component -// ---------------------------------------------------------------------------- - -interface CardIconProps extends HTMLAttributes { - onClick?: () => void; - children?: React.ReactNode; -} - -type CardIconComponent = React.FC; - -interface CardProps extends Omit { - color?: string; -} - -interface CardComponent extends React.FC { - Icon: CardIconComponent; -} - -const CardIcon: CardIconComponent = ({onClick, children, ...props}) => { - const {inverted} = useContext(ThemeContext); - const cardIcon =
{children}
; - if (onClick) { - return
- {cardIcon} -
- } else { - return cardIcon; - } -}; - -const CardBase: React.FC = ({color, ...props}) => { - const {inverted} = useContext(ThemeContext); - - const style: CSSProperties = props.style || {}; - const emphasisColor = ColorToSemanticHexColor(color); - if (emphasisColor) { - // Increase drop shadow to emphasize color. - const borderColor = inverted ? '#888' : '#ddd'; - style.boxShadow = `0 0 0 2px ${borderColor}, 0 5px 0 0 ${emphasisColor}, 0 0px 3px 0 #d4d4d5`; - } - return -}; - -export const Card: CardComponent = Object.assign(CardBase, { - Icon: CardIcon, -}); - -// ---------------------------------------------------------------------------- -// Tab Compound Component -// ---------------------------------------------------------------------------- - -type TabPaneComponent = React.FC; - -interface TabComponent extends React.FC { - Pane: TabPaneComponent; -} - -const TabPane: TabPaneComponent = (props) => { - const {i} = useContext(ThemeContext); - return -}; - -const TabBase: React.FC = (props) => { - const {i} = useContext(ThemeContext); - const mergedProps = invertedNull({...i, ...props}); - return -}; - -export const Tab: TabComponent = Object.assign(TabBase, { - Pane: TabPane, -}); - -// ---------------------------------------------------------------------------- -// Statistic Compound Component -// ---------------------------------------------------------------------------- - -type StatisticGroupComponent = React.FC; - -interface StatisticComponent extends React.FC { - Group: StatisticGroupComponent; -} - -const StatisticGroup: StatisticGroupComponent = (props) => { - const {i} = useContext(ThemeContext); - const style: CSSProperties = {...props.style, marginLeft: 0, marginRight: 0}; - return -}; - -const StatisticBase: React.FC = (props) => { - const {i, inverted} = useContext(ThemeContext); - const mergedProps = defaultGrey({...i, ...props}, !!inverted); - return -}; - -export const Statistic: StatisticComponent = Object.assign(StatisticBase, { - Group: StatisticGroup, -}); - -// ---------------------------------------------------------------------------- -// Breadcrumb Compound Component -// ---------------------------------------------------------------------------- - -interface BreadcrumbDividerProps { - icon?: string; -} - -type BreadcrumbDividerComponent = React.FC; - -interface BreadcrumbComponent extends React.FC { - Divider: BreadcrumbDividerComponent; -} - -const BreadcrumbDivider: BreadcrumbDividerComponent = ({icon, ...props}) => { - const {inverted} = useContext(ThemeContext); - - // TODO this only handles icons for now. - const className = `divider icon ${inverted} ${icon || ''}`; - return -}; - -const BreadcrumbBase: React.FC = (props) => { - const {t} = useContext(ThemeContext); - return -}; - -export const Breadcrumb: BreadcrumbComponent = Object.assign(BreadcrumbBase, { - Divider: BreadcrumbDivider, -}); - -// ---------------------------------------------------------------------------- -// Table Compound Component -// ---------------------------------------------------------------------------- - -type TableBodyComponent = React.FC; -type TableCellComponent = React.FC; -type TableFooterComponent = React.FC; -type TableHeaderComponent = React.FC; -type TableHeaderCellComponent = React.FC; -type TableRowComponent = React.FC; - -interface TableComponent extends React.FC { - Body: TableBodyComponent; - Cell: TableCellComponent; - Footer: TableFooterComponent; - Header: TableHeaderComponent; - HeaderCell: TableHeaderCellComponent; - Row: TableRowComponent; -} - -const TableBody: TableBodyComponent = (props) => { - return -}; - -const TableCell: TableCellComponent = (props) => { - return -}; - -const TableFooter: TableFooterComponent = (props) => { - return -}; - -const TableHeader: TableHeaderComponent = (props) => { - return -}; - -const TableHeaderCell: TableHeaderCellComponent = (props) => { - return -}; - -const TableRow: TableRowComponent = (props) => { - return -}; - -const TableBase: React.FC = (props) => { - const {i} = useContext(ThemeContext); - return -}; - -export const Table: TableComponent = Object.assign(TableBase, { - Body: TableBody, - Cell: TableCell, - Footer: TableFooter, - Header: TableHeader, - HeaderCell: TableHeaderCell, - Row: TableRow, -}); +/* + * The Semantic UI wrappers that used to live below this line are gone. + * + * They existed so call sites could get a themed Button, Segment, Modal and so on while + * the app still rendered Semantic components; every one of those call sites now imports + * from `src/components/ui` instead. The dark-mode compatibility props they consumed -- + * `i`, `s`, `t`, `inverted` -- went with them: those carried hardcoded greys, which is + * precisely what the token themes replaced. + * + * What remains is the part that was never about Semantic: the provider that resolves the + * chosen theme, stamps `data-theme` and `data-media-filter` on , persists both, and + * hands Mantine our tokens. + */ diff --git a/app/src/components/collections/CollectionEditForm.js b/app/src/components/collections/CollectionEditForm.js index f0354cee..f9c4a078 100644 --- a/app/src/components/collections/CollectionEditForm.js +++ b/app/src/components/collections/CollectionEditForm.js @@ -47,50 +47,39 @@ export function CollectionEditForm({
{/* - * `ui stackable grid` replicates Semantic's own Grid CSS classes (still loaded - * globally via semantic-ui-offline while other call sites remain unmigrated). - * Callers of this component (Channels.js, Archive.js, Playlists.js) still pass - * Semantic / children -- they have not migrated to the - * component library yet. This div keeps their layout without importing - * semantic-ui-react here. Replace with the real `Grid`/`Grid.Col` once every - * caller has migrated. - */} + Every caller now passes library components rather than Semantic Grid rows, so + this is a plain flex column instead of the `ui stackable grid` bridge it used + to be. That bridge was the last thing in the app depending on Semantic's + stylesheet. + */}
{children} - {appliedTagName &&
-
- -
-
} + {appliedTagName &&
} -
-
- {actionButtons} - {onCancel && } -
-
- -
+
+ {actionButtons} + {onCancel && } +
diff --git a/app/src/components/ui/ui.css b/app/src/components/ui/ui.css index fbd75c1c..464da8c8 100644 --- a/app/src/components/ui/ui.css +++ b/app/src/components/ui/ui.css @@ -342,6 +342,54 @@ html[data-theme="amber"] .wrolpi-searchbox-control:focus-within { border-top: 1px solid var(--border); } +/* A stack of form rows, replacing Semantic's `ui stackable grid` in edit forms. */ +.wrolpi-form-rows { + display: flex; + flex-direction: column; + gap: 14px; +} + +/* ------------------------------------------------------------- Form fields */ +/* + * `hooks/useForm.js` renders its own - - - +
+ +
{messageElm} } @@ -463,31 +463,36 @@ export function UrlsTextarea({name = 'urls', required, form}) { }; return ( - -