From 7c7f980942d5c35f8c252c4fa6c9aadb26579ff3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Samuseu Date: Thu, 17 Sep 2026 11:59:16 +0200 Subject: [PATCH] EPBDS-16211 H1: delete the dead weight, 26 files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §9.9-H1 has been "cheap, do first" since the plan was written. Doing it. WHAT WENT 12 orphan components (src/core/components/): Avatar, Badge, Carousel, Collapse, ErrorContent, ErrorTable, FloatNumber, ImageSwatch, MenuButton, Square, TabList, Tags. 10 LESS files: the whole src/style/unused/ tree. 4 icomoon demo artifacts: demo.html, demo-files/, "Read Me.txt". formatTime and toHours in src/core/utils/time.js, with the imports that existed only for them. WHAT DELIBERATELY STAYED The ENGINE TabList (pages/main/components/TabList.js) — mapper.js:33 resolves it, and it is a different component from the pack copy: both import './Tabs', and those two Tabs differ by ~100 lines. Swapping them would have silently regressed tab behaviour. The pack Tabs.js, so H6's duplicate pair is only HALF resolved. icomoon's selection.json and style.css — selection.json is the project file you re-upload to regenerate the icon set, the only round-trip path back to it. Dead to every build, but its loss is not recoverable the way the demo page's is. FIELD.TYPE.DATE, which H1 also lists. Deleting it versus giving it a `view: 'Date'` branch is an owners' decision, so it was left alone. ONE DECISION THIS FORCED ErrorTable could not go alone — ErrorContent imported it — and it was also the last in-repo caller passing `inverted`/`striped` to Table. SUPPORTED-PROPS.md said step 1 deferred their fate to exactly this deletion. They are KEPT: both are genuinely styled (table:not(.as-layout).inverted, table.striped tr:nth-child(2n)), Table.js still destructures them, and a consumer meta can still set them. Dropping a supported prop because our own last caller went away would be a breaking change bought for nothing. The generated "Attributes at the call sites" table no longer lists them, which is correct — that table reports what the codebase passes, not what the component accepts — so wrapper-prop-curation.js now says so in as many words. VERIFIED BEFORE DELETING One agent per target, each asked to REFUTE that its target was safe: mapper registration, the components barrel, the published entry, every import spelling, tracked metas, docs, schema, CSS, the sibling checkouts. src/style/unused/ was cleared by compiling index.less through the repo's own less-options and reading less's resolved import list (44 files, none under unused/) rather than by grepping basenames. MEASURED AFTER suite 166 suites, 2539 -> 2527 tests. The -12 is exactly the two time suites (-9, -3); no other suite's count moved. coverage 94.83 -> 97.04 stmts, 90.19 -> 94.06 branch, 93.28 -> 96.88 funcs, 95.36 -> 97.76 lines. Thresholds green. CSS static/all.css sha256 UNCHANGED at dcdb0a40a6d8ee79... gates lint:js, lint:css, css:fixture:check, both docs checks, build-lib, build-css all green. SIDE EFFECTS WORTH NAMING moment is now imported nowhere in src/core/utils/ — progress on §9.7-F2. Workstream A loses 4 class components and 3 UNSAFE_* sites (29/14 -> 26/12). The third function-component defaultProps site is resolved by deletion. The plan is updated in the same commit: 28 places closed or corrected, including the orphan counts, which are now a historical record rather than a list of files to go looking for. --- docs/SUPPORTED-PROPS.md | 18 +- docs/UPGRADE-PLAN.md | 66 +- scripts/wrapper-prop-curation.js | 15 +- src/core/components/Avatar.js | 79 -- src/core/components/Badge.js | 27 - src/core/components/Carousel.js | 122 -- src/core/components/Collapse.js | 94 -- src/core/components/ErrorContent.js | 51 - src/core/components/ErrorTable.js | 66 - src/core/components/FloatNumber.js | 24 - src/core/components/ImageSwatch.js | 39 - src/core/components/MenuButton.js | 37 - src/core/components/Square.js | 142 -- src/core/components/TabList.js | 21 - src/core/components/Tags.js | 48 - .../time.formatting-and-conversion.test.js | 45 +- src/core/utils/__tests__/time.test.js | 23 +- src/core/utils/time.js | 24 - src/style/fonts/icons/Read Me.txt | 7 - src/style/fonts/icons/demo-files/demo.css | 155 --- src/style/fonts/icons/demo-files/demo.js | 30 - src/style/fonts/icons/demo.html | 1231 ----------------- src/style/unused/_classic.less | 19 - src/style/unused/_classic_variables.less | 74 - src/style/unused/_policy.less | 11 - src/style/unused/_policy_variables.less | 0 src/style/unused/avatar.less | 153 -- src/style/unused/carousel.less | 119 -- src/style/unused/chat.less | 308 ----- src/style/unused/placeholder.less | 47 - src/style/unused/reviews.less | 55 - src/style/unused/video.less | 61 - 32 files changed, 61 insertions(+), 3150 deletions(-) delete mode 100644 src/core/components/Avatar.js delete mode 100644 src/core/components/Badge.js delete mode 100644 src/core/components/Carousel.js delete mode 100644 src/core/components/Collapse.js delete mode 100644 src/core/components/ErrorContent.js delete mode 100644 src/core/components/ErrorTable.js delete mode 100644 src/core/components/FloatNumber.js delete mode 100644 src/core/components/ImageSwatch.js delete mode 100644 src/core/components/MenuButton.js delete mode 100644 src/core/components/Square.js delete mode 100644 src/core/components/TabList.js delete mode 100644 src/core/components/Tags.js delete mode 100644 src/style/fonts/icons/Read Me.txt delete mode 100644 src/style/fonts/icons/demo-files/demo.css delete mode 100644 src/style/fonts/icons/demo-files/demo.js delete mode 100644 src/style/fonts/icons/demo.html delete mode 100644 src/style/unused/_classic.less delete mode 100644 src/style/unused/_classic_variables.less delete mode 100644 src/style/unused/_policy.less delete mode 100644 src/style/unused/_policy_variables.less delete mode 100644 src/style/unused/avatar.less delete mode 100644 src/style/unused/carousel.less delete mode 100644 src/style/unused/chat.less delete mode 100644 src/style/unused/placeholder.less delete mode 100644 src/style/unused/reviews.less delete mode 100644 src/style/unused/video.less diff --git a/docs/SUPPORTED-PROPS.md b/docs/SUPPORTED-PROPS.md index e2fe9ea1..d9492acb 100644 --- a/docs/SUPPORTED-PROPS.md +++ b/docs/SUPPORTED-PROPS.md @@ -95,8 +95,8 @@ CSS contract: `src/style/components/table.less` hangs EVERY cell's padding off ` | Prop | Meaning | | --- | --- | | `className` | Appended last, after `table`. `TableView` builds it from the meta `styles`/`fill`/`vertical` attributes; consumer metas add `as-layout`, `no-header`, `highlight-N-last` and the sticky-column tokens through the same channel. | -| `inverted` | Dark table. Emitted as the `inverted` class, which `table.less` and `expand.less` both select on. Reached only from `ErrorTable.js` — a §9.9-H1 orphan, so the prop is kept but its fate is that deletion's to decide, not this step's. | -| `striped` | Zebra rows, emitted as the `striped` class. Same single call site as `inverted`, and also genuinely styled — which is why neither was dropped with the rest. | +| `inverted` | Dark table. Emitted as the `inverted` class, which `table.less` and `expand.less` both select on. **KEPT — decided at §9.9-H1 (2026-09-17), which is where step 1 left the call.** Its one in-repo caller was `ErrorTable.js`, and H1 deleted it, so the "Attributes at the call sites" table above no longer lists this prop — that table reports what the CODEBASE passes, not what the component accepts. The prop itself is untouched: `Table.js` still destructures it and still emits the class, the CSS still selects on it, and a consumer meta can still set it. Dropping it because our own last caller went away would have been a breaking change bought for nothing. | +| `striped` | Zebra rows, emitted as the `striped` class. Same story as `inverted` exactly: genuinely styled (`table.striped tr:nth-child(2n)`), which is why neither was dropped with the rest, and still accepted after H1 removed their shared call site. | **Consumed by every subcomponent (1).** All 6 share one implementation, so this list applies to each of them identically. @@ -108,7 +108,7 @@ CSS contract: `src/style/components/table.less` hangs EVERY cell's padding off ` **Passthrough.** `style`, `colSpan`, `scope`, `id`, `data-*` and every event handler still reach the element untouched — they always did, because Semantic did not handle them either, so they ride the rest spread exactly as before. There is no `forwardRef`: nothing in `src` passes a ref to a table element, so the parameter would have had no caller. -**Dropped (4) — the semver record.** Props semantic-ui-react handled that this implementation deliberately does not. All of them remain REACHABLE from a consumer meta: the component is rendered with open spreads (`...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...props`, `...rest`), so an attribute nobody anticipated on a meta node still arrives here as a prop. That is why the component strips them explicitly and warns once per prop in development. Stripping matters because the value would otherwise reach a real element as an attribute — a string-valued one lands lowercase (`verticalAlign="top"` rendered `verticalalign="top"`) and a boolean draws React's "Received `true` for a non-boolean attribute" warning, both of them junk the DOM contract's tripwires exist to keep out. Warning matters because a meta still carrying one would otherwise never learn it stopped working, and React's own unknown-prop warning cannot be relied on: it is silent for a lowercase name. +**Dropped (4) — the semver record.** Props semantic-ui-react handled that this implementation deliberately does not. All of them remain REACHABLE from a consumer meta: the component is rendered with open spreads (`...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...rest`), so an attribute nobody anticipated on a meta node still arrives here as a prop. That is why the component strips them explicitly and warns once per prop in development. Stripping matters because the value would otherwise reach a real element as an attribute — a string-valued one lands lowercase (`verticalAlign="top"` rendered `verticalalign="top"`) and a boolean draws React's "Received `true` for a non-boolean attribute" warning, both of them junk the DOM contract's tripwires exist to keep out. Warning matters because a meta still carrying one would otherwise never learn it stopped working, and React's own unknown-prop warning cannot be relied on: it is silent for a lowercase name. | Prop | Why it is gone | | --- | --- | @@ -123,12 +123,12 @@ Those four were the *published* ones — they had curated entries on this page w | Component | Attributes at the call sites | Spreads | Rendered by | | --- | --- | --- | --- | -| `Table` | `className`, `inverted`, `striped` | `...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...props` | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` | -| `Table.Header` | `className` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` | -| `Table.HeaderCell` | `className`, `colSpan`, `key`, `style` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` | -| `Table.Row` | `className`, `key` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` | -| `Table.Cell` | `className`, `colSpan`, `key`, `scope`, `style` | `...rest` | `core/components/ErrorTable.js`, `core/pages/main/components/LocalDraftTableRow.js`, `core/pages/main/components/TableView.js`, `core/pages/main/mapper.js` | -| `Table.Body` | — | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` | +| `Table` | `className` | `...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)` | `core/pages/main/components/TableView.js` | +| `Table.Header` | `className` | — | `core/pages/main/components/TableView.js` | +| `Table.HeaderCell` | `className`, `colSpan`, `key`, `style` | — | `core/pages/main/components/TableView.js` | +| `Table.Row` | `className`, `key` | — | `core/pages/main/components/TableView.js` | +| `Table.Cell` | `className`, `colSpan`, `key`, `style` | `...rest` | `core/pages/main/components/LocalDraftTableRow.js`, `core/pages/main/components/TableView.js`, `core/pages/main/mapper.js` | +| `Table.Body` | — | — | `core/pages/main/components/TableView.js` | | `Table.Footer` | — | — | *nothing* | `mapper.js`'s spread onto `Table.Cell` is a meta node's whole rest bag and is still unfiltered at the call site — the filter is now inside the cell, which is why it is safe. All three unfiltered boundaries on this surface are now closed inside the component: the table cell at step 1, the tooltip at step 2 part 3, and the dropdown at step 3 part 2, which strips twice — once in the wrapper and once in `Listbox` at the element. diff --git a/docs/UPGRADE-PLAN.md b/docs/UPGRADE-PLAN.md index 8be5a614..c7dfdc86 100644 --- a/docs/UPGRADE-PLAN.md +++ b/docs/UPGRADE-PLAN.md @@ -29,7 +29,7 @@ Standing decisions: 2. **`moment` stays** as a peer dependency — no dayjs migration. The requested native-replacement feasibility analysis (§9.7-F2) concludes it is possible and well-bounded (~400–600 lines behind an adapter seam), but it is parked behind an explicit decision gate; the only near-term action is funneling usage through a single internal adapter module. 3. **The published JavaScript API stays a single callable value.** `require('eis-ui-render')` returns the function directly (there is no runtime `.default` or `.UIRender` property); TypeScript models this with `export =`, which also supports a default import when module interop is enabled. Named interfaces and type aliases remain available through the merged namespace, but a named `UIRender` value export is not restored. Script-tag/global typing remains tied to the separate UMD-support gate in §10. -The **project-structure analysis** (§9.9) found: the documented `ui-*-pack` alias system is dead (zero imports in the codebase — CLAUDE.md was stale on this; fixed alongside this plan), the engine lives under an app-boilerplate-era `core/pages/main/` path, and 12 orphan components (9 direct, plus the `ErrorTable`/`Square`/pack-`TabList` cluster reachable only from other orphans) plus a dead `style/unused/` tree can simply be deleted. The workstream re-homes the engine to `core/engine/` ahead of the §9.3 decomposition, isolates the demo, and locks layer direction in with lint. +The **project-structure analysis** (§9.9) found: the documented `ui-*-pack` alias system is dead (zero imports in the codebase — CLAUDE.md was stale on this; fixed alongside this plan), the engine lives under an app-boilerplate-era `core/pages/main/` path, and 12 orphan components (9 direct, plus the `ErrorTable`/`Square`/pack-`TabList` cluster reachable only from other orphans) plus a dead `style/unused/` tree ~~can simply be deleted~~ **— deleted 2026-09-17 (§9.9-H1).** Those files are no longer in the tree, so the counts here are a historical record rather than files to go looking for; the engine `TabList` under `core/pages/main/` survives (the mapper resolves it) and the pack `Tabs.js` is still present, so the pack/engine duplication is only half resolved. The workstream re-homes the engine to `core/engine/` ahead of the §9.3 decomposition, isolates the demo, and locks layer direction in with lint. §9.6 defines a **full TypeScript migration** (infra step → utils/contract → components/modules riding other workstreams → engine last → guarded public-API switchover), which **retires the `prop-types` runtime dependency** as its exit criterion (E5 — React 19 ignores propTypes anyway, and the shapes currently ship in the production bundle). The plan also adds **Appendix C** — a consolidated checklist of every verification it depends on, so no check lives only in review discussions. @@ -135,7 +135,7 @@ A second audit pass — against the working tree, the build configs, a full test | 11 | **Hybrid dependency model:** everything except `react`/`react-dom`/`moment` is **both** bundled into the UMD **and** declared in `dependencies` — npm hosts install full copies of the form stack, `rc-picker` etc. that the bundle never uses. **The SUIR half is closed:** `semantic-ui-react` was the entry whose own `react ≤^18` peer capped the host's React, and §9.7-F1 step 3½ removed it. The pattern itself remains for the other packages, and ~~`react-final-form` 6.5.9 is now the only remaining `react ≤^18` peer~~ — **inventory corrected 2026-09-17: there are two, not one.** A scan of every installed package's `peerDependencies.react` finds both `react-final-form` 6.5.9 and `react-final-form-arrays` 3.1.4 declaring the same `^16.8.0` / `^17.0.0` / `^18.0.0` cap, and both are in our own `dependencies`. Every other react peer in the tree is open-ended (`rc-picker` and the `rc-*` packages `>=16.9.0`; `react-markdown` `>=16` and `react-syntax-highlighter` `>= 0.14.0`, which are devDependencies anyway). The remediation is unchanged — §9.7-F4 bumps the final-form packages together — but the same off-by-one wording stands at §8-FP2a and in the `react-19-advisory` comment in `ci.yml`, and both need the second package added. | §9.7-F1 step 3½ — done, §9.7-F3 gate, §8 | | 12 | **moment IS on the public API.** `README:12,18` and `docs.md:24–25` document that the library "accepts `moment` instances on its API" (with cross-copy `instanceof` caveats), and rc-picker callbacks leak moment objects outward — `onSelect` ← `onCalendarChange` (`InputDate.js:96`) plus passthrough `disabledDate`/`cellRender`/… via `{...props}` (`InputDate.js:89`). The earlier "moment never crosses the public API" claim was wrong; corrected in §9.7-F2. | §9.7-F2 | | 13 | **Quality baselines measured (2026-07-21):** tests 76 suites / 1215 tests green in 8.3 s (zero snapshots); `lint:css` clean; `npx eslint src` fails (11 errors / 28 warnings; no lint script exists); `npm audit --omit=dev` **0** vulnerabilities; full `npm audit` **20** (2 critical, 7 high — dev tooling); **13 devDependencies with zero references** in code/configs: `tsconfig-paths-webpack-plugin`, `backoff`, `history`, `html-loader`, `minimist`, `path-browserify`, `postcss-scss`, `raw-loader`, `remark-loader`, `rimraf`, `sass`, `sass-loader`, `webpack-node-externals` (verify `dot-prop-immutable` too). | Phase 0.9, §9.9-H1 | -| 14 | **Orphan set is 12, not 9:** the 9 direct orphans re-confirmed, plus `ErrorTable` (imported only by orphan `ErrorContent`), `Square` (only by orphan `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). ~~Engine `tester/` fixtures are referenced by nothing.~~ — the `tester/` pair has since been deleted (§9.9-H1). | §9.9-H1, §9.2 | +| 14 | ~~**Orphan set is 12, not 9:**~~ **CLOSED 2026-09-17 (§9.9-H1).** All 12 are deleted from `src/core/components/` (`Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `ErrorTable`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Square`, `TabList`, `Tags`) — the count is a historical record now, not a set of files to look for; `ErrorContent` imported `ErrorTable`, so those two had to go together. The engine `TabList` (`src/core/pages/main/components/TabList.js`) is untouched and is still what `mapper.js:33` resolves, and the pack `Tabs.js` survives, so the pack/engine duplication is only half resolved. Deleting `ErrorTable` also removed the only in-repo call site of `Table`'s `inverted`/`striped`; the H1 decision was to **keep** both props — `Table.js` still destructures them, the CSS still selects on them, and consumer meta can still set them — they merely no longer appear in the generated "Attributes at the call sites" table in `docs/SUPPORTED-PROPS.md`, which reports what the codebase passes, not what the component accepts. The finding as it stood: the 9 direct orphans re-confirmed, plus `ErrorTable` (imported only by orphan `ErrorContent`), `Square` (only by orphan `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). ~~Engine `tester/` fixtures are referenced by nothing.~~ — the `tester/` pair has since been deleted (§9.9-H1). | §9.9-H1, §9.2 | | 15 | **Hardcoded version strings** `data-version="0.34.2"` in `AppWrapper.js:10` and `types/UIRender.tsx:76` — drift on every release; should come from `package.json` at build time. | §9.9-H6 | | 16 | **One application's field names were compiled into the engine.** *Resolved.* When a popup could not resolve its `relativePath`, `rules.js` probed the consumer's data for two literal paths belonging to a single host app and adopted whichever matched. It never matched in 1928 tests — but for data that *did* carry one of those keys it bound the popup to a table the opener had nothing to do with (the two candidates were tried in order, so a row from the second table got the first), re-creating by data shape the rebinding that commit `89bac56` removed. Deleted; an unresolved scope now warns instead of guessing, and meta states the scope either by declaring the `Popup` inside the row or via `{relativePath}` in the `popupOpen` args. Three comment/JSDoc examples naming the same fields were neutralised. | §9.3 | | 17 | **The date field is selected by `type`, and its view constant is dead.** `renders.js:38-40` overrides whatever the view switch chose whenever `type === 'date'`, so a date picker is reachable from meta as `{view: 'Input', type: 'date'}` — any `view` the mapper does not claim works, which is why grepping example metas for `"view": "Date"` finds nothing. Meanwhile `FIELD.TYPE.DATE` (`modules/form/constants.js:16`) has **zero readers** — `view: 'Date'` renders the "field does not exist" placeholder. Note `FIELD.RENDER.DATE` (`variables/fields.js:47`) is a different, live thing: a read-only date formatter. Either give the view constant a branch or delete it; the two-`'Date'` split is a docs-truth trap. The rendered picker is an rc-picker text input plus a JS overlay with no native `type="date"` attribute — the reason the §5 overlay QA item exists. | §9.9-H1, §9.9-H2 | @@ -261,8 +261,12 @@ container while the bundled dependencies keep their own native `document` listen (§2.6-17); the rendered DOM is an rc-picker text input plus a JS overlay with no native `type="date"` attribute, which is precisely why this item mattered for React 17. - [x] **Tabs, Collapse, Expand** — tab switch swaps panel content; example rows expand and collapse. - *Carousel autoplay is moot: `Carousel` is not registered in `mapper.js` and is one of the §2.6-14 - orphans slated for deletion under §9.9-H1, so no meta can render it.* + ~~*Carousel autoplay is moot: `Carousel` is not registered in `mapper.js` and is one of the §2.6-14 + orphans slated for deletion under §9.9-H1, so no meta can render it.*~~ **CLOSED 2026-09-17.** The + deletion landed: `src/core/components/Carousel.js` is gone — §9.9-H1 removed all 12 §2.6-14 orphans — + so there is no Carousel left to exercise. The pack `Collapse` (`src/core/components/Collapse.js`) went + in the same batch; it was never registered in `mapper.js` either, and the expand/collapse behaviour + verified here is the live `Expand` / `ExpandList` path, which the deletion did not touch. - [x] **Table: sorting, pagination, inline edit rows** — sorting cycles asc → unsorted → desc on a header whose table declares `sorts` (`TableView.js` gives a header `onClick` only then; the `adminCosts` table inside the `all` example's Admin Expenses section is the one that does), verified by both the @@ -327,7 +331,7 @@ check, ~~and the §10 decision on whether React 17 ships as its own release~~. * - **Steps 1, 3-5 landed together** (they cannot be split — RTL ≥13 requires React ≥18): React and React DOM 18.3.1, RTL 16 with an explicit `@testing-library/dom` 10 peer, `@types/react`/`@types/react-dom` 18, peers widened to `^16.14.0 || ^17.0.0 || ^18.0.0`. The type-consumer matrix was repointed — `@types/react` is 18 now, so the 16 and 17 slots come from locked aliases — and all six interop/CommonJS combinations still pass. - **The RTL 12→16 migration cost two test files, not the 1-2 weeks budgeted here.** Both failures shared the predicted cause: under `createRoot` a state update outside `act()` is no longer flushed before the assertion. `ProgressBar` drove its own `setTimeout` via `jest.runAllTimers()`; `Slider` dispatched a raw `MouseEvent` to reach `onPointerDown`. No product code was involved. **140 suites / 1946 tests, zero `act` warnings.** -- **Step 1's `defaultProps` prediction was right, and scanning jest output would have missed it:** the suites mock `console.error`, so the warnings were swallowed and the output looked clean. A probe with a recording spy showed React 18.3 warns for both `TooltipPop` and `Image`; both moved to default parameters (`Image` now forwards `decoding`/`loading` explicitly). `ImageSwatch` keeps its `defaultProps` — it is an orphan nothing renders, so it cannot warn; deletion is §9.9-H1's job. One test asserted `TooltipPop.defaultProps.delay`, i.e. the mechanism rather than the behaviour, and was rewritten against what Semantic actually receives. +- **Step 1's `defaultProps` prediction was right, and scanning jest output would have missed it:** the suites mock `console.error`, so the warnings were swallowed and the output looked clean. A probe with a recording spy showed React 18.3 warns for both `TooltipPop` and `Image`; both moved to default parameters (`Image` now forwards `decoding`/`loading` explicitly). ~~`ImageSwatch` keeps its `defaultProps` — it is an orphan nothing renders, so it cannot warn; deletion is §9.9-H1's job.~~ **CLOSED 2026-09-17.** §9.9-H1 deleted `src/core/components/ImageSwatch.js`, so that third `defaultProps` site was resolved by deletion rather than conversion; the sentence as it stood on 2026-08-20 is struck through. One test asserted `TooltipPop.defaultProps.delay`, i.e. the mechanism rather than the behaviour, and was rewritten against what Semantic actually receives. - **Step 2 done:** `src/main.jsx` mounts via `createRoot`, so the demo renders with automatic batching rather than 17-compat mode. - **Step 6, the batching pass, found no regression.** The unit suite now runs under `createRoot` (batching in effect) and is green. In the browser, on the demo: the form flow — date picker overlay, validation clearing, both submit branches, `addData` committing a row while the draft clears, `removeData` taking the right row — all behave as they did on 17; and the cascading Select, the most batching-sensitive path in the library because its reset calls `onChange` from inside an effect, correctly reset Product from `Alpha` to `Delta` when Category changed, with the dependent table following. No console errors. `flushSync` was not needed anywhere. - **Step 7:** the risky interactive views (Dropdown, Popup, rc-picker, Tabs, Table sorting/pagination/inline edit) were exercised on 18 through the §5 checklist. The two console warnings the demo does emit are React Router v6 future-flag notices — demo-only, unrelated to React 18. @@ -348,15 +352,15 @@ check, ~~and the §10 decision on whether React 17 ships as its own release~~. * *Console volume is small and misleading.* React aggregates per lifecycle kind and dedupes per component, so the demo produced **one** warning (`Expand, Tabs`) and a form-heavy meta produced **three** (one per lifecycle: `UNSAFE_componentWillReceiveProps` naming `Expand, InputNative, TableView, Tabs, UIRender, WithForm`; `UNSAFE_componentWillMount` and `UNSAFE_componentWillUpdate` naming `UIRender`). The count to plan against is the components, not the messages. -*The work item is 29 `UNSAFE_*` call sites across 14 files*, one of which (`Carousel`) is an orphan that cannot warn and is deleted under §9.9-H1: +~~*The work item is 29 `UNSAFE_*` call sites across 14 files*, one of which (`Carousel`) is an orphan that cannot warn and is deleted under §9.9-H1:~~ **UPDATED 2026-09-17** — §9.9-H1 landed, so the two orphan rows below no longer exist in the tree. *The work item is now 26 `UNSAFE_*` call sites across 12 files* (`Carousel`'s 1 site and `Collapse`'s 2 went with the deleted files; the pre-deletion count is struck through above): | Sites | File | Note | |---|---|---| | 12 | `pages/main/rules.js` | the engine, including the prototype-patching machinery — §9.3 step 5 is exactly this | | 4 | `modules/form/utils.js` | `WithForm` plus patched lifecycles | -| 2 | `components/Collapse.js` | | +| ~~2~~ 0 | ~~`components/Collapse.js`~~ | **DELETED 2026-09-17** under §9.9-H1 — orphan, never registered in `mapper.js`; these 2 sites no longer exist. | | 1 each | `pages/main/components/{Tabs,TableView}.js`, `modules/form/views/AutoSave.js`, `components/utils/interactions.js`, `components/{Tabs,ProgressSteps,ProgressBar,InputNative,Expand,Counter}.js` | leaf props→state derivations, mechanically convertible (§2.3) | -| 1 | `components/Carousel.js` | orphan — not registered in `mapper.js`, deleted under H1 | +| ~~1~~ 0 | ~~`components/Carousel.js`~~ | orphan — not registered in `mapper.js`; **DELETED 2026-09-17** under §9.9-H1, so this site no longer exists. | Six of those components were observed warning in practice (`Expand`, `Tabs`, `InputNative`, `TableView`, `UIRender`, `WithForm`); the rest are live code that simply needs the right prop change to surface. Outside StrictMode, React 18.3 emits **zero** warnings for any of them — the renamed `UNSAFE_*` forms are fully supported, which is why the upgrade did not need this work. @@ -372,7 +376,7 @@ Sequencing: StrictMode becomes the *acceptance criterion* of workstream §9.3 (e | Legacy context (removed in 19) | ✅ none | Zero `contextTypes` declarations. **Observed side effect of the removal (2026-08-20):** React 18 invoked every function component as `Component(props, secondArg)`, where `secondArg` was the frozen `emptyContextObject` (`{}`) returned by `getMaskedContext`; 19 passes literal `void 0`. Impact was **test-only** — four assertion sites across three test files pinned that second argument with a trailing `expect.anything()` (the other two of the five suites failing on 19 were the warning-text ones in the row below). All are now version-agnostic: assert `mock.calls[0][0]`, never the positional pair, because `toHaveBeenCalledWith(props)` fails on **arity** against a real `(props, undefined)` call. No `src/` code reads a second parameter, and the `key={typeof index !== 'object' ? index : undefined}` guard at `src/core/ui-render/Render.js:47` exists precisely to keep that legacy `{}` out of the `key` slot when React (rather than a mapper) calls `Render`. | | `findDOMNode` (removed in 19) | ✅ none in `src/` | Moot for SUIR once §9.7-F1 steps 1–3 land. | | `ReactDOM.render` (removed in 19) | 🔶 fixed by Phase 2 | Demo entry only. | -| `defaultProps` on function components (ignored in 19) | 🔶 3 occurrences | `TooltipPop.js:23`, `ImageSwatch.js:27`, `Image.js:27` → convert to default parameters (done in Phase 2 — §6 step 1). `ImageSwatch` is an orphan slated for deletion (§9.9-H1). | +| `defaultProps` on function components (ignored in 19) | ~~🔶 3 occurrences~~ ☑ **none left — CLOSED 2026-09-17** | `TooltipPop.js:23`, `ImageSwatch.js:27`, `Image.js:27` → convert to default parameters (done in Phase 2 — §6 step 1). ~~`ImageSwatch` is an orphan slated for deletion (§9.9-H1).~~ **CLOSED 2026-09-17:** `TooltipPop` and `Image` were converted in Phase 2 and `src/core/components/ImageSwatch.js` was deleted by §9.9-H1, so none of the three sites survive — the file list above is the historical record of where they were. | | `propTypes` (validation removed entirely in 19) | 🔶 40 importing files | No crash — silently ignored. Fully resolved by §9.6-E5: propTypes are deleted per TS conversion and the `prop-types` dependency is removed at the end. **Observed on 19:** the two `is marked as required` warnings that 18 emits for `formProps` and `instance` (attached by `withFormSetup`, `src/core/modules/form/utils.js:483–484`) simply vanish. They report a **real unmet prop contract** that 19 silences — the disappearance is loss of a diagnostic, not a fix, so E5 must not read it as the contract being met. | | Dev warning text changed in 19 (no component stack, no `Warning:` prefix) | ~~🔶 **new finding**~~ ☑ **CLOSED 2026-09-17.** Match-on-message-body is implemented and documented at both console probes: `src/demo/pages/__tests__/Examples.registry-and-rendering.test.js` (comment above `EXPECTED_CONSOLE_ERROR_PATTERNS`; all 9 allowlist entries are message-body fragments) and `src/demo/pages/__tests__/NestedDataKind.interactions.test.js`. | React 18's dev `printWarning` appended `getStackAddendum()` as an extra `%s` argument and prefixed `"Warning: "`; 19 emits the bare message. Consequence: any assertion — or console-probe allowlist — that matches a **component name or file path inside warning text** changes meaning across versions and is unreliable as a gate; match the message body only. This is the same recording-spy technique that caught the `defaultProps` warnings in Phase 2 (§6 step 1 notes), so it applies to the audit method, not just to committed tests. | | `UNSAFE_*` lifecycles | ✅ still supported in 19 | But StrictMode-hostile; §9.3 is the prerequisite for StrictMode/concurrent adoption on 19, not for the 19 flip itself. | @@ -407,7 +411,7 @@ Every workstream below is a series of small, independently shippable, reversible ### 9.2 Workstream A — Class → hooks migration (leaf-first) -**Motivation:** 21 class components, most carrying `UNSAFE_componentWillReceiveProps` that is a simple props→state derivation. Hooks versions are smaller, StrictMode-safe, and testable. +**Motivation:** ~~21 class components~~, most carrying `UNSAFE_componentWillReceiveProps` that is a simple props→state derivation. Hooks versions are smaller, StrictMode-safe, and testable. **Count superseded 2026-09-17 (§9.9-H1):** the deletion took **4 class components** out of this population — `Carousel`, `Collapse`, and the *two* declared in `Square.js` — so the figure above predates it. The exact replacement number is deliberately not written here: "21" was measured with a methodology this plan never recorded (the tree then held 39 `class … extends Component` declarations across 24 files, 35 across 21 files now), so re-derive it against a stated rule when Workstream A actually starts rather than inheriting an unreproducible count. What is firm is the delta and the `UNSAFE_*` figure below, both measured. **Rules of engagement:** - One component per PR. Refactor only components with existing tests (write them first otherwise). @@ -417,8 +421,8 @@ Every workstream below is a series of small, independently shippable, reversible **Suggested order (dependency- and risk-sorted):** -1. Leaf presentational, mechanical conversions: `Expand`, `Counter`, `ProgressBar`, `ProgressSteps`, `InputNative`, `Tabs` (components pack; gate `Tabs` on the H6 duplicate audit first). `Carousel` and `Collapse` turned out to be orphans (§9.9-H1) — **delete instead of migrating**; `Square` is already a function component, and the pack `TabList` joined the orphan list (mapper uses the engine copy — §2.6-14). -2. Function-component `defaultProps` → default parameters: **done in Phase 2** (`TooltipPop`, `Image` — §6 step 1; React 18.3 warns on them); the third site, `ImageSwatch`, is an orphan resolved by deletion (§9.9-H1). +1. Leaf presentational, mechanical conversions: `Expand`, `Counter`, `ProgressBar`, `ProgressSteps`, `InputNative`, `Tabs` (components pack; gate `Tabs` on the H6 duplicate audit first). ~~`Carousel` and `Collapse` turned out to be orphans (§9.9-H1) — **delete instead of migrating**; `Square` is already a function component, and the pack `TabList` joined the orphan list (mapper uses the engine copy — §2.6-14).~~ **CLOSED 2026-09-17** for those four: `Carousel`, `Collapse`, `Square` and the pack `TabList` are deleted, so the two `UNSAFE_*` migrations `Carousel` and `Collapse` carried are off this workstream permanently and the engine `TabList` (`src/core/pages/main/components/TabList.js`, the copy `mapper.js` uses) is untouched. `Tabs` is **still open**: the pack copy `src/core/components/Tabs.js` survived the deletion, so the H6 duplicate is only half resolved and the gate above still applies. +2. Function-component `defaultProps` → default parameters: **done in Phase 2** (`TooltipPop`, `Image` — §6 step 1; React 18.3 warns on them); ~~the third site, `ImageSwatch`, is an orphan resolved by deletion (§9.9-H1).~~ **CLOSED 2026-09-17** — `ImageSwatch` is deleted, so the third site is resolved and this step has nothing left in it. 3. Page-level: `pages/main/components/Tabs`, `TableView`, `LocalDraftTableRow`. 4. Module-level: `AutoSave`, `ToggleField`, `asInputDateField`, `Upload` views. 5. **Not in this workstream:** `rules.js`, `form/utils.js`, `mapper.js`, `Render.js` → §9.3. @@ -647,7 +651,7 @@ The wrappers' external APIs are the contract; `mapper.js` and meta authors never **What it got wrong, or under-stated.** (a) The tier-1 surface was published as "5 prop names across 7 components" including `verticalAlign` — but `verticalAlign` was tier-1 only because *our own* `LocalDraftTableRow` passed it, never because a meta did, and it did nothing. The real exercised surface is four names (`children`, `className`, `style`, `colSpan`), and "tier 1" needs reading as "something passes it", not "something depends on it". (b) `fixedHeader` was described as unused; it is also **non-functional** — both classes it emits have zero occurrences in `static/all.css` and in `src/style`, so it rendered two unstyled `
`s. Deleted with its test rather than reimplemented. (c) The step notes did not anticipate that removing the wrapper would require restructuring the `docs:props` generator: `Table` cannot stay in a `WRAPPERS` list that asserts a SUIR import, so the page now has two kinds of section (in-house vs wrapper) and the in-house half derives the element per subcomponent, the shared destructure, and the *absence* of any SUIR reference. That was the largest single piece of work in the step, and it is worth budgeting for again at steps 2 and 3. **The budget was spent and the restructuring is finished (verified 2026-09-17):** `scripts/generate-wrapper-prop-reference.js:89` is now `const WRAPPERS = []` and `Table`, `TooltipPop` and `Dropdown` are all `IN_HOUSE` entries (`:96` onward) — the comment at `:80` saying "Only `Dropdown` is still a wrapper" is itself superseded by `:84`, which records that it now renders the in-house `Listbox` and has moved to `IN_HOUSE`. The two lists check each other: `readInHouse` throws if an `IN_HOUSE` entry reaches it. - **Decisions recorded.** `verticalAlign` DROPPED (SUIR emitted `top aligned`; no loaded CSS selects on `aligned`, and no `.top` rule can match a cell, so the 15 cells that asked for it already rendered at the `` default — pixel-identical, and both call sites went with it). `inverted`/`striped` KEPT (the only modifiers any call site passes, and both genuinely styled). `fixedHeader` DELETED. `as`/`celled`/`textAlign` DROPPED. `TableView.js`'s `sellStyles` typo resolved. Semver: on this evidence the step is **minor** — every dropped prop is either unused, unstyled, or both, and every prop any meta in either corpus passes still works; Step 5 records the call for the whole exit. **RECORDED 2026-09-11: the owners chose MINOR for the whole exit** — `docs/SUPPORTED-PROPS.md:265` ("THE SEMVER CALL IS MADE: the owners chose to ship the exit as a MINOR (2026-09-11)"), and step 5 reasons from it at line 846 ("a MINOR that removes published props has to be actionable"). The forward reference is discharged; the bump itself is still pending only because the release is deferred. + **Decisions recorded.** `verticalAlign` DROPPED (SUIR emitted `top aligned`; no loaded CSS selects on `aligned`, and no `.top` rule can match a cell, so the 15 cells that asked for it already rendered at the `` default — pixel-identical, and both call sites went with it). `inverted`/`striped` KEPT (~~the only modifiers any call site passes, and~~ both genuinely styled — **updated 2026-09-17:** §9.9-H1 deleted `ErrorTable.js`, which WAS that call site, so no in-repo caller passes them any more and the generated "Attributes at the call sites" table no longer lists them; the props themselves are untouched and the KEPT decision stands, which is exactly the call this step deferred to H1). `fixedHeader` DELETED. `as`/`celled`/`textAlign` DROPPED. `TableView.js`'s `sellStyles` typo resolved. Semver: on this evidence the step is **minor** — every dropped prop is either unused, unstyled, or both, and every prop any meta in either corpus passes still works; Step 5 records the call for the whole exit. **RECORDED 2026-09-11: the owners chose MINOR for the whole exit** — `docs/SUPPORTED-PROPS.md:265` ("THE SEMVER CALL IS MADE: the owners chose to ship the exit as a MINOR (2026-09-11)"), and step 5 reasons from it at line 846 ("a MINOR that removes published props has to be actionable"). The forward reference is discharged; the bump itself is still pending only because the release is deferred. **Gates.** The markup-independent behavioural layer was run BEFORE the snapshots were regenerated and was green on the new markup while the old baseline was still red: `examples.behavior-contract` + `UIRender.table-behavior` + `UIRender.listbox-behavior` + `UIRender.overlay-behavior`, 4 suites / 104 tests. Then one `-u`, then all three React legs at identical totals (158 suites / 2396 tests / 38 snapshots on 16.14, 17 and 18.3), per-file coverage (`Table.js` at 100/100/100/100, its own new threshold), `lint:js`, `lint:css`, `docs:views:check`, `docs:props:check`. The DOM zero-tripwires (`FIXED_PROP_LEAKS`, `FIXED_MARKUP_JUNK`) stayed at zero and the `name`-only-on-a-form-control invariant stayed at 57. **Still owed and not delivered by this step:** the mandatory Playwright visual/keyboard/a11y suite (§9.7-F1.3) does not exist in the repo, so the `.ui.table` padding contract is verified here by reading the compiled CSS and by class-string assertions, not by rendered pixels. @@ -878,7 +882,7 @@ One measurement worth carrying into Step 4's estimate: the five loaded semantic- | `TextDateValue.js:8` | `moment(value).format(config.dateFormat \|\| 'DD/MM/YYYY')` | same | | `InputDate.js:58–65` | `moment(value)` parse; `moment(str, dateFormat).format('YYYY-MM-DD')` normalize-on-change | token **parser** (parse-by-format) | | `InputDate.js:10,88,94` | rc-picker with `generateConfig` from `rc-picker/lib/generate/moment`; input `format={[dateFormat, 'YYYY-MM-DD']}` | custom `GenerateConfig` | -| `time.js` (`formatTime`, `toHours`) | `moment(t).format(f)` | **no production callers** (referenced only by `time.test.js`); `formatDuration` there is already moment-free | +| `time.js` (~~`formatTime`, `toHours`~~ — **DELETED 2026-09-17**) | ~~`moment(t).format(f)`~~ — none remain | ~~**no production callers** (referenced only by `time.test.js`); `formatDuration` there is already moment-free~~ — **CLOSED 2026-09-17:** both functions were deleted on `h1-dead-weight`, together with `time.js`'s thereby-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and the 12 tests that covered them. Consequence for this analysis: `moment` is now imported **nowhere** under `src/core/utils/` — the remaining `formatDuration` (and `.shortEnglish`) was already moment-free, so the whole utils layer is moment-free and only the four component/rc-picker rows above are left on this table | **Contract constraints (corrected by the re-audit — less favorable than first assessed, §2.6-12):** moment **does** cross the public API. The project's own docs promise it (`README:12,18` — the API "accepts `moment` instances", with cross-copy `instanceof` caveats; `docs.md:24–25`); `InputDate` feeds any `props.value` into `moment(value)` (instances included); and rc-picker callbacks emit moment objects outward — `onSelect` (wired to `onCalendarChange`, `InputDate.js:96`) and every passthrough such as `disabledDate`/`cellRender` (`{...props}` at `InputDate.js:89`). Also contract: `dateFormat` **tokens** are moment syntax (note the `dateFormat` *prop* ~~is currently dead — §2.6-2 — only the context default applies until §9.4 fixes the wiring~~ — **CLOSED 2026-09-17:** §9.4 repaired the wiring, as §9.4-C-config already records. `rules.js:240` declares `dateFormat: type.String`, `:339` destructures it and `:384` publishes it via ``; `providers/ConfigOverride.jsx` merges it into `ConfigContext`; `components/TextDateValue.js:21` reads `moment(value).format(dateFormat || config.dateFormat || 'DD/MM/YYYY')`, so an explicit prop wins over the context default. Pinned by `pages/main/__tests__/UIRender.config-channel.test.js:63`, "formats values with the dateFormat it was given, not the context default"). Consequence: a native adapter changes observable callback types and is a **breaking change** for hosts that use those callbacks or pass instances — not the drop-in swap the first draft assumed. @@ -897,7 +901,7 @@ One measurement worth carrying into Step 4's estimate: the five loaded semantic- Feasible and well-bounded (**~400–600 lines + tests; ~1–2 weeks + regression — less with option (a) for the picker**) because *internal* usage is narrow — but per §2.6-12 the **external** surface does leak moment, so the flip is expected to be **semver-major** (or shipped behind a compatibility shim). And since moment is externalized, the library itself gains nothing — the benefit accrues only to host applications that want moment out of *their* bundles. -- **Now (non-breaking, cheap):** funnel the three component call-sites through a single internal `dateAdapter` module (an extension of `ui-utils-pack/time.js`), and document the supported `dateFormat` token subset. Also: `formatTime`/`toHours` have no production callers — delete or fold into the adapter. +- **Now (non-breaking, cheap):** funnel the three component call-sites through a single internal `dateAdapter` module (an extension of `ui-utils-pack/time.js`), and document the supported `dateFormat` token subset. ~~Also: `formatTime`/`toHours` have no production callers — delete or fold into the adapter.~~ — **CLOSED 2026-09-17:** done on `h1-dead-weight`, and the choice was **delete**, not fold-in — both functions went, along with `time.js`'s thereby-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests (jest 2540 → 2528). `src/core/utils/` now imports `moment` nowhere, so the `dateAdapter` seam would be extending a moment-free `time.js`. The rest of this bullet — funnelling the three component call-sites through `dateAdapter`, and documenting the supported `dateFormat` token subset — is **still open**. - **Later (at a future breaking-change window, or on host demand):** implement the native adapter behind the seam; flip the internal default only when the parity suite is green; demote `moment` to `peerDependenciesMeta.optional` so hosts *may* drop it — hosts that keep it see zero change. - **Triggers to revisit:** hosts asking to shed moment; rc-picker deprecating its moment config; `Temporal` reaching Baseline. @@ -956,8 +960,8 @@ The form stack splits into a React-free core and React bindings: |---|---| | 1 | **The `ui-*-pack` alias system is dead** — **0 imports** use `ui-react-pack`/`ui-modules-pack`/`ui-utils-pack`; no webpack config defines those aliases (only `theme.config` and `process` are aliased); `tsconfig-paths-webpack-plugin` (devDep) is referenced by no config. CLAUDE.md's alias claim was stale (fixed alongside this plan). Actual convention: relative imports + a small `components` barrel used by the engine for `{ cn, type }`. | | 2 | **The engine lives under `core/pages/main/`** — an app-era address for the library's heart (`rules.js`, `mapper.js`, `Data.js`, `dataKindPush.js`, engine-local `utils.js` and `components/`), while the recursive renderer sits separately in `core/ui-render/`. Images (`static/images`) also live inside it; the `tester/` dev fixtures that used to were deleted under H1. | -| 3 | **12 orphan components**: 9 direct — `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` (no imports, no JSX usage, not registered in `mapper.js`) — plus a cluster reachable only from orphans: `ErrorTable` (imported solely by `ErrorContent`), `Square` (solely by `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). | -| 4 | **Dead style trees:** `src/style/unused/` (10 files incl. `_policy`/`_classic` pairs) imported by nothing; `override/` carries `.variables`/`.overrides` for semantic modules that are commented out (§9.7-F1 step 4 finishes that job); icomoon build artifacts (`fonts/icons/demo.html` + demo files, ~1.2k lines) checked in. | +| 3 | ~~**12 orphan components**: 9 direct — `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` (no imports, no JSX usage, not registered in `mapper.js`) — plus a cluster reachable only from orphans: `ErrorTable` (imported solely by `ErrorContent`), `Square` (solely by `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`).~~ **CLOSED 2026-09-17 — all 12 deleted from `src/core/components/` under H1.** The list above is history, not a file inventory: none of those files exist any more, so nobody should go hunting for them. Two details the deletion settled: `ErrorTable` could not go alone (`ErrorContent` was its only importer, so both went in one move), and only the **components-pack** `TabList` went — the engine copy at `src/core/pages/main/components/TabList.js`, which is what `mapper.js:33` resolves, survives untouched. | +| 4 | **Dead style trees:** ~~`src/style/unused/` (10 files incl. `_policy`/`_classic` pairs) imported by nothing~~ — **CLOSED 2026-09-17:** the directory and all 10 files are deleted under H1; `override/` carries `.variables`/`.overrides` for semantic modules that are commented out (§9.7-F1 step 4 finishes that job); ~~icomoon build artifacts (`fonts/icons/demo.html` + demo files, ~1.2k lines) checked in~~ — **CLOSED 2026-09-17:** `demo.html`, `demo-files/` (`demo.css`, `demo.js`) and `Read Me.txt` are deleted. Kept under `fonts/icons/` as a conscious call rather than an oversight: `style.less` and `variables.less` (both on the LESS import graph), `fonts/` (webpack copies it), and `style.css` + `selection.json` as the icon-regeneration set — `selection.json` is icomoon's project file and the only round-trip path back to the icon set. Loose end worth revisiting: now that `demo.html` is gone, `style.css` is referenced by nothing. | | 5 | **Demo entry files sit at `src/` root** (`index.js` → `main.jsx` → `App.jsx`) next to an otherwise self-contained `src/demo/`, blurring the library/demo boundary. | | 6 | **Layering violations:** a genuine engine↔form-modules **import cycle** (`form/utils.js:13–14` ⇄ `rules.js:3`, §2.6-4), plus `components/Text.js:4` importing `../modules/variables` (`ISO_8601_COMPLETE_DATE`). Otherwise directions are clean: core never imports demo; utils imports nothing upward. | | 7 | **Duplicate basenames blur navigation:** two `Tabs.js` and two `TabList.js` (components pack vs engine `components/`); three `utils.js` files (engine, form module, `components/charts/`) plus the utility directories `components/utils/` and `core/utils`; generic `constants.js`/`translations.js`/`styles.js` scattered. Naming collision: `pages/main/components/Popup.js` is actually a **modal**, while `TooltipPop` wraps SUIR *Popup*. | @@ -966,10 +970,10 @@ The form stack splits into a React-free core and React bindings: #### H1 — Delete dead weight (cheap, do first) -- The **12 orphan components** (9 direct + the `ErrorTable`/`Square`/pack-`TabList` cluster, §2.6-14). Gate: grep consumer metas for these names as `view` values first — they are not registered in `mapper.js`, so no meta can render them, but verify before deleting. Bonus effects: `Carousel`/`Collapse` drop two `UNSAFE_` migrations from Workstream A; `ImageSwatch` removes one of the three `defaultProps` sites (§8); the pack `TabList` resolves an H6 duplicate. -- `src/style/unused/` (10 files); ~~`override/_policy.less` / `_classic.less` (verify unreferenced)~~ — **CLOSED 2026-09-17 (same item as AppC-H1-2, which already recorded it done; this backlog line was simply not updated).** Both files are gone from `src/style/override/`, removed by 9f393959 "F1 step 4: delete semantic-ui-less and the theme.config machinery (#46)" along with the rest of the override tree. Not to be confused with `src/style/unused/_policy.less` / `_classic.less`, which still exist and are covered by the `src/style/unused/` item at the head of this bullet. Still open: icomoon demo artifacts under `fonts/icons/`. +- ~~The **12 orphan components** (9 direct + the `ErrorTable`/`Square`/pack-`TabList` cluster, §2.6-14). Gate: grep consumer metas for these names as `view` values first — they are not registered in `mapper.js`, so no meta can render them, but verify before deleting. Bonus effects: `Carousel`/`Collapse` drop two `UNSAFE_` migrations from Workstream A; `ImageSwatch` removes one of the three `defaultProps` sites (§8); the pack `TabList` resolves an H6 duplicate.~~ — **CLOSED 2026-09-17:** all 12 are deleted from `src/core/components/` (`Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `ErrorTable`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Square`, `TabList`, `Tags`); `ErrorTable` could not go alone because `ErrorContent` imported it, so both went together. The bonus effects landed as written (two `UNSAFE_` migrations off Workstream A, one of the three `defaultProps` sites off §8). **One correction to the old text:** deleting the pack `TabList` resolves only *half* the H6 duplicate — the pack `Tabs` (`src/core/components/Tabs.js`) is still present, so H6 is not closed. **Decision this item owned, taken 2026-09-17:** `Table`'s `inverted`/`striped` are **KEPT**. `ErrorTable` was their only in-repo call site, so the generated "Attributes at the call sites" table in `docs/SUPPORTED-PROPS.md` no longer lists them — that table reports what the *codebase passes*, not what the component *accepts*. `Table.js` still destructures both, the CSS still selects on them (`table:not(.as-layout).inverted`, `table.striped tr:nth-child(2n)`), and a consumer meta can still set them; `scripts/wrapper-prop-curation.js` now records this. Verified after: jest **167 suites / 2528 tests** (was 2540 — the drop is exactly the 12 deleted `time.js` tests below, not anything to do with the components), and `lint:js`, `lint:css`, `css:fixture:check`, `docs:props:check`, `docs:views:check`, `build-lib`, `build-css` all green with the published `static/all.css` sha256 unchanged at `dcdb0a40a6d8ee79929bc40a9335b04f8222e7a235a872242c87ecffa9332723`. +- ~~`src/style/unused/` (10 files)~~ — **CLOSED 2026-09-17:** the directory and all 10 files are deleted on `h1-dead-weight`. ~~`override/_policy.less` / `_classic.less` (verify unreferenced)~~ — **CLOSED 2026-09-17 (same item as AppC-H1-2, which already recorded it done; this backlog line was simply not updated).** Both files are gone from `src/style/override/`, removed by 9f393959 "F1 step 4: delete semantic-ui-less and the theme.config machinery (#46)" along with the rest of the override tree. Not to be confused with `src/style/unused/_policy.less` / `_classic.less`, which still exist and are covered by the `src/style/unused/` item at the head of this bullet. ~~Still open: icomoon demo artifacts under `fonts/icons/`.~~ **CLOSED 2026-09-17:** `demo.html`, `demo-files/` (`demo.css`, `demo.js`) and `Read Me.txt` are deleted. `style.less`, `variables.less`, `fonts/`, `style.css` and `selection.json` were kept deliberately — see H0-4 for which and why, including the loose end that `style.css` is now referenced by nothing. - ~~the unreferenced engine `tester/` fixtures~~ — **deleted.** They were worse than merely dead: `test_data.js` and `test_meta.js` re-exported from `../examples/…`, and no `examples/` directory exists under `src/core/pages/main/` (the fixtures live in `src/demo/examples/`), so either file would have failed module resolution the moment anything imported it. Nothing did. `eslint-config-react-app` could not see it because `import/no-unresolved` is off. Removing them also took two permanently-0% files out of the coverage report; global coverage after deletion is 94.29% statements / 89.38% branches / 92.71% functions / 94.9% lines. -- `formatTime`/`toHours` in `time.js` (no production callers, §9.7-F2). +- ~~`formatTime`/`toHours` in `time.js` (no production callers, §9.7-F2).~~ — **CLOSED 2026-09-17:** both are deleted from `src/core/utils/time.js`, along with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }`; their test blocks went too (12 tests, the whole 2540 → 2528 delta). `formatDuration` (and `.shortEnglish`) stay, and were already moment-free. **Consequence for §9.7-F2:** `moment` is now imported **nowhere** under `src/core/utils/` — the only remaining internal importers are the three component call sites (`Text.js`, `TextDateValue.js`, `InputDate.js`), which is exactly the set the F2 `dateAdapter` funnel has to cover. - `FIELD.TYPE.DATE` (`modules/form/constants.js:16`) — defined, never read, and `view: 'Date'` therefore renders a placeholder instead of the date field the name implies (§2.6-17). Deleting it is the honest move unless the view spelling is meant to be supported, in which case it needs a branch in `renders.js` rather than a constant. - ~~The **13 zero-reference devDependencies**~~ — **done in Phase 0.9:** all 13 plus `dot-prop-immutable` removed, verified by pruning `node_modules` to the lockfile with `npm ci` and re-running every pipeline including `build-css` and the watch build. @@ -995,7 +999,7 @@ Fix the direct violation (move `ISO_8601_COMPLETE_DATE` into `core/utils`); the - `pages/main/components/Popup.js` → `Modal.js` (it *is* a modal; frees the name collision with tooltip-Popup). - `TooltipPop` → `Tooltip` during its F1 step 2 rewrite. - Engine `utils.js` dissolves into named modules during §9.3 (`dataMapping.js`, `errorMapping.js`, …). -- `Tabs`/`TabList` pairs: the re-audit confirmed mapper uses the engine copies and the pack `TabList` is an orphan (delete via H1); audit the pack `Tabs` the same way before its §9.2 migration. +- `Tabs`/`TabList` pairs: the re-audit confirmed mapper uses the engine copies and the pack `TabList` is an orphan ~~(delete via H1)~~ — **deleted 2026-09-17 under H1**, so the `TabList` duplicate is gone and `pages/main/components/TabList.js` is the only one left. **Half of this item remains open:** the pack `Tabs` (`src/core/components/Tabs.js`) still sits alongside the engine `Tabs`, so audit it the same way before its §9.2 migration — do not read the `TabList` deletion as closing H6's duplicate-basename item. - **Completed version guard:** `npm version` synchronizes the runtime wrapper and demo-shell literals from `package.json`; `prepack` rejects drift before rebuilding. The removed type shim is no longer a version site (§2.6-15). #### H7 — Build config consolidation @@ -1028,7 +1032,7 @@ src/ utils/ # pure utils (+ dateAdapter from F2); absorbs core/common AFTER untangling (H0-9) contexts/ providers/ services/ demo/ # all demo code incl. entries (H3) - style/ # minus unused/ and icomoon artifacts + style/ # unused/ gone (H1); icomoon demo files gone, style.css + selection.json kept ``` **Sequencing:** H1/H2 — immediately, in any quiet moment (Phase 4 window). H3/H4 — right after Phase 2 ships, as dedicated pure-move commits in a lull between feature branches (see R11). H5 rides the 4-S window (it is one ESLint-config PR); H6–H7 — opportunistic. **H4 must precede Phase 6.** @@ -1103,8 +1107,8 @@ Phases 3 and 4 can partially overlap. Tracks **5a/5b** (SUIR exit) and **6** (en - **Docs hardcoding React version:** `src/demo/markdowns/docs.md:16,37,41` · `README.md` (plus a prose peer-deps note at `changelog.md:50`). - **SUIR JS surface — ~~3 of 54 pack files~~ → 0 (re-measured 2026-09-17).** `grep -rn 'semantic-ui-react' src/core/components/{Table,TooltipPop,Dropdown}.js` returns only explanatory comments (`Table.js:9,:96` · `TooltipPop.js:7,:12,:103,:111,:118,:171,:318` · `Dropdown.js:26,:69`) — no import in any of the three. The inventory as it stood: `Table.js` (subcomponent users: `mapper.js:170`, `TableView.js`, `LocalDraftTableRow.js`, `ErrorTable.js`) · `TooltipPop.js` (`Render.Tooltip`, `mapper.js:44,483`) · `Dropdown.js` (`mapper.js:540–544`). - **semantic-ui-less modules actually imported** (`src/style/override/_semantic.less`) — **superseded 2026-09-17: there are none; the package itself is gone (9f393959).** `_semantic.less` now imports `../vendor/semantic-reset` and `../vendor/semantic-dropdown`, each followed by our own overrides; `ls src/style/override/globals` = `reset.overrides`, `reset.variables`, `site.variables` and `ls src/style/override/modules` = `dropdown.overrides`, `dropdown.variables`. The label/menu/popup modules were measured unused and deleted at F1 step 4's first half (9a531871). The inventory as it stood: `globals/reset`, `elements/label` (chips), `collections/menu` (own Pagination), `modules/dropdown`, `modules/popup`; `transition` already replaced by an in-house `transition.less` — **that last clause is still true** (`@import 'transition'` closes `_semantic.less`). -- **moment call sites:** `Text.js:43` · `TextDateValue.js:8` · `InputDate.js:58–65` (+ rc-picker moment `generateConfig` at :10/:88) · `time.js` `formatTime`/`toHours` (test-only, no production callers). -- **Structure facts (§9.9):** `ui-*-pack` alias imports: **0** across `src` · ~~`tsconfig-paths-webpack-plugin`: referenced by no config~~ **REMOVED 2026-08-10 (00d67612, the Phase 0.5/0.9 sweep): it is not a devDependency at all any more — `grep -n 'tsconfig-paths' package.json` returns nothing (re-verified 2026-09-17)** · orphan components ×12: `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` + cluster `ErrorTable`/`Square`/pack-`TabList` · layering: engine⇄form-modules cycle (`form/utils.js:13–14` ⇄ `rules.js:3`) + `components/Text.js:4` → `../modules/variables` · demo entries at `src/` root: `index.js`, `main.jsx`, `App.jsx`. +- **moment call sites:** `Text.js:43` · `TextDateValue.js:8` · `InputDate.js:58–65` (+ rc-picker moment `generateConfig` at :10/:88) · ~~`time.js` `formatTime`/`toHours` (test-only, no production callers)~~ — **CLOSED 2026-09-17 (H1, branch `h1-dead-weight`): both functions are DELETED**, together with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests; `formatDuration` (and `.shortEnglish`) stay. Do not go looking for the two functions — read the struck clause as the inventory it was. **Consequence for §9.7-F2:** the remaining moment surface is exactly the three components plus the rc-picker `generateConfig`, and **moment is now imported nowhere in `src/core/utils/`**. +- **Structure facts (§9.9):** `ui-*-pack` alias imports: **0** across `src` · ~~`tsconfig-paths-webpack-plugin`: referenced by no config~~ **REMOVED 2026-08-10 (00d67612, the Phase 0.5/0.9 sweep): it is not a devDependency at all any more — `grep -n 'tsconfig-paths' package.json` returns nothing (re-verified 2026-09-17)** · ~~orphan components ×12: `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` + cluster `ErrorTable`/`Square`/pack-`TabList`~~ **CLOSED 2026-09-17 (H1, branch `h1-dead-weight`): all 12 files are DELETED from `src/core/components/`** — the count is now history, not a set of files anyone can open. `ErrorTable` could not go alone (`ErrorContent` imported it), so the pair went together. Only the **pack** `TabList` went: the engine `TabList` (`src/core/pages/main/components/TabList.js`) is untouched and mapper still uses it, which resolves **half** of the H6 duplicate — pack `Tabs.js` is still on disk, so **H6 is not closed**. Re-running the Appendix B orphan scan now reports no zero-count pack file except the `index.js` barrel, which is a scan artefact · layering: engine⇄form-modules cycle (`form/utils.js:13–14` ⇄ `rules.js:3`) + `components/Text.js:4` → `../modules/variables` · demo entries at `src/` root: `index.js`, `main.jsx`, `App.jsx`. - **Public-surface facts (§2.6):** runtime entry `library/index.js → main.js` (`AppProvider → AppWrapper → engine`) · `require(dist)` returns one callable function with no `.default`/`.UIRender`; declarations use `export =`, retain named types through a merged namespace, and pass locked React 16/17/18 checks in interop and direct-CommonJS modes · ~~`dateFormat` prop is still dead (`Render.js:45`)~~ **CLOSED (re-verified 2026-09-17): the config channel works.** `Render.js:55–59` drops the prop *deliberately* — the format is configuration and travels through `ConfigContext`, which `rules.js:384` feeds via ``, and `TextDateValue.js:21` reads `dateFormat || config.dateFormat`; pinned by `UIRender.config-channel.test.js:63` and announced at `changelog.md:32` ("**The `dateFormat`, `currency` and `language` props now work.**"). The cited `Render.js:45` no longer holds the destructure either — it is at :59 · fixed-id portal root (`AppWrapper.js:17`) · `prepack` checks version sync and rebuilds; ~~the measured tarball is 295 files / 7.25 MB unpacked / 2.53 MB packed~~ **Figures re-measured 2026-09-17** through the repo's own gate (`scripts/check-package-budget.js`, i.e. `npm pack --dry-run --json --ignore-scripts`): **299 files / 6.44 MB unpacked / 2.39 MB packed** — still inside the 330 / 8 MB / 3 MB budgets, assets shipped once in the root `static/` payload with `dist/static/*.css` as `@import` re-exports, source maps kept by decision, and CI enforcing both the budgets and a packed-tarball server-render smoke. ## Appendix B — Verification commands @@ -1140,6 +1144,14 @@ npm run lint:js npm run docs:props:check # Structure: orphan component scan (§9.9-H1 — count of importing files per component; 0 = orphan) +# Since H1 landed (2026-09-17) the 12 named orphans are DELETED, so this is a regression guard, not a +# to-delete list. Two traps in reading its output, both measured 2026-09-17: +# - the only remaining 0 is `index.js`, an artefact — the barrel is imported by directory path +# (`from '../components'`), never as `/index'`; +# - the pattern `/${n}'` ignores the directory, so a pack file shares its count with any same-named +# file elsewhere. `Tabs` reads 3, but `mapper.js` and `pages/main/components/TabList.js` both import +# the ENGINE `./Tabs`; the pack `Tabs.js` has exactly one importer, `src/demo/components/NavTabs.jsx` +# (demo chrome, not mapper-reachable). Check the importers, not just the count. for f in src/core/components/*.js; do n=$(basename "$f" .js); \ c=$(rg -l "/${n}'" src --glob '!*.test.*' | grep -v "components/${n}.js" | wc -l); echo "$c $n"; done | sort -n @@ -1192,8 +1204,8 @@ Every check this plan depends on, in one place. ✅ = already verified during th - ✅ No legacy React APIs in `src`: string refs, legacy context, `findDOMNode`, `unstable_*`, `react-dom/test-utils`, `element.ref` reads — all still zero, re-measured 2026-09-17. **`e.persist()` corrected 2026-09-17:** this line used to list `e.persist()` among them, and stopped being true on 2026-08-20, when one deliberate call was *added* to fix a React 16 event-pooling bug — `persistEvent()` at `src/core/modules/upload/views/Upload.js:34`, called at `:100` and `:105`, covered by `Upload.interactions.test.js:98-111`. §2.4 and §5 already record it as the one intended call site; only this line was never updated. The call site is correct and stays. - ✅ All runtime deps declare React 17/18 peers (lock-verified); ~~`@testing-library/react` 12 is the only `<18` blocker~~ **SUPERSEDED 2026-09-17 (Phase 2):** the first clause still holds, but the blocker is gone and inverted — RTL is now `^16.3.2`, whose peers read `react`/`react-dom` `^18.0.0 || ^19.0.0`, so it no longer blocks 18, it requires it. Read the struck half as the audit fact it was, not as current state - ✅ ~~SUIR surface: imports confined to `src/core/components`; exactly 3 wrappers; 5 semantic LESS modules active~~ **SUPERSEDED 2026-09-17 (§9.7-F1 steps 1–4):** all three clauses now hold only in a stronger form, so the line as written is false — re-measured 2026-09-17: **zero** `semantic-ui-react` imports anywhere in `src` (the package is not installed, is in neither dependency block, and `no-restricted-imports` covers all of `src` with a message); **zero** SUIR wrappers — `Table.js`, `TooltipPop.js` and `Dropdown.js` are in-house and `Listbox.js` is new; and **no** `semantic-ui-less` module is imported at all — `override/_semantic.less` imports the two vendored files under `src/style/vendor/` (`globals/reset`, `modules/dropdown`), step 4's first half having deleted `modules/popup`, `elements/label` and `collections/menu`, and every remaining `semantic-ui-less` mention in `src/style` is prose or a provenance header. The ongoing-invariant line "SUIR isolation grep: **nothing at all**" below already states the stronger version -- ✅ moment surface: 3 components + rc-picker `generateConfig`; `formatTime`/`toHours` have no production callers -- ✅ Structure: `ui-*-pack` alias imports = 0; `tsconfig-paths-webpack-plugin` referenced by no config; 12 orphan components incl. the `ErrorTable`/`Square`/pack-`TabList` cluster; layering = engine⇄form-modules cycle + `Text.js:4` +- ✅ moment surface: 3 components + rc-picker `generateConfig`; ~~`formatTime`/`toHours` have no production callers~~ **SUPERSEDED 2026-09-17 (H1): both are DELETED from `src/core/utils/time.js`**, with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests (`formatDuration`/`.shortEnglish` stay). The first clause still holds — `Text.js`, `TextDateValue.js`, `InputDate.js` + the rc-picker `generateConfig` are now the *whole* surface — and the F2-relevant consequence is that **moment is imported nowhere in `src/core/utils/`**. Read the struck half as the 2026-07-21 audit fact it was +- ✅ Structure: `ui-*-pack` alias imports = 0; `tsconfig-paths-webpack-plugin` referenced by no config; ~~12 orphan components incl. the `ErrorTable`/`Square`/pack-`TabList` cluster~~ **SUPERSEDED 2026-09-17: all 12 were DELETED under H1** — the count is a record of what the audit found, not files on disk. The engine `TabList` survives (mapper uses it) and pack `Tabs.js` survives too, so the H6 duplicate is only half reconciled; layering = engine⇄form-modules cycle + `Text.js:4` - ✅ Re-audit (2026-07-21): tests 76 suites / 1215 green in 8.3 s; `lint:css` clean; eslint fails (11 errors / 28 warnings); prod `npm audit` 0, full audit 20; tarball 579 files / 11.6 MB with duplicated assets + maps; published types ≠ runtime; `dateFormat` prop dead; built CSS ships unscoped `html`/`body`/`*`; moment crosses the public API (README-documented + picker callbacks) - ✅ 19-ready form stack exists upstream (`react-final-form` 7.0.1 / `final-form` 5.0.1 / `final-form-arrays` 4.0.1 / `react-final-form-arrays` 5.0.0 — npm-registry-verified) - ✅ Single `cloneElement` site passes plain props only (ref-as-prop safe) @@ -1299,7 +1311,7 @@ Every check this plan depends on, in one place. ✅ = already verified during th - ☐ Orphan component names grepped across consumer metas as `view` values - ☑ `override/_policy.less` / `_classic.less` confirmed unreferenced — and DELETED at step 4 with 87 other dead files, the set determined by the LESS import graph rather than by grep - ☒ `less-plugin-functions`: removed in a branch → all four pipelines still build → devDep dropped (§9.8). **Attempted and CLOSED as not-doable-as-written (2026-09-11):** the build fails without it at `_variables.less:264` on `round()`. Re-opening this means replacing those nine call sites first. -- ☐ Pack `Tabs`/`TabList` audited for orphan status before reconciling the duplicates (H6) +- 🔶 ~~Pack `Tabs`/`TabList` audited for orphan status before reconciling the duplicates (H6)~~ **HALF DONE 2026-09-17 (H1):** the pack `TabList` was audited, confirmed an orphan (mapper imports the engine copy, `mapper.js:33`) and DELETED with the rest of the H1 set; the engine `TabList` (`pages/main/components/TabList.js`) is untouched. **Still open — do not read H6 as closed:** the pack `Tabs.js` is still on disk. Measured 2026-09-17, its only importer is `src/demo/components/NavTabs.jsx` (demo chrome), while `mapper.js:34` and the engine `TabList.js:3` both import the engine `./Tabs` — so it is not mapper-reachable, but it is not an orphan either, and the duplicate is still unreconciled ### F4 — form-stack major bump diff --git a/scripts/wrapper-prop-curation.js b/scripts/wrapper-prop-curation.js index 459bcafa..1d2e3577 100644 --- a/scripts/wrapper-prop-curation.js +++ b/scripts/wrapper-prop-curation.js @@ -103,10 +103,17 @@ const IN_HOUSE_CURATION = { + '`styles`/`fill`/`vertical` attributes; consumer metas add `as-layout`, `no-header`, ' + '`highlight-N-last` and the sticky-column tokens through the same channel.', inverted: 'Dark table. Emitted as the `inverted` class, which `table.less` and ' - + '`expand.less` both select on. Reached only from `ErrorTable.js` — a §9.9-H1 orphan, ' - + 'so the prop is kept but its fate is that deletion\'s to decide, not this step\'s.', - striped: 'Zebra rows, emitted as the `striped` class. Same single call site as `inverted`, ' - + 'and also genuinely styled — which is why neither was dropped with the rest.', + + '`expand.less` both select on. **KEPT — decided at §9.9-H1 (2026-09-17), which is ' + + 'where step 1 left the call.** Its one in-repo caller was `ErrorTable.js`, and H1 ' + + 'deleted it, so the "Attributes at the call sites" table above no longer lists this ' + + 'prop — that table reports what the CODEBASE passes, not what the component accepts. ' + + 'The prop itself is untouched: `Table.js` still destructures it and still emits the ' + + 'class, the CSS still selects on it, and a consumer meta can still set it. Dropping ' + + 'it because our own last caller went away would have been a breaking change bought ' + + 'for nothing.', + striped: 'Zebra rows, emitted as the `striped` class. Same story as `inverted` exactly: ' + + 'genuinely styled (`table.striped tr:nth-child(2n)`), which is why neither was ' + + 'dropped with the rest, and still accepted after H1 removed their shared call site.', }, partProps: { className: 'Passed through verbatim, or the attribute is omitted entirely when it is ' diff --git a/src/core/components/Avatar.js b/src/core/components/Avatar.js deleted file mode 100644 index 05ac15e4..00000000 --- a/src/core/components/Avatar.js +++ /dev/null @@ -1,79 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React from 'react' -import Icon from './Icon' -import Text from './Text' -import View from './View' - -/** - * Avatar - Component. - * - * @param {String} name - name - * @param {String} [status] - online status, one of ['online', 'dueling', 'paused', 'stopped'] - * @param {Boolean} [hideName] - whether to hide the name underneath - * @param {Boolean} [small] - whether to add 'small' css class - * @param {Boolean} [large] - whether to add 'large' css class - * @param {Boolean} [larger] - whether to add 'larger' css class - * @param {Boolean} [secure] - whether to disable images - * @param {Function} [onClick] - function to call - * @param {String} [src] - image URL - * @param {String} [className] - css class name to apply - * @param {Object} [style] - css props - * @param {Object} [children] - extra content to render inside avatar - * @returns {Object} - React component - */ -export function Avatar ({ - name = '', - status = '', - hideName = true, - src = '', - secure, - small, - large, - larger, - className, - onClick, - style, - children, -}) { - const hasImage = src && !secure - return ( - - - - {hasImage - ? {name} // for screen readers - : {name.charAt(0).toUpperCase() || } - } - - - {!hideName && {name}} - {children} - - ) -} - -Avatar.propTypes = { - name: PropTypes.string.isRequired, - hideName: PropTypes.bool, - small: PropTypes.bool, - large: PropTypes.bool, - larger: PropTypes.bool, - secure: PropTypes.bool, - src: PropTypes.string, - onClick: PropTypes.func, - children: PropTypes.any, -} - -export default React.memo(Avatar) diff --git a/src/core/components/Badge.js b/src/core/components/Badge.js deleted file mode 100644 index 4f3ee0fb..00000000 --- a/src/core/components/Badge.js +++ /dev/null @@ -1,27 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React from 'react' -import { shortNumber } from '../utils' -import View from './View' - -/** - * Badge Counter - Pure Component. - */ -export function Badge ({ - count, - digits = 2, - className, - ...props -}) { - const counter = (digits && count > 9) ? shortNumber(count, digits) : count - // noinspection JSConstructorReturnsPrimitive - return counter ? {counter} : null -} - -Badge.propTypes = { - count: PropTypes.number.isRequired, - digits: PropTypes.number, - className: PropTypes.string, -} - -export default React.memo(Badge) diff --git a/src/core/components/Carousel.js b/src/core/components/Carousel.js deleted file mode 100644 index 6975c621..00000000 --- a/src/core/components/Carousel.js +++ /dev/null @@ -1,122 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React, { PureComponent } from 'react' -import { isFunction } from '../utils' -import { formatDuration, } from '../utils/time' -import Icon from './Icon' -import { imageSrc } from './Image' -import Loading from './Loading' -import Row from './Row' -import ScrollView from './ScrollView' -import Square from './Square' -import Text from './Text' -import { withTimer } from './utils' -import View from './View' - -/** - * Carousel - Self Contained Container. - */ -@withTimer -export default class Carousel extends PureComponent { - static propTypes = { - items: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.shape({ - avatar: PropTypes.string, // small image url - src: PropTypes.string, // full image url - }), - PropTypes.shape({ // same as Image.propTypes - name: PropTypes.string.isRequired, - path: PropTypes.string, - }), - ])).isRequired, - activeIndex: PropTypes.number, // opened tab index (controlled) - defaultIndex: PropTypes.number, // opened tab index initially (uncontrolled) - hideImage: PropTypes.bool, // whether to hide background image for active item - hideItems: PropTypes.bool, // whether to hide list of items below - hideCount: PropTypes.bool, // whether to hide item count at the top left - hideControls: PropTypes.bool, // whether to hide prev and next arrows - onChange: PropTypes.func, // callback when active item index changes, receives activeItem ID if given, or index - square: PropTypes.bool, // whether to render as square - className: PropTypes.string, - itemClass: PropTypes.string, // css class names to add to active item - children: PropTypes.any, // extra content to render inside Carousel, receives active item props as argument - } - - state = { - activeIndex: Math.max(this.props.activeIndex || this.props.defaultIndex || 0, 0), - transition: false, - } - - UNSAFE_componentWillReceiveProps (next) { - const {activeIndex, items} = next - if (activeIndex != null && activeIndex !== this.props.activeIndex) this.handleClickItem(activeIndex) - - // Handle use case when parent changes layout and carousel has less panels than previously set active index - if (this.state.activeIndex >= items.length) this.setState({activeIndex: 0}) - } - - handleClickItem = (activeIndex) => { - this.setState({transition: true}) - this.setTimeout(() => { - this.setState({activeIndex, transition: false}) - const {onChange, items} = this.props - onChange && onChange((items[activeIndex] || {}).id || activeIndex) - }, 50) - } - - handleClickPrev = () => { - let { activeIndex } = this.state - activeIndex = activeIndex ? activeIndex - 1 : this.props.items.length - 1 - this.handleClickItem(activeIndex) - } - - handleClickNext = () => { - let {activeIndex} = this.state - activeIndex = (activeIndex === this.props.items.length - 1) ? 0 : activeIndex + 1 - this.handleClickItem(activeIndex) - } - - render () { - const { - items, - hideImage, hideItems, hideCount, hideControls, - itemClass, children, className, style, fill, square, - loading = false, - } = this.props - const {activeIndex, transition} = this.state - if (!items.length) return null - const activeItem = items[activeIndex] || {} - const backgroundImage = !hideImage ? `url('${imageSrc(activeItem)}')` : '' - const dateAgo = activeItem.time ? formatDuration(Date.now() - activeItem.time, {shorten: true, largest: 1}) : '' - const Canvas = square ? Square.Row : Row - return ( - - - {!hideCount && {activeIndex + 1}/{items.length}} - {dateAgo && {dateAgo}} - {items.length > 1 && !hideControls && - <> - - - } - {isFunction(children) ? children(activeItem, activeIndex) : children} - - {!hideItems && items.length > 1 && - - {items.map((item, index) => { - const style = {backgroundImage: `url('${imageSrc(item)}')`} - const active = index === activeIndex - return this.handleClickItem(index))} - /> - })} - - } - {loading && } - - ) - } -} diff --git a/src/core/components/Collapse.js b/src/core/components/Collapse.js deleted file mode 100644 index 62fb2d37..00000000 --- a/src/core/components/Collapse.js +++ /dev/null @@ -1,94 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React, { PureComponent } from 'react' -import { removeNilValues } from '../utils' -import Icon from './Icon' -import Row from './Row' -import View from './View' - -/** - * Collapse / Expand - Self Contained Container. - * @example: - * } - * contentRender={isOpen => (isOpen && Expanded content)} - * /> - * } - * contentOpened={Expanded content} - * contentClosed={Closed content} - * /> - */ -export default class Collapse extends PureComponent { - static propTypes = { - isOpen: PropTypes.bool, - horizontal: PropTypes.bool, - minSize: PropTypes.string, // min-height or min-width css property when collapsed, default is `0` - maxSize: PropTypes.string, // max-height or max-width css property when expanded, default is `100%` - toggleClass: PropTypes.string, // css class names - toggleRender: PropTypes.func, // toggle render function, receives `isOpen` as argument - toggleOpened: PropTypes.any, // toggle content to render in expanded state - toggleClosed: PropTypes.any, // toggle content to render in collapsed state - contentClass: PropTypes.string, // css class names - contentRender: PropTypes.func, // content render function, receives `isOpen` as argument - contentOpened: PropTypes.any, // content to render in expanded state, if `render()` not given - contentClosed: PropTypes.any, // content to render in collapsed state, if `render()` not given - className: PropTypes.string, // css class names - style: PropTypes.object, // css styles - children: PropTypes.any, // additional content to render inside component - } - - static defaultProps = { - toggleOpened: , - toggleClosed: , - } - - state = { - isOpen: false, - size: '0', - } - - handleToggle = () => { - const {isOpen} = this.state - const {minSize, maxSize, contentClosed} = this.props - const size = !isOpen ? (maxSize || '100%') : (minSize || (contentClosed ? 'min-content' : '0')) - this.setState({ isOpen: !isOpen, size }) - } - - setOpenState = ({ isOpen, minSize, maxSize, contentClosed } = this.props) => { - if (minSize == null && contentClosed) minSize = 'min-content' - if ( - (isOpen !== null && isOpen !== this.state.isOpen) || - (!this.state.isOpen && minSize !== null && minSize !== this.state.size) || - (this.state.isOpen && maxSize !== null && maxSize !== this.state.size) - ) this.setState(removeNilValues({ isOpen, size: isOpen ? maxSize : minSize })) - } - - UNSAFE_componentWillMount () { - this.setOpenState() - } - - UNSAFE_componentWillReceiveProps (nextProps, nextContext) { - this.setOpenState(nextProps) - } - - render () { - const { - toggleRender, toggleOpened, toggleClosed, - contentRender, contentOpened, contentClosed, - horizontal, className, style, toggleClass, contentClass, children, - } = this.props - const {isOpen, size} = this.state - const toggle = toggleRender ? toggleRender(isOpen) : (isOpen ? toggleOpened : toggleClosed) - const content = contentRender ? contentRender(isOpen) : (isOpen ? contentOpened : contentClosed) - const contentStyle = {[horizontal ? 'maxWidth' : 'maxHeight']: size} - const Container = horizontal ? Row : View - return ( - - {toggle} - {content} - {children} - - ) - } -} diff --git a/src/core/components/ErrorContent.js b/src/core/components/ErrorContent.js deleted file mode 100644 index f1926cd0..00000000 --- a/src/core/components/ErrorContent.js +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Fragment } from 'react' -import { hasListValue } from '../utils' -import ErrorTable from './ErrorTable' -import View from './View' - -/** - * Error Alert Message Content - Pure Component. - * - * @param {Array} items - error objects - * @param {*} props - other attributes to pass to `` - * @returns {Object} - React table component - */ -export function ErrorContent ({ - items, - ...props -}) { - // Filter out User vs Server Errors - const serverErrors = items.filter(({status, statusCode, code}) => (((status || statusCode || code) >= 500) || ((status || statusCode || code) == null))) - const userErrors = items.filter(({status, statusCode, code}) => ((status || statusCode || code) < 500)) - return ( - - {hasListValue(serverErrors) && - - - - } - {hasListValue(userErrors) && - - - - } - - ) -} - -ErrorContent.propTypes = { - items: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.shape({ - id: PropTypes.any, - status: PropTypes.number, - title: PropTypes.string, - detail: PropTypes.string, - message: PropTypes.string - }) - ])).isRequired -} - -export default React.memo(ErrorContent) diff --git a/src/core/components/ErrorTable.js b/src/core/components/ErrorTable.js deleted file mode 100644 index b34e025a..00000000 --- a/src/core/components/ErrorTable.js +++ /dev/null @@ -1,66 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { get, hasListValue } from '../utils' -import Table from './Table' - -/** - * Error Table Listing - Pure Component. - * - * @param {Array} items - error objects - * @param {Boolean} isServerError - whether the error is server related or user related - * @param {Boolean} inverted - whether to add `inverted` class - * @param {*} props - other attributes to pass to `
` - * @returns {Object} - React table component - */ -export function ErrorTable ({ - items, - isServerError, - inverted, - ...props -}) { - const hasId = !!get(items, '[0].id') - const hasStatus = !!get(items, '[0].status') || !!get(items, '[0].statusCode') || !!get(items, '[0].code') - const hasTitle = !!get(items, '[0].title') || !!get(items, '[0].error') - return ( -
- - - {hasId && Error ID} - {hasStatus && Status} - {hasTitle && {isServerError ? 'Title' : 'Reason'}} - Message - - - - {hasListValue(items) && - items.map(({id, status, statusCode, code, title, error, content, detail, message, msg, ...data}, index) => ( - - {hasId && {id}} - {hasStatus && {status || statusCode || code}} - {hasTitle && {title || error}} - - {content || detail || message || msg || get(data, 'details[0].message', String(items[index]))} - - - ))} - -
- ) -} - -ErrorTable.propTypes = { - isServerError: PropTypes.bool, - items: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.shape({ - id: PropTypes.any, - status: PropTypes.number, - title: PropTypes.string, - detail: PropTypes.string, - message: PropTypes.string - }) - ])).isRequired -} - -export default React.memo(ErrorTable) diff --git a/src/core/components/FloatNumber.js b/src/core/components/FloatNumber.js deleted file mode 100644 index 10853511..00000000 --- a/src/core/components/FloatNumber.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import { renderFloat } from './renders' -import { type } from './types' - -/** - * Localised Float or Integer Number - * (with option to round to fixed Fraction length or truncate without rounding) - */ -export function FloatNumber ({value, decimals, ...props}) { - return renderFloat(value, decimals, props) -} - -FloatNumber.propTypes = { - // Localised Number to render - value: type.NumberOrString, - // Number of fraction digits to keep - decimals: type.Number, - // Disable fraction part faded styling - faded: type.Boolean, - // Truncate fraction without rounding - truncated: type.Boolean, -} - -export default React.memo(FloatNumber) diff --git a/src/core/components/ImageSwatch.js b/src/core/components/ImageSwatch.js deleted file mode 100644 index b9d079f4..00000000 --- a/src/core/components/ImageSwatch.js +++ /dev/null @@ -1,39 +0,0 @@ -import cn from '../utils/classNames' -import React from 'react' -import Image from './Image' -import { type } from './types' -import { View } from './View' - -/** - * Image Swatch - Pure Component. - */ -export function ImageSwatch ({ - src, - name, - small, - large, - className, - children, - ...props -}) { - // Use for SEO and lazy load performance - // Use cached component to reduce rendering because `src` and `alt` are unlikely to change, unlike `onClick` - return - {name}/ - {children} - -} - -ImageSwatch.defaultProps = { - name: '', -} -ImageSwatch.propTypes = { - /** Full Path, URL, Base64 or Preview String object of Image file */ - src: type.UrlOrBase64OrPreview.isRequired, - small: type.Boolean, - large: type.Boolean, - className: type.String, - style: type.Object, -} - -export default React.memo(ImageSwatch) diff --git a/src/core/components/MenuButton.js b/src/core/components/MenuButton.js deleted file mode 100644 index 0b98a108..00000000 --- a/src/core/components/MenuButton.js +++ /dev/null @@ -1,37 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React from 'react' -import Button from './Button' - -/** - * Hamburger Style Button animating into Cross - Pure Component. - * - * @param {Boolean} [active] - whether to render the cross - * @param {String} [className] - optional css class name - * @param {*} [props] - other props - * @returns {Object} - React button component - */ -export function MenuButton ({ - active, - className, - ...props -}) { - return ( - - ) -} - -MenuButton.propTypes = { - active: PropTypes.bool, - className: PropTypes.string, -} - -export default React.memo(MenuButton) diff --git a/src/core/components/Square.js b/src/core/components/Square.js deleted file mode 100644 index f067d329..00000000 --- a/src/core/components/Square.js +++ /dev/null @@ -1,142 +0,0 @@ -import classNames from '../utils/classNames' -import PropTypes from 'prop-types' -import React from 'react' -import { SizeMe } from './utils/SizeMe' -import Row from './Row' -import View from './View' - -/** - * Square Container (using pure CSS, may not work on some flex layout or browsers, like Safari) - Pure Component - * (Render inner content as a square, resizing to fit dynamic width and height from parent) - * @Note: alternatively use @asSquare decorator, which works cross platform, but may not resize when screen shrinks - * @Use: or - */ -export function SquareRender ({ - top, right, bottom, left, - children, className, classNameInner, styleInner, - wScale = 1, hScale = 1, // ratio used to render rectangle (must be whole integer), default is square - ...p -}) { - const positioning = position({top, right, bottom, left}) - return ( - - - - - - - - {children} - - - - - - ) -} - -const Square = React.memo(SquareRender) -Square.propTypes = { - center: PropTypes.bool, - middle: PropTypes.bool, - right: PropTypes.bool, - bottom: PropTypes.bool, - className: PropTypes.string, - classNameInner: PropTypes.string, - styleInner: PropTypes.string, - children: PropTypes.any -} - -Square.View = asSquare(View) -Square.Row = asSquare(Row) - -export default Square - -/** - * React Component Decorator (HOC) to Render as Square/Rectangle, taking all available width and height - * @todo: fix - does not resize back when screen is smaller if wScale and hScale defined - * @example: - * @asSquare - * class UploadGrid extends Component { - * //... - * } - * - * @param {Class|Function|MemoExoticComponent|NamedExoticComponent} Component - to render as square - */ -export function asSquare (Component) { - function Square ({top, right, bottom, left, wScale, hScale, className, ...props}) { - const ratio = wScale && hScale ? wScale / hScale : 1 - const isRectangle = ratio !== 1 - return { - const smallerSize = Math.floor(Math.min(width || height, height || width)) - const style = {width: smallerSize, height: smallerSize} // square case - - // Rectangle case - if (isRectangle) { - const maxWidth = Math.floor(width || height) - const maxHeight = Math.floor(height || width) - // First take full available width - style.width = maxWidth - style.height = Math.floor(style.width / ratio) - // Then resize back if needed - if (style.height > maxHeight) { - style.height = maxHeight - style.width = Math.floor(style.height * ratio) - } - } - - // SizeMe will attempt to render placeholder (wrapped component) first to detect width and height. - // The placeholder till take className and style applied to wrapped component. - // We need two wrapper components to minimize re-renders, because React does not allow mutating this.props - // @Note: filling child element of `square-wrap` causes major UI lagging in Chrome (example UploadGrid) - // The fix is to make immediate child positioned absolute within the square. - return - - - - - }}/> - } - - return React.memo(Square) -} - -function position ({top, right, bottom, left}) { - return {middle: !bottom && !top, center: !left && !right, top, right, bottom, left} -} - -/** - * React Component Decorator (HOC) to Render as compact Square, taking all available width - * @example: - * @asSquareFullWidth - * class UploadGrid extends Component { - * //... - * } - * - * @param {Class|Function|MemoExoticComponent|NamedExoticComponent} Component - to render as square - */ -export function asSquareFullWidth (Component) { - // SizeMe will attempt to render placeholder (wrapped component) first to detect width and height. - // The placeholder till take className and style applied to wrapped component. - return function SquareWrapper ({style: s, ...props}) { - return ( - { - height = width = width || 0 // initially width and height are undefined - const fill = !(width && height) - // On subsequent renders, the wrapper has no styling, to allow full control of Component styles - let style = s - if (!fill) { // only create new style object when needed to minimze rendering - if (!s || s.width !== width || s.height !== height) style = {width, height, ...s} // allow custome override - } - return ( - /* Wrapper is required, because fixed width/height style applied prevents further resize calculation */ - // For wrapper only use fill, so it collapses if Component has position-absolute - - - - ) - }}/> - ) - } -} - diff --git a/src/core/components/TabList.js b/src/core/components/TabList.js deleted file mode 100644 index 77184fe0..00000000 --- a/src/core/components/TabList.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Tabs from './Tabs' - -/** - * Dynamic List of Tabs - Pure Component. - */ -export function TabList ({renderLabel, renderItem, items, ...props}) { - return ({tab: renderLabel(item, i), content: renderItem(item, i)}))} - /> -} - -TabList.propTypes = { - items: PropTypes.array.isRequired, - renderLabel: PropTypes.func.isRequired, - renderItem: PropTypes.func.isRequired, -} - -export default React.memo(TabList) diff --git a/src/core/components/Tags.js b/src/core/components/Tags.js deleted file mode 100644 index 13a85e7d..00000000 --- a/src/core/components/Tags.js +++ /dev/null @@ -1,48 +0,0 @@ -import cn from '../utils/classNames' -import PropTypes from 'prop-types' -import React from 'react' -import { isEmpty } from '../utils' -import Icon from './Icon' -import Row from './Row' -import Text from './Text' -import View from './View' - -// List of Tags -export function Tags ({ - items, - defByCode, // example: tag.select.tagById(state) - label, - className, - classNameInner, - classNameItem, - onClick, - ...props -}) { - if (isEmpty(items)) return null - const Container = label ? View : Row - return ( - - {label && {label}} - - {!label && } - {items.map(tag => ( - onClick && onClick(tag)} - > - {(defByCode[tag] || {}).name} - - ))} - - - ) -} - -Tags.propTypes = { - items: PropTypes.arrayOf(PropTypes.string).isRequired, - defByCode: PropTypes.object.isRequired, - label: PropTypes.string, -} - -export default React.memo(Tags) diff --git a/src/core/utils/__tests__/time.formatting-and-conversion.test.js b/src/core/utils/__tests__/time.formatting-and-conversion.test.js index c229bfdf..580486ee 100644 --- a/src/core/utils/__tests__/time.formatting-and-conversion.test.js +++ b/src/core/utils/__tests__/time.formatting-and-conversion.test.js @@ -1,4 +1,4 @@ -import { formatDuration, formatTime, toHours } from '../time' +import { formatDuration } from '../time' describe('formatDuration migration contract', () => { const units = [ @@ -97,46 +97,3 @@ describe('formatDuration.shortEnglish migration contract', () => { expect(formatDuration.shortEnglish(1000, null)).toBe('1 s') }) }) - -describe('formatTime migration contract', () => { - it('uses the full documented default format', () => { - const time = new Date(2024, 5, 15, 14, 30) - - expect(formatTime(time)).toBe('Sat, 15 Jun - 02:30 pm') - }) - - it('supports deterministic custom Moment format tokens', () => { - const time = new Date(2024, 5, 15, 14, 30, 45, 123) - - expect(formatTime(time, 'YYYY-MM-DD HH:mm:ss.SSS')).toBe('2024-06-15 14:30:45.123') - expect(formatTime(time, '[quarter:]Q [day:]DD')).toBe('quarter:2 day:15') - }) - - it('accepts millisecond timestamps without interpreting them as Unix seconds', () => { - expect(formatTime(0, 'x')).toBe('0') - expect(formatTime(1718451045123, 'x')).toBe('1718451045123') - }) - - it('returns the Moment invalid-date sentinel for invalid values', () => { - expect(formatTime(Number.NaN, 'YYYY-MM-DD')).toBe('Invalid date') - expect(formatTime(null, 'YYYY-MM-DD')).toBe('Invalid date') - }) -}) - -describe('toHours migration contract', () => { - it.each([ - [0, '12 am'], - [9, '9 am'], - [12, '12 pm'], - [23, '11 pm'], - ])('formats local hour %i using the 12-hour clock', (hour, expected) => { - expect(toHours(new Date(2024, 5, 15, hour, 59))).toBe(expected) - }) - - it('accepts a numeric timestamp and returns invalid values unchanged by the wrapper', () => { - const timestamp = new Date(2024, 5, 15, 22, 30).getTime() - - expect(toHours(timestamp)).toBe('10 pm') - expect(toHours(Number.NaN)).toBe('Invalid date') - }) -}) diff --git a/src/core/utils/__tests__/time.test.js b/src/core/utils/__tests__/time.test.js index 601f6a38..f6ed32e5 100644 --- a/src/core/utils/__tests__/time.test.js +++ b/src/core/utils/__tests__/time.test.js @@ -1,4 +1,4 @@ -import { formatDuration, formatTime, toHours } from '../time' +import { formatDuration } from '../time' describe('formatDuration', () => { it('returns "0 seconds" for zero milliseconds', () => { @@ -43,24 +43,3 @@ describe('formatDuration.shortEnglish', () => { expect(formatDuration.shortEnglish(60000)).toBe('1 m') }) }) - -describe('formatTime', () => { - it('formats a Date with the default format', () => { - // 2024-06-15 14:30:00 local - const t = new Date(2024, 5, 15, 14, 30, 0) - const out = formatTime(t) - expect(out).toMatch(/Sat, 15 Jun - \d{2}:\d{2} (a|p)m/) - }) - - it('honors a custom format', () => { - const t = new Date(2024, 5, 15) - expect(formatTime(t, 'YYYY-MM-DD')).toBe('2024-06-15') - }) -}) - -describe('toHours', () => { - it('renders short hour-meridian form', () => { - const t = new Date(2024, 5, 15, 9, 30, 0) - expect(toHours(t)).toBe('9 am') - }) -}) diff --git a/src/core/utils/time.js b/src/core/utils/time.js index 8feca022..5b569f02 100644 --- a/src/core/utils/time.js +++ b/src/core/utils/time.js @@ -1,6 +1,3 @@ -import moment from 'moment' -import { FORMAT_TIME_FOR_HUMAN } from './constants.js' - /** * TIME FUNCTIONS ============================================================== * ============================================================================= @@ -71,24 +68,3 @@ export function formatDuration (milliseconds, { formatDuration.shortEnglish = function (milliseconds, options = {}) { return formatDuration(milliseconds, { ...options, shorten: true }) } - -/** - * Convert time to human readable date and time - * - * @param {String|Number|Date} time - date string, Unix timestamp, Date object, etc. - * @param {String} [format] - to render date time as - * @returns {String} - date time in given format - */ -export function formatTime (time, format = FORMAT_TIME_FOR_HUMAN) { - return moment(time).format(format) -} - -/** - * Convert time to full hour string - * - * @param {String|Number|Date} time - date string, Unix timestamp, Date object, etc. - * @return {String} - hours and minutes - */ -export function toHours (time) { - return formatTime(time, 'h a') -} diff --git a/src/style/fonts/icons/Read Me.txt b/src/style/fonts/icons/Read Me.txt deleted file mode 100644 index 8491652f..00000000 --- a/src/style/fonts/icons/Read Me.txt +++ /dev/null @@ -1,7 +0,0 @@ -Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. - -To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts - -You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. - -You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. diff --git a/src/style/fonts/icons/demo-files/demo.css b/src/style/fonts/icons/demo-files/demo.css deleted file mode 100644 index 2da1d6d7..00000000 --- a/src/style/fonts/icons/demo-files/demo.css +++ /dev/null @@ -1,155 +0,0 @@ -body { - padding: 0; - margin: 0; - font-family: sans-serif; - font-size: 1em; - line-height: 1.5; - color: #555; - background: #fff; -} -h1 { - font-size: 1.5em; - font-weight: normal; -} -small { - font-size: .66666667em; -} -a { - color: #e74c3c; - text-decoration: none; -} -a:hover, a:focus { - box-shadow: 0 1px #e74c3c; -} -.bshadow0, input { - box-shadow: inset 0 -2px #e7e7e7; -} -input:hover { - box-shadow: inset 0 -2px #ccc; -} -input, fieldset { - font-family: sans-serif; - font-size: 1em; - margin: 0; - padding: 0; - border: 0; -} -input { - color: inherit; - line-height: 1.5; - height: 1.5em; - padding: .25em 0; -} -input:focus { - outline: none; - box-shadow: inset 0 -2px #449fdb; -} -.glyph { - font-size: 16px; - width: 15em; - padding-bottom: 1em; - margin-right: 4em; - margin-bottom: 1em; - float: left; - overflow: hidden; -} -.liga { - width: 80%; - width: calc(100% - 2.5em); -} -.talign-right { - text-align: right; -} -.talign-center { - text-align: center; -} -.bgc1 { - background: #f1f1f1; -} -.fgc1 { - color: #999; -} -.fgc0 { - color: #000; -} -p { - margin-top: 1em; - margin-bottom: 1em; -} -.mvm { - margin-top: .75em; - margin-bottom: .75em; -} -.mtn { - margin-top: 0; -} -.mtl, .mal { - margin-top: 1.5em; -} -.mbl, .mal { - margin-bottom: 1.5em; -} -.mal, .mhl { - margin-left: 1.5em; - margin-right: 1.5em; -} -.mhmm { - margin-left: 1em; - margin-right: 1em; -} -.mls { - margin-left: .25em; -} -.ptl { - padding-top: 1.5em; -} -.pbs, .pvs { - padding-bottom: .25em; -} -.pvs, .pts { - padding-top: .25em; -} -.unit { - float: left; -} -.unitRight { - float: right; -} -.size1of2 { - width: 50%; -} -.size1of1 { - width: 100%; -} -.clearfix:before, .clearfix:after { - content: " "; - display: table; -} -.clearfix:after { - clear: both; -} -.hidden-true { - display: none; -} -.textbox0 { - width: 3em; - background: #f1f1f1; - padding: .25em .5em; - line-height: 1.5; - height: 1.5em; -} -#testDrive { - display: block; - padding-top: 24px; - line-height: 1.5; -} -.fs0 { - font-size: 16px; -} -.fs1 { - font-size: 28px; -} -.fs2 { - font-size: 32px; -} - diff --git a/src/style/fonts/icons/demo-files/demo.js b/src/style/fonts/icons/demo-files/demo.js deleted file mode 100644 index 6f45f1c4..00000000 --- a/src/style/fonts/icons/demo-files/demo.js +++ /dev/null @@ -1,30 +0,0 @@ -if (!('boxShadow' in document.body.style)) { - document.body.setAttribute('class', 'noBoxShadow'); -} - -document.body.addEventListener("click", function(e) { - var target = e.target; - if (target.tagName === "INPUT" && - target.getAttribute('class').indexOf('liga') === -1) { - target.select(); - } -}); - -(function() { - var fontSize = document.getElementById('fontSize'), - testDrive = document.getElementById('testDrive'), - testText = document.getElementById('testText'); - function updateTest() { - testDrive.innerHTML = testText.value || String.fromCharCode(160); - if (window.icomoonLiga) { - window.icomoonLiga(testDrive); - } - } - function updateSize() { - testDrive.style.fontSize = fontSize.value + 'px'; - } - fontSize.addEventListener('change', updateSize, false); - testText.addEventListener('input', updateTest, false); - testText.addEventListener('change', updateTest, false); - updateSize(); -}()); diff --git a/src/style/fonts/icons/demo.html b/src/style/fonts/icons/demo.html deleted file mode 100644 index b93ab20a..00000000 --- a/src/style/fonts/icons/demo.html +++ /dev/null @@ -1,1231 +0,0 @@ - - - - - IcoMoon Demo - - - - - -
-

Font Name: iconsOpenL (Glyphs: 85)

-
-
-

Grid Size: 14

-
-
- - icon-trash -
-
- - -
-
- liga: - -
-
-
-
-

Grid Size: 16

-
-
- - icon-logo -
-
- - -
-
- liga: - -
-
-
-
- - icon-logo-symbol -
-
- - -
-
- liga: - -
-
-
-
- - icon-logo-brand -
-
- - -
-
- liga: - -
-
-
-
- - icon-add-circle -
-
- - -
-
- liga: - -
-
-
-
- - icon-add -
-
- - -
-
- liga: - -
-
-
-
- - icon-analytics -
-
- - -
-
- liga: - -
-
-
-
- - icon-arrow-down -
-
- - -
-
- liga: - -
-
-
-
- - icon-arrow-left -
-
- - -
-
- liga: - -
-
-
-
- - icon-arrow-right -
-
- - -
-
- liga: - -
-
-
-
- - icon-arrow-up -
-
- - -
-
- liga: - -
-
-
-
- - icon-bell-outline -
-
- - -
-
- liga: - -
-
-
-
- - icon-bell -
-
- - -
-
- liga: - -
-
-
-
- - icon-bookmark-outline -
-
- - -
-
- liga: - -
-
-
-
- - icon-bookmark -
-
- - -
-
- liga: - -
-
-
-
- - icon-briefcase -
-
- - -
-
- liga: - -
-
-
-
- - icon-camera -
-
- - -
-
- liga: - -
-
-
-
- - icon-caret-down -
-
- - -
-
- liga: - -
-
-
-
- - icon-caret-up -
-
- - -
-
- liga: - -
-
-
-
- - icon-chat -
-
- - -
-
- liga: - -
-
-
-
- - icon-check-circle -
-
- - -
-
- liga: - -
-
-
-
- - icon-checkbox-filled -
-
- - -
-
- liga: - -
-
-
-
- - icon-checkbox -
-
- - -
-
- liga: - -
-
-
-
- - icon-delete -
-
- - -
-
- liga: - -
-
-
-
- - icon-dropdown-spinner -
-
- - -
-
- liga: - -
-
-
-
- - icon-dropdown-chevron -
-
- - -
-
- liga: - -
-
-
-
- - icon-dropdown-delete -
-
- - -
-
- liga: - -
-
-
-
- - icon-checkmark -
-
- - -
-
- liga: - -
-
-
-
- - icon-chevron-down -
-
- - -
-
- liga: - -
-
-
-
- - icon-chevron-left -
-
- - -
-
- liga: - -
-
-
-
- - icon-chevron-right -
-
- - -
-
- liga: - -
-
-
-
- - icon-chevron-up -
-
- - -
-
- liga: - -
-
-
-
- - icon-clock -
-
- - -
-
- liga: - -
-
-
-
- - icon-close-circle -
-
- - -
-
- liga: - -
-
-
-
- - icon-close -
-
- - -
-
- liga: - -
-
-
-
- - icon-compass-arrow -
-
- - -
-
- liga: - -
-
-
-
- - icon-compass-target -
-
- - -
-
- liga: - -
-
-
-
- - icon-credit-card -
-
- - -
-
- liga: - -
-
-
-
- - icon-disabled -
-
- - -
-
- liga: - -
-
-
-
- - icon-edit -
-
- - -
-
- liga: - -
-
-
-
- - icon-elipses -
-
- - -
-
- liga: - -
-
-
-
- - icon-envelop-filled -
-
- - -
-
- liga: - -
-
-
-
- - icon-envelope-outline -
-
- - -
-
- liga: - -
-
-
-
- - icon-error -
-
- - -
-
- liga: - -
-
-
-
- - icon-eye -
-
- - -
-
- liga: - -
-
-
-
- - icon-file-download -
-
- - -
-
- liga: - -
-
-
-
- - icon-file-upload -
-
- - -
-
- liga: - -
-
-
-
- - icon-filter -
-
- - -
-
- liga: - -
-
-
-
- - icon-gear -
-
- - -
-
- liga: - -
-
-
-
- - icon-heart-outline -
-
- - -
-
- liga: - -
-
-
-
- - icon-heart -
-
- - -
-
- liga: - -
-
-
-
- - icon-help -
-
- - -
-
- liga: - -
-
-
-
- - icon-home -
-
- - -
-
- liga: - -
-
-
-
- - icon-image -
-
- - -
-
- liga: - -
-
-
-
- - icon-info -
-
- - -
-
- liga: - -
-
-
-
- - icon-lock-filled -
-
- - -
-
- liga: - -
-
-
-
- - icon-lock-open -
-
- - -
-
- liga: - -
-
-
-
- - icon-lock -
-
- - -
-
- liga: - -
-
-
-
- - icon-map-pin -
-
- - -
-
- liga: - -
-
-
-
- - icon-menu -
-
- - -
-
- liga: - -
-
-
-
- - icon-mobile -
-
- - -
-
- liga: - -
-
-
-
- - icon-notes -
-
- - -
-
- liga: - -
-
-
-
- - icon-paperclip -
-
- - -
-
- liga: - -
-
-
-
- - icon-person -
-
- - -
-
- liga: - -
-
-
-
- - icon-phone -
-
- - -
-
- liga: - -
-
-
-
- - icon-play-circle -
-
- - -
-
- liga: - -
-
-
-
- - icon-play -
-
- - -
-
- liga: - -
-
-
-
- - icon-print -
-
- - -
-
- liga: - -
-
-
-
- - icon-radio-blank -
-
- - -
-
- liga: - -
-
-
-
- - icon-radio-filled -
-
- - -
-
- liga: - -
-
-
-
- - icon-refresh -
-
- - -
-
- liga: - -
-
-
-
- - icon-search -
-
- - -
-
- liga: - -
-
-
-
- - icon-shuffle -
-
- - -
-
- liga: - -
-
-
-
- - icon-star -
-
- - -
-
- liga: - -
-
-
-
- - icon-tasks -
-
- - -
-
- liga: - -
-
-
-
- - icon-thumbs-down -
-
- - -
-
- liga: - -
-
-
-
- - icon-thumbs-up -
-
- - -
-
- liga: - -
-
-
-
- - icon-time -
-
- - -
-
- liga: - -
-
-
-
- - icon-warning -
-
- - -
-
- liga: - -
-
-
-
- - icon-desc -
-
- - -
-
- liga: - -
-
-
-
- - icon-asc -
-
- - -
-
- liga: - -
-
-
-
- - icon-sort -
-
- - -
-
- liga: - -
-
-
-
- - icon-dollar -
-
- - -
-
- liga: - -
-
-
-
- - icon-theme -
-
- - -
-
- liga: - -
-
-
-
- - icon-test -
-
- - -
-
- liga: - -
-
-
- - -
-

Font Test Drive

- - -
  -
-
- -
-

Generated by IcoMoon

-
- - - - diff --git a/src/style/unused/_classic.less b/src/style/unused/_classic.less deleted file mode 100644 index 5d5475e5..00000000 --- a/src/style/unused/_classic.less +++ /dev/null @@ -1,19 +0,0 @@ -// ============================================================================= -// Main LESS CSS File for Classic UI Integration -// ============================================================================= - -@import "_all"; -@import "_mixins"; -@import "_classic_variables"; - -// ----------------------------------------------------------------------------- -// CLASSIC STYLE FIXES -// ----------------------------------------------------------------------------- - -// Keep circular buttons as circle, without background image -button { - &[type="button"] { - height: auto; - background-image: inherit; - } -} diff --git a/src/style/unused/_classic_variables.less b/src/style/unused/_classic_variables.less deleted file mode 100644 index e2936969..00000000 --- a/src/style/unused/_classic_variables.less +++ /dev/null @@ -1,74 +0,0 @@ -// ============================================================================= -// VARIABLES OVERRIDES -// Classic has default html root font-size at 12px -// => default font-size is 1rem, size-scale is 75% -// ============================================================================= - -// Sizes -@size-scale: 75%; -@size-base: 1rem; // default text size, used to scale all other sizes, like button, etc. -@size-base-px: px(@size-base); -@size-base-space: size(round(@size-base-px * 1/4)); // character spacing -@size-smallest: size(round(11/16 * @size-base-px)); // round to whole pixel, then convert to rem -@size-smaller: size(round(12/16 * @size-base-px)); -@size-small: size(round(14/16 * @size-base-px)); -@size-large: size(round(20/16 * @size-base-px)); -@size-larger: size(round(24/16 * @size-base-px)); -@size-largest: size(round(32/16 * @size-base-px)); -@size-h6: size(round(18/16 * @size-base-px)); // actual header sizes will be converted to % -@size-h5: size(round(20/16 * @size-base-px)); -@size-h4: size(round(24/16 * @size-base-px)); -@size-h3: size(round(24/16 * @size-base-px)); -@size-h2: size(round(28/16 * @size-base-px)); -@size-h1: size(round(36/16 * @size-base-px)); - -// Icons -@size-icon: 1rem; // Custom icon fonts http://icomoon.io/app/#/select -@size-icon-px: px(@size-icon); -@size-icon-smallest: size(round(12/16 * @size-icon-px)); -@size-icon-smaller: size(round(14/16 * @size-icon-px)); -@size-icon-small: size(round(16/16 * @size-icon-px)); -@size-icon-large: size(round(24/16 * @size-icon-px)); -@size-icon-larger: size(round(32/16 * @size-icon-px)); -@size-icon-largest: size(round(40/16 * @size-icon-px)); - -// Line Heights -@line-height-scale: 1.37; // 1.37 works for Open Sans (use unitless number to scale dynamically) -@line-height-base: size(round(@line-height-scale * @size-base-px)); // round to whole pixel for crisp display -@line-height-small: size(round(@line-height-scale * px(@size-small))); // round to whole pixel for crisp display - - -// ----------------------------------------------------------------------------- -// LAYOUT -// ----------------------------------------------------------------------------- - -// Margin -@margin-base: 1rem; -@margin-smallest: (@margin-base * 4 /16); -@margin-smaller: (@margin-base * 8 /16); -@margin-small: (@margin-base * 12 /16); -@margin-large: (@margin-base * 20 /16); -@margin-larger: (@margin-base + 24 /16); -@margin-largest: (@margin-base * 28 /16); - -// Padding -@padding-base: @margin-base; -@padding-smallest: @margin-smallest; -@padding-smaller: @margin-smaller; -@padding-small: @margin-small; -@padding-large: @margin-large; -@padding-larger: @margin-larger; -@padding-largest: @margin-largest; - -// Border -@border-size: size(1); -@border-size-thick: @border-size * 2; -@border: @border-size @border-style; -@border-base: @border @color-border; -@border-base-light: @border @color-border-light; -@border-base-dark: @border @color-border-dark; -@radius-base: size(4); -@radius-small: size(3); -@radius-large: size(7); -@radius-larger: size(10); -@radius-largest: size(13); diff --git a/src/style/unused/_policy.less b/src/style/unused/_policy.less deleted file mode 100644 index 05e96384..00000000 --- a/src/style/unused/_policy.less +++ /dev/null @@ -1,11 +0,0 @@ -// ============================================================================= -// Main LESS CSS File for Classic UI Integration -// ============================================================================= - -@import "_all"; -@import "_mixins"; -@import "_policy_variables"; - -// ----------------------------------------------------------------------------- -// APP UI STYLE FIXES -// ----------------------------------------------------------------------------- diff --git a/src/style/unused/_policy_variables.less b/src/style/unused/_policy_variables.less deleted file mode 100644 index e69de29b..00000000 diff --git a/src/style/unused/avatar.less b/src/style/unused/avatar.less deleted file mode 100644 index 3141b22e..00000000 --- a/src/style/unused/avatar.less +++ /dev/null @@ -1,153 +0,0 @@ -@import "_mixins"; - -@avatar-border: @border @color-text-inverse; - -.app__profile { - &:extend(.no-outline all); - - .app__progress__stats { - background-color: @color-shadow-dark; - } -} - -.app__avatar { - @status-size: size(12); - &:extend(.flex--col.center all); - &:extend(.no-outline all); - &:extend(.transition all); - - &.online { - .app__avatar__inner::after { - @offset: (@size-avatar * 0.15 - @status-size / 2); - - top: @offset; - right: @offset; - .circle(@status-size); - - position: absolute; - content: ' '; - border: @avatar-border; - } - - &.large .app__avatar__inner::after { - @offset: (@size-avatar-large * 0.15 - @status-size / 2); - - top: @offset; - right: @offset; - } - } - - &.online .app__avatar__inner::after { - background-color: @color-green; - } - - &.interact { - @{select-interaction} { - .app__avatar__inner::after, .app__avatar__image { - border-color: @color-primary-light; - } - - .app__avatar__image { - &:extend(.flash-text all); - } - } - - // Show Camera Icon on interaction with shadow background behind - .app__profile & { - @{select-interaction} { - .app__avatar__image { - &::after { - &:extend(.position-center all); - .circle(@size-avatar); - - content: ' '; - background-color: @color-shadow-dark; - } - - > .placeholder { - opacity: 0; - } - } - - &::after { - &:extend(.position-center all); - &:extend(.fade-in all); - .icon(@camera); - - margin-left: size(1); // offset icon alignment - font-size: size(round(px(@size-avatar) * 0.4)); - } - } - } - } -} - -.app__avatar__inner { - &::before { - @scale: 0.7; - - .app__avatar.fetching & { - &:extend(.position-center all); - .icon(@picture); - - color: @color-neutral; - font-size: @size-avatar * @scale; - } - - .app__avatar.small & { - font-size: @size-avatar-small * @scale; - } - - .app__avatar.large & { - font-size: @size-avatar-large * @scale; - } - - .app__avatar.larger & { - font-size: @size-avatar-larger * @scale; - } - } -} - -.app__avatar__image { - .circle(@size-avatar); - &:extend(.text.bold all); - &:extend(.overflow-hidden all); - &:extend(.bg-image all); - - font-size: size(round(px(@size-avatar) * 0.8)); - text-transform: uppercase; - border: @avatar-border; - - .placeholder { - &:extend(.transition all); - - @{select-icon} { - &:extend(.flex--col); - &:extend(.no-margin all); - - font-size: 75%; - } - } - - .app__profile & { - border-color: @color-primary-light; - } - - .app__avatar.small & { - .square(@size-avatar-small); - - font-size: size(round(px(@size-avatar-small) * 0.8)); - } - - .app__avatar.large & { - .square(@size-avatar-large); - - font-size: size(round(px(@size-avatar-large) * 0.8)); - } - - .app__avatar.larger & { - .square(@size-avatar-larger); - - font-size: size(round(px(@size-avatar-larger) * 0.8)); - } -} diff --git a/src/style/unused/carousel.less b/src/style/unused/carousel.less deleted file mode 100644 index 6ba01835..00000000 --- a/src/style/unused/carousel.less +++ /dev/null @@ -1,119 +0,0 @@ -@import "_mixins"; - -@carousel-size-control-icon: @size-icon-largest; - -.carousel { - &:extend(.radius-largest all); - - overflow: hidden; -} - -.carousel__image { - &:extend(.bg-image all); - &:extend(.radius-large all); -} - -.carousel__image__count, -.carousel__image__date { - &:extend(.padding all); - &:extend(.text-shadow-soft all); - - color: @color-white; -} - -.carousel__image__date { - &:extend(.position-top-right all); -} - -.carousel__image__count { - &:extend(.position-top-left all); -} - -.carousel__control { - &:extend(.position-absolute all); - - width: 50%; - height: 100%; - - &.icon { - &:extend(.text-shadow-soft all); - &:extend(.no-outline all); - - color: @color-white; - font-size: @carousel-size-control-icon; - opacity: 0.65; - - &::before { - &:extend(.position-middle all); - &:extend(.padding-smaller all); - } - - @{select-interaction} { - opacity: 1; - } - } - - &.prev { - left: 0; - - &::before { - left: 0; - } - @{select-interaction} { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - } - - &.next { - right: 0; - - &::before { - right: 0; - } - @{select-interaction} { - border-top-left-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - } -} - -.carousel__items { - margin-top: @margin-smallest; - - @media screen and (min-width: @screen-desktop) { - margin-top: @margin-small; - } -} - -.carousel__item { - @clickColor: @color-secondary; - @clickContrastColor: @color-white; - &:extend(.bg-image all); - &:extend(.radius-large all); - .square(size(40)); - - border: @border transparent; - - &.active { - border-color: @clickColor; - box-shadow: inset 0 0 0 1px @clickContrastColor;; - } - - & + .carousel__item { - margin-left: @margin-smallest; - } - - &:focus { - .outline(@color: @clickContrastColor); - } - - - @media screen and (min-width: @screen-desktop) { - .square(size(70)); - - & + .carousel__item { - margin-left: @margin-small; - } - } -} diff --git a/src/style/unused/chat.less b/src/style/unused/chat.less deleted file mode 100644 index e892a025..00000000 --- a/src/style/unused/chat.less +++ /dev/null @@ -1,308 +0,0 @@ -@import "_mixins"; - -@chat-users-width: 35%; -@chat-users-screen-size: @screen-tablet; // breakpoint - -.app__chat, -.chat, -.chat__users, -.chat__view { - &:extend(.max-size all); // for ScrollView -} - - -@media screen and (min-width: @chat-users-screen-size) { - .chat__view { - width: 100% - @chat-users-width; - } -} - -.chat__header { - &:extend(.padding-h-large all); - &:extend(.padding-v-small all); -} - -.chat__event { - &:extend(.bg-grey-light all); - &:extend(.border-gradient-h all); - - .app__collapse__toggle { - @radius: 0 0 @radius-round @radius-round !important; - @padding: 0 size(10) size(5) !important; - &:extend(.position-absolute); - - bottom: 0; - left: 50%; - transform: translate(-50%, 100%); - z-index: 1; - border-radius: @radius; - - @{select-interaction} { - border-radius: @radius; - - .chat__event__toggle { - background-color: @color-bg-white; - } - } - - .chat__event__toggle { - padding: @padding; - background-color: @color-bg-grey-light; - border-radius: @radius; - } - } - - .event--summary { - box-shadow: none; - border-radius: 0; - &:extend(.no-margin); - &:extend(.padding-h-large); - - &:not(.active) { - &:extend(.bg-transparent all); - } - - + .event--summary { - &:extend(.border-gradient-left-grey all); - &:extend(.no-margin-left); - - padding-left: @padding-largest; - } - } - - .carousel, .carousel__image { - border-radius: 0; - } -} - -.chat__event__carousel { - @margin-offset-control: @carousel-size-control-icon + @padding-smaller; - &:extend(.padding-top); - &:extend(.padding-bottom-smaller); - .margin-h(@margin-offset-control); - - max-width: calc(100% - @margin-offset-control * 2); - - .inverted & { - &:extend(.text-shadow-soft all); - } -} - -.chat__messages { - &:extend(.padding-v all); - &:extend(.flex--col.center all); - &:extend(.bg-grey all); - &:extend(.bg-texture all); - - transform: rotate(180deg); - direction: rtl; - - //&:before { - // &:extend(.position-fill all); - // .background-image(@name: 'diamonds.png'; @size: 120px 108px;); - // .object(); - // background-attachment: fixed !important; - // transform: rotate(180deg); - // opacity: 0.04; - // z-index: -1; - //} - - > .min-height { - width: 100%; - max-width: size(700); - } -} - -.chat__message { - transform: rotate(180deg); - direction: ltr; - max-width: size(400); - align-self: flex-start; - border-color: @color-bg; // for pointer - &:extend(.bg-base all); - &:extend(.radius-large all); - &:extend(.margin-h-large all); - &:extend(.margin-top-smallest all); - &:extend(.padding-v-small all); - &:extend(.padding-h all); - &:extend(.box-shadow all); - - &.self { - &:extend(.bg-green-light all); - - border-color: @color-bg-green-light; // for pointer - align-self: flex-end; - margin-right: @margin-large * 2; - - + .chat__message:not(.self) { - &:extend(.margin-top-largest all); - - border-bottom-left-radius: 0; - - &::after { - &:extend(.bubble-triangle); - &:extend(.bubble-triangle.left); - } - } - } - - &:not(.self) { - margin-left: @margin-large * 2; - - + .chat__message.self { - &:extend(.margin-top-largest all); - - border-bottom-right-radius: 0; - - &::after { - &:extend(.bubble-triangle); - &:extend(.bubble-triangle.right); - } - } - } -} - -.chat__message__content { - &:extend(.p all); -} - -.chat__message__content__space { - .object(inline-flex); - - width: @size-smaller * 4; - - .chat__message.sent &, - .chat__message.read & { - width: @size-smaller * 6; - } -} - -.chat__message__meta { - &:extend(.position-bottom-right all); - &:extend(.flex--row.bottom all); - &:extend(.padding-smaller all); -} - -.chat__message__time { - &:extend(.text.smaller all); - &:extend(.fade all); - &:extend(.padding-right-smaller all); -} - -.chat__message__status { -} - -.chat__input { - &:extend(.padding all); - &:extend(.bg-base all); - &:extend(.border-gradient-top all); - - textarea { - &:extend(.chat-input-interaction all); - } - - i.icon-send { - font-size: @size-icon-large; - } -} - -.chat__users { - &:extend(.min-width-290 all); - &:extend(.max-width-430 all); - &:extend(.max-height all); - &:extend(.box-shadow-right all); - - z-index: 1; // for shadow - - @media screen and (min-width: @chat-users-screen-size) { - width: @chat-users-width; - } -} - -.chat__users__search { - &:extend(.padding-small all); - - input { - &:extend(.chat-input-interaction all); - } -} - -.chat__users__list { - &:extend(.no-scrollbar all); -} - -.chat__user { - &:extend(.padding all); - &:extend(.border-gradient-top-grey all); - - &.active { - &:extend(.bg-grey all); - } -} - -.chat__user__summary { - &:extend(.padding-top-smallest all); - &:extend(.padding-left all); - &:extend(.padding-right-small all); - - max-width: calc(100% - @size-avatar); // for text truncate to work - - .chat__message__unread { - &:extend(.margin-right-smaller); - } -} - -.chat__user__name { - &:extend(.bold all); -} - -.chat__user__message { - &:extend(.padding-smaller all); - &:extend(.no-padding-left all); - &:extend(.truncate all); - &:extend(.text.small all); -} - -.chat-input-interaction { - width: 100%; - border-color: transparent !important; - - @{select-interaction} { - &:extend(.no-outline all); - } -} - -.bubble-triangle { - @pointerSize: size(4); - .object(); - .square(0); - - top: 100%; - border-color: inherit; - border-style: solid; - border-width: @pointerSize (@pointerSize * 1.2); - border-bottom-color: transparent; - - &.left { - &:extend(.position-bottom-left); - - border-right-color: transparent; - } - - &.right { - &:extend(.position-bottom-right); - - border-left-color: transparent; - } -} - -i.icon-sent { - &:extend(.fade all); -} - -i.icon-read { - &:extend(.fade--quarter all); - &:extend(.margin-right-smallest all); - - color: @color-primary; -} diff --git a/src/style/unused/placeholder.less b/src/style/unused/placeholder.less deleted file mode 100644 index b4a22ff6..00000000 --- a/src/style/unused/placeholder.less +++ /dev/null @@ -1,47 +0,0 @@ -@import "_mixins"; -@import "_layout"; - -.app__placeholder { - &:extend(.fill); - .background-image('placeholder.jpg'; auto 100%) !important; - - .sun { - width: 12.7vh; - height: 13.3vh; - bottom: 62.5vh; - right: 10.7vh; - background: fade(@color-white, 80%); - transform: rotate(-33deg); - } - - .app__form { - .fade(0.8); - - margin-top: 27vh; - margin-left: 3vh; - - .float-label + .float-label { - margin-top: @margin-small; - } - - input { - border-color: @color-yellow-dark !important; - } - - label { - color: @color-yellow-dark !important; - } - } - - &.inverted { - h1 { - margin: @margin-base; - color: @color-orange-light; - .text-shadow(@opacity: 1; @blur: 7px); - } - - > h1 { - margin-top: 33vh; - } - } -} diff --git a/src/style/unused/reviews.less b/src/style/unused/reviews.less deleted file mode 100644 index a70380ca..00000000 --- a/src/style/unused/reviews.less +++ /dev/null @@ -1,55 +0,0 @@ -@import "_mixins"; - -.review { - &:extend(.padding-v all); - &:extend(.max-width-600 all); - - + .review { - &:extend(.border-gradient-top-grey-light); - } -} - -.review__user { - &:extend(.padding-top all); -} - -.review__text { - &:extend(.padding-v); - &:extend(.padding-h-smaller); - &:extend(.p all); - - text-align: left; -} - -.review__meta { - &:extend(.padding-left); -} - -.review__name { - &:extend(.bold); - &:extend(.padding-bottom-smallest); -} - -.review__time { - &:extend(.padding-left); - &:extend(.padding-right-smaller); - &:extend(.padding-bottom-smallest); - &:extend(.text.small); - &:extend(.fade); -} - -.stars { - color: @color-primary; - - .inverted & { - color: @color-primary-light; - } - - .icon { - .icon(); - } - - > span > span + span { - &:extend(.margin-left-smallest); - } -} diff --git a/src/style/unused/video.less b/src/style/unused/video.less deleted file mode 100644 index c8368f05..00000000 --- a/src/style/unused/video.less +++ /dev/null @@ -1,61 +0,0 @@ -@import "_mixins"; -@import "_layout"; - -.video { - &.placeholder { - .align-center; - .pointer; - - position: relative; - width: 300px; - height: 170px; - color: @color-black; - border: 3px solid @color-black; - border-radius: @radius-large; - - .inverted & { - color: @color-white; - border-color: @color-white; - } - - .icon-play { - .position-center; - .font-size(96); - - text-align: center; - } - - @media screen and (max-width: @screen-mobile) { - width: 260px; - height: 145px; - } - } - - &.embed { - position: relative; - width: 100%; - padding-bottom: 56.25%; - - /* 16:9 */ - padding-top: 25px; - - /* standard padding for YouTube */ - height: 0; - overflow: hidden; - - iframe { - /* remove width and height attributes from iframe in html */ - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - } - - &.wrapper { - border: 2px solid fade(@color-border, 50%); - border-radius: @radius-large; - overflow: hidden; - } -}