From c8a9eecaf89139bd80965f39052c1483b910d6bc Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Fri, 31 Jul 2026 06:49:06 +1000 Subject: [PATCH] feat(skin): improved responsive design (#1832) --- .../references/migration.md | 34 +- apps/e2e/tests/visual/video-skin.spec.ts | 2 +- .../src/define/audio/minimal-skin.tailwind.ts | 29 +- .../html/src/define/audio/minimal-skin.ts | 29 +- packages/html/src/define/audio/minimal-ui.ts | 18 +- .../html/src/define/audio/skin.tailwind.ts | 4 +- packages/html/src/define/audio/ui.ts | 11 +- .../live-audio/minimal-skin.tailwind.ts | 3 +- .../html/src/define/live-audio/minimal-ui.ts | 11 +- .../src/define/live-audio/skin.tailwind.ts | 3 +- packages/html/src/define/live-audio/ui.ts | 11 +- .../live-video/minimal-skin.tailwind.ts | 34 +- .../src/define/live-video/minimal-skin.ts | 12 +- .../html/src/define/live-video/minimal-ui.ts | 10 +- .../src/define/live-video/skin.tailwind.ts | 44 +- packages/html/src/define/live-video/skin.ts | 163 +++---- packages/html/src/define/live-video/ui.ts | 10 +- packages/html/src/define/ui/compounds.ts | 11 + .../src/define/video/minimal-skin.tailwind.ts | 72 ++- .../html/src/define/video/minimal-skin.ts | 57 +-- packages/html/src/define/video/minimal-ui.ts | 10 +- .../html/src/define/video/skin.tailwind.ts | 361 ++++++++------- packages/html/src/define/video/skin.ts | 419 +++++++++--------- packages/html/src/define/video/ui.ts | 10 +- .../presets/audio/minimal-skin.tailwind.tsx | 4 +- .../react/src/presets/audio/minimal-skin.tsx | 4 +- .../react/src/presets/audio/skin.tailwind.tsx | 4 +- .../live-audio/minimal-skin.tailwind.tsx | 3 +- .../src/presets/live-audio/skin.tailwind.tsx | 3 +- .../live-video/minimal-skin.tailwind.tsx | 11 +- .../src/presets/live-video/minimal-skin.tsx | 8 +- .../src/presets/live-video/skin.tailwind.tsx | 160 +++---- .../react/src/presets/live-video/skin.tsx | 160 +++---- .../presets/video/minimal-skin.tailwind.tsx | 74 +--- .../react/src/presets/video/minimal-skin.tsx | 57 +-- .../react/src/presets/video/skin.tailwind.tsx | 266 ++++++----- packages/react/src/presets/video/skin.tsx | 278 ++++++------ packages/skins/src/default/css/audio.css | 23 +- .../src/default/css/components/badge.css | 6 +- .../default/css/components/button-group.css | 6 +- .../src/default/css/components/button.css | 24 +- .../src/default/css/components/captions.css | 6 +- .../src/default/css/components/controls.css | 10 +- .../src/default/css/components/error.css | 2 +- .../default/css/components/input-feedback.css | 22 +- .../src/default/css/components/menus.css | 50 ++- .../src/default/css/components/popup.css | 18 +- .../skins/src/default/css/components/root.css | 28 +- .../src/default/css/components/slider.css | 364 ++++++++------- .../src/default/css/components/surface.css | 4 +- .../src/default/css/components/thumbnail.css | 6 +- .../skins/src/default/css/components/time.css | 11 +- packages/skins/src/default/css/video.css | 235 ++++++---- .../src/default/tailwind/audio.tailwind.ts | 21 +- .../src/default/tailwind/components/badge.ts | 2 +- .../tailwind/components/button-group.ts | 4 +- .../src/default/tailwind/components/button.ts | 10 +- .../default/tailwind/components/controls.ts | 8 +- .../src/default/tailwind/components/error.ts | 2 +- .../tailwind/components/input-feedback.ts | 6 +- .../src/default/tailwind/components/menu.ts | 23 +- .../src/default/tailwind/components/popup.ts | 4 +- .../src/default/tailwind/components/root.ts | 16 +- .../src/default/tailwind/components/seek.ts | 2 +- .../src/default/tailwind/components/slider.ts | 6 +- .../default/tailwind/components/surface.ts | 2 +- .../default/tailwind/components/thumbnail.ts | 4 +- .../src/default/tailwind/components/time.ts | 6 +- .../src/default/tailwind/video.tailwind.ts | 122 +++-- packages/skins/src/minimal/css/audio.css | 44 +- .../src/minimal/css/components/badge.css | 6 +- .../minimal/css/components/button-group.css | 6 +- .../src/minimal/css/components/button.css | 26 +- .../src/minimal/css/components/captions.css | 6 +- .../src/minimal/css/components/controls.css | 6 + .../src/minimal/css/components/error.css | 2 +- .../minimal/css/components/input-feedback.css | 35 +- .../src/minimal/css/components/menus.css | 50 ++- .../src/minimal/css/components/overlay.css | 7 +- .../src/minimal/css/components/popup.css | 18 +- .../skins/src/minimal/css/components/root.css | 25 +- .../src/minimal/css/components/slider.css | 322 +++++++------- .../src/minimal/css/components/thumbnail.css | 4 +- .../skins/src/minimal/css/components/time.css | 8 +- packages/skins/src/minimal/css/video.css | 135 +++--- .../src/minimal/tailwind/audio.tailwind.ts | 25 +- .../src/minimal/tailwind/components/badge.ts | 2 +- .../tailwind/components/button-group.ts | 4 +- .../src/minimal/tailwind/components/button.ts | 18 +- .../minimal/tailwind/components/controls.ts | 6 +- .../tailwind/components/input-feedback.ts | 12 +- .../src/minimal/tailwind/components/menu.ts | 23 +- .../minimal/tailwind/components/overlay.ts | 2 +- .../src/minimal/tailwind/components/popup.ts | 8 +- .../src/minimal/tailwind/components/root.ts | 14 +- .../src/minimal/tailwind/components/seek.ts | 2 +- .../src/minimal/tailwind/components/slider.ts | 4 +- .../minimal/tailwind/components/thumbnail.ts | 5 +- .../src/minimal/tailwind/components/time.ts | 6 +- .../src/minimal/tailwind/video.tailwind.ts | 87 ++-- packages/utils/src/dom/style.ts | 34 +- packages/utils/src/dom/tests/style.test.ts | 141 +++++- .../content/docs/concepts/ui-components.mdx | 2 +- 103 files changed, 2399 insertions(+), 2149 deletions(-) diff --git a/.agents/skills/migrate-css-to-tailwind/references/migration.md b/.agents/skills/migrate-css-to-tailwind/references/migration.md index f1271e79..50c06008 100644 --- a/.agents/skills/migrate-css-to-tailwind/references/migration.md +++ b/.agents/skills/migrate-css-to-tailwind/references/migration.md @@ -36,7 +36,25 @@ Legacy **`tailwind.config.js`** theme spreads are not the primary path here—ex - `border-radius: 8px` → prefer `rounded-lg` (or a theme radius key) if equivalent/acceptable - `font-size`, `spacing`, `colors`, `shadow`, `z-index`, `radius` should map to **`@theme` or default v4 scales** when acceptable -### 3. Handle site one-offs without arbitrary-value classes +### 3. Use Tailwind's `--spacing` scale + +Tailwind v4 spacing utilities are based on the `--spacing` variable. Translate spacing calculations directly to native utilities: + +- `padding: calc(var(--spacing) * 2)` → `p-2` +- `margin-inline: calc(var(--spacing) * 3)` → `mx-3` +- `gap: var(--spacing)` → `gap-1` + +Use built-in responsive or named container variants (`md:`, `@md:`, `@md/media-root:`) for media-query behavior. If a scoped design needs to scale all spacing, overriding `--spacing` at that scope is valid because native utilities inherit it. Ignore `--base-size` and `--size`; resolve font and icon sizes to rem values. + +For arbitrary values, Tailwind v4 also provides the build-time `--spacing(N)` function. Use it for literal spacing multipliers that do not map cleanly to a native utility: + +- `border-radius: calc(var(--spacing) * 7)` → `rounded-[--spacing(7)]` +- `[--max-width:calc(var(--spacing)*44)]` → `[--max-width:--spacing(44)]` +- `[bottom:calc(100% + var(--spacing) * 4.8)]` → `[bottom:calc(100%+--spacing(4.8))]` + +`rounded-(--spacing(7))` is not the equivalent syntax: the parenthesized form is for a custom-property utility and would look for `var(--spacing(7))`. Keep `calc(var(--spacing) * var(...))` when the multiplier is runtime-derived because `--spacing()` only replaces literal values. + +### 4. Handle site one-offs without arbitrary-value classes For site code: @@ -44,7 +62,7 @@ For site code: - For responsive, dark-mode, or other variants, define an inline custom property and consume it with syntax such as `md:min-h-(--md-min-h)`. - Add a theme token or semantic utility when the value repeats. -### 4. Avoid arbitrary values for common scale values +### 5. Avoid arbitrary values for common scale values Bad: @@ -60,7 +78,7 @@ Good: - `rounded-lg` - `text-sm` -### 5. Prefer theme-backed utilities +### 6. Prefer theme-backed utilities Avoid: @@ -74,11 +92,11 @@ Prefer: (Adapt names to the project's **`@theme`** variable names; add tokens to CSS when missing.) -### 6. Prefer named variants and utilities +### 7. Prefer named variants and utilities Use existing responsive, state, data, ARIA, and custom variants. Add **`@utility`**, **`@custom-variant`**, or an **`@theme`** token when a pattern repeats instead of copying bracket syntax. -### 7. Preserve responsive, state, and media behavior +### 8. Preserve responsive, state, and media behavior - `@media (min-width: 768px)` → `md:` (match project breakpoints from **`@theme`** / default v4 screens) - Named container queries (e.g. **`@container media-root`**) → match existing utilities such as **`@*/media-root:`** / **`max-*` / `@2xl`** patterns used in skins—do not silently switch to plain `md:` if the source is container-based @@ -86,7 +104,11 @@ Use existing responsive, state, data, ARIA, and custom variants. Add **`@utility - `:focus-visible` → `focus-visible:` - `[data-state='open']` → `data-[state=open]:` -### 8. After migration — short report +### 9. Avoid legacy semantic class hooks + +Do not add `media-*` semantic or BEM marker classes to Tailwind templates, including forms such as `media-button--*`, `media-popover--*`, `media-menu__*`, or `media-sr-only`. Use the corresponding Tailwind utility, component class, data attribute, or custom-element selector. Before reporting completion, scan Tailwind source files for `media-...--` and `media-...__` marker classes. + +### 10. After migration — short report Include: diff --git a/apps/e2e/tests/visual/video-skin.spec.ts b/apps/e2e/tests/visual/video-skin.spec.ts index 4502e548..d17100f3 100644 --- a/apps/e2e/tests/visual/video-skin.spec.ts +++ b/apps/e2e/tests/visual/video-skin.spec.ts @@ -113,7 +113,7 @@ test.describe('Visual — HTML Portrait Layout', () => { const thumbnail = document.querySelector('video-skin')!.shadowRoot!.querySelector('media-slider-thumbnail')!; const style = getComputedStyle(thumbnail); const probe = document.createElement('div'); - probe.style.height = style.getPropertyValue('--media-slider-thumbnail-max-height'); + probe.style.height = style.getPropertyValue('--max-height'); document.body.append(probe); const configuredMaxHeight = parseFloat(getComputedStyle(probe).height); diff --git a/packages/html/src/define/audio/minimal-skin.tailwind.ts b/packages/html/src/define/audio/minimal-skin.tailwind.ts index 5fc3d2ce..0bb065f6 100644 --- a/packages/html/src/define/audio/minimal-skin.tailwind.ts +++ b/packages/html/src/define/audio/minimal-skin.tailwind.ts @@ -107,6 +107,21 @@ function getTemplateHTML() {
+ + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + + + + + + + + + + @@ -122,20 +137,6 @@ function getTemplateHTML() { - - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} - - - - - - - - - -
diff --git a/packages/html/src/define/audio/minimal-skin.ts b/packages/html/src/define/audio/minimal-skin.ts index 4d41a597..8ea71d9c 100644 --- a/packages/html/src/define/audio/minimal-skin.ts +++ b/packages/html/src/define/audio/minimal-skin.ts @@ -89,20 +89,6 @@ function getTemplateHTML() {
- - - - - - - ${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })} ${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })} @@ -117,6 +103,21 @@ function getTemplateHTML() { + + + + + + + +
diff --git a/packages/html/src/define/audio/minimal-ui.ts b/packages/html/src/define/audio/minimal-ui.ts index 5a1d3250..9911fc6e 100644 --- a/packages/html/src/define/audio/minimal-ui.ts +++ b/packages/html/src/define/audio/minimal-ui.ts @@ -10,12 +10,15 @@ import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playbac import { PlaybackRateRadioGroupElement } from '../../ui/playback-rate-radio-group/playback-rate-radio-group-element'; import { PopoverElement } from '../../ui/popover/popover-element'; import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; -import { TooltipElement } from '../../ui/tooltip/tooltip-element'; -import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; -import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element'; -import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element'; import { safeDefine } from '../safe-define'; -import { defineErrorDialog, defineMenu, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; +import { + defineErrorDialog, + defineMenu, + defineTime, + defineTimeSlider, + defineTooltip, + defineVolumeSlider, +} from '../ui/compounds'; // Value import — player.ts body runs before this module's body. import { AudioPlayerElement } from './player'; @@ -31,6 +34,7 @@ defineTimeSlider(); defineVolumeSlider(); defineTime(); defineMenu(); +defineTooltip(); // Standalone elements. safeDefine(BufferingIndicatorElement); @@ -41,7 +45,3 @@ safeDefine(PlaybackRateButtonElement); safeDefine(PlaybackRateRadioGroupElement); safeDefine(PopoverElement); safeDefine(SeekButtonElement); -safeDefine(TooltipLabelElement); -safeDefine(TooltipShortcutElement); -safeDefine(TooltipElement); -safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/audio/skin.tailwind.ts b/packages/html/src/define/audio/skin.tailwind.ts index 60e7e0aa..fef0636c 100644 --- a/packages/html/src/define/audio/skin.tailwind.ts +++ b/packages/html/src/define/audio/skin.tailwind.ts @@ -64,7 +64,7 @@ function getTemplateHTML() { - + ${renderIcon('seek', { class: cn(icon, iconFlipped) })} ${SEEK_TIME} @@ -75,7 +75,7 @@ function getTemplateHTML() { - + ${renderIcon('seek', { class: icon })} ${SEEK_TIME} diff --git a/packages/html/src/define/audio/ui.ts b/packages/html/src/define/audio/ui.ts index ec567cfc..7ea82702 100644 --- a/packages/html/src/define/audio/ui.ts +++ b/packages/html/src/define/audio/ui.ts @@ -14,12 +14,8 @@ import { PlaybackRateRadioGroupElement } from '../../ui/playback-rate-radio-grou import { PopoverElement } from '../../ui/popover/popover-element'; import { SeekButtonElement } from '../../ui/seek-button/seek-button-element'; import { TextElement } from '../../ui/text/text-element'; -import { TooltipElement } from '../../ui/tooltip/tooltip-element'; -import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; -import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element'; -import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element'; import { safeDefine } from '../safe-define'; -import { defineErrorDialog, defineMenu, defineSliders, defineTime } from '../ui/compounds'; +import { defineErrorDialog, defineMenu, defineSliders, defineTime, defineTooltip } from '../ui/compounds'; // Value import — player.ts body runs before this module's body. import { AudioPlayerElement } from './player'; @@ -35,6 +31,7 @@ defineErrorDialog(); defineSliders(); defineTime(); defineMenu(); +defineTooltip(); // Standalone elements. safeDefine(GestureElement); @@ -48,7 +45,3 @@ safeDefine(PlaybackRateRadioGroupElement); safeDefine(PopoverElement); safeDefine(SeekButtonElement); safeDefine(TextElement); -safeDefine(TooltipLabelElement); -safeDefine(TooltipShortcutElement); -safeDefine(TooltipElement); -safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/live-audio/minimal-skin.tailwind.ts b/packages/html/src/define/live-audio/minimal-skin.tailwind.ts index 340ae234..118863e9 100644 --- a/packages/html/src/define/live-audio/minimal-skin.tailwind.ts +++ b/packages/html/src/define/live-audio/minimal-skin.tailwind.ts @@ -10,6 +10,7 @@ import { popup, root, slider, + spacer, } from '@videojs/skins/minimal/tailwind/audio.tailwind'; import { createTemplate } from '@videojs/utils/dom'; import { cn } from '@videojs/utils/style'; @@ -59,7 +60,7 @@ function getTemplateHTML() { - +
diff --git a/packages/html/src/define/live-audio/minimal-ui.ts b/packages/html/src/define/live-audio/minimal-ui.ts index 463e9a58..2b1d96d2 100644 --- a/packages/html/src/define/live-audio/minimal-ui.ts +++ b/packages/html/src/define/live-audio/minimal-ui.ts @@ -8,12 +8,8 @@ import { LiveButtonElement } from '../../ui/live-button/live-button-element'; import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; import { PlayButtonElement } from '../../ui/play-button/play-button-element'; import { PopoverElement } from '../../ui/popover/popover-element'; -import { TooltipElement } from '../../ui/tooltip/tooltip-element'; -import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; -import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element'; -import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element'; import { safeDefine } from '../safe-define'; -import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; +import { defineErrorDialog, defineTime, defineTimeSlider, defineTooltip, defineVolumeSlider } from '../ui/compounds'; // Value import — player.ts body runs before this module's body. import { LiveAudioPlayerElement } from './player'; @@ -28,6 +24,7 @@ defineErrorDialog(); defineTimeSlider(); defineVolumeSlider(); defineTime(); +defineTooltip(); // Standalone elements. safeDefine(BufferingIndicatorElement); @@ -35,7 +32,3 @@ safeDefine(LiveButtonElement); safeDefine(MuteButtonElement); safeDefine(PlayButtonElement); safeDefine(PopoverElement); -safeDefine(TooltipLabelElement); -safeDefine(TooltipShortcutElement); -safeDefine(TooltipElement); -safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/live-audio/skin.tailwind.ts b/packages/html/src/define/live-audio/skin.tailwind.ts index bfe2e450..28d3f61d 100644 --- a/packages/html/src/define/live-audio/skin.tailwind.ts +++ b/packages/html/src/define/live-audio/skin.tailwind.ts @@ -10,6 +10,7 @@ import { popup, root, slider, + spacer, } from '@videojs/skins/default/tailwind/audio.tailwind'; import { createTemplate } from '@videojs/utils/dom'; import { cn } from '@videojs/utils/style'; @@ -59,7 +60,7 @@ function getTemplateHTML() {
- +
diff --git a/packages/html/src/define/live-audio/ui.ts b/packages/html/src/define/live-audio/ui.ts index 1d81bde7..e14e8b11 100644 --- a/packages/html/src/define/live-audio/ui.ts +++ b/packages/html/src/define/live-audio/ui.ts @@ -12,12 +12,8 @@ import { MuteButtonElement } from '../../ui/mute-button/mute-button-element'; import { PlayButtonElement } from '../../ui/play-button/play-button-element'; import { PopoverElement } from '../../ui/popover/popover-element'; import { TextElement } from '../../ui/text/text-element'; -import { TooltipElement } from '../../ui/tooltip/tooltip-element'; -import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element'; -import { TooltipLabelElement } from '../../ui/tooltip/tooltip-label-element'; -import { TooltipShortcutElement } from '../../ui/tooltip/tooltip-shortcut-element'; import { safeDefine } from '../safe-define'; -import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds'; +import { defineErrorDialog, defineTime, defineTimeSlider, defineTooltip, defineVolumeSlider } from '../ui/compounds'; // Value import — player.ts body runs before this module's body. import { LiveAudioPlayerElement } from './player'; @@ -33,6 +29,7 @@ defineErrorDialog(); defineTimeSlider(); defineVolumeSlider(); defineTime(); +defineTooltip(); // Standalone elements. safeDefine(GestureElement); @@ -43,7 +40,3 @@ safeDefine(MuteButtonElement); safeDefine(PlayButtonElement); safeDefine(PopoverElement); safeDefine(TextElement); -safeDefine(TooltipLabelElement); -safeDefine(TooltipShortcutElement); -safeDefine(TooltipElement); -safeDefine(TooltipGroupElement); diff --git a/packages/html/src/define/live-video/minimal-skin.tailwind.ts b/packages/html/src/define/live-video/minimal-skin.tailwind.ts index 698629ff..8518e720 100644 --- a/packages/html/src/define/live-video/minimal-skin.tailwind.ts +++ b/packages/html/src/define/live-video/minimal-skin.tailwind.ts @@ -15,6 +15,7 @@ import { poster, root, slider, + spacer, } from '@videojs/skins/minimal/tailwind/video.tailwind'; import { createTemplate } from '@videojs/utils/dom'; import { cn } from '@videojs/utils/style'; @@ -65,30 +66,31 @@ function getTemplateHTML() { + + + ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + + + + + + + + +
- +
- - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} - - - - - - - - - - ${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })} ${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })} - +