@x-plat/design-system 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8974,6 +8974,8 @@ var TableCell = import_react34.default.memo((props) => {
8974
8974
  children,
8975
8975
  align = "center",
8976
8976
  isSticky = false,
8977
+ width,
8978
+ nowrap = false,
8977
8979
  onClick
8978
8980
  } = props;
8979
8981
  const { registerStickyCell, stickyCells } = useTableRowContext();
@@ -9017,9 +9019,13 @@ var TableCell = import_react34.default.memo((props) => {
9017
9019
  isSticky && "table-sticky",
9018
9020
  isLastSticky && stickyShadow && "right-shadow",
9019
9021
  onClick && "clickable",
9020
- enableHover && "cell-hover"
9022
+ enableHover && "cell-hover",
9023
+ nowrap && "nowrap"
9021
9024
  ),
9022
- style: isSticky ? { left } : void 0,
9025
+ style: {
9026
+ ...isSticky ? { left } : null,
9027
+ ...width != null ? { width: typeof width === "number" ? `${width}px` : width } : null
9028
+ },
9023
9029
  onClick,
9024
9030
  children
9025
9031
  }
@@ -9275,6 +9281,16 @@ var Tooltip_default = Tooltip;
9275
9281
  // src/components/Video/Video.tsx
9276
9282
  var import_react39 = __toESM(require("react"), 1);
9277
9283
  var import_jsx_runtime346 = require("react/jsx-runtime");
9284
+ var PipIcon = () => /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
9285
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
9286
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("rect", { x: "12", y: "11", width: "7", height: "6", rx: "1", fill: "currentColor" })
9287
+ ] });
9288
+ var formatTime = (sec) => {
9289
+ if (!Number.isFinite(sec) || sec < 0) return "0:00";
9290
+ const m = Math.floor(sec / 60);
9291
+ const s = Math.floor(sec % 60);
9292
+ return `${m}:${s.toString().padStart(2, "0")}`;
9293
+ };
9278
9294
  var Video = import_react39.default.forwardRef((props, ref) => {
9279
9295
  const {
9280
9296
  src,
@@ -9283,13 +9299,36 @@ var Video = import_react39.default.forwardRef((props, ref) => {
9283
9299
  muted,
9284
9300
  loop,
9285
9301
  playsInline,
9302
+ showControls = true,
9303
+ showCenterPlay = true,
9304
+ playbackRates,
9305
+ showCaptions = false,
9306
+ showPip = false,
9307
+ showDownload = false,
9308
+ downloadFileName,
9286
9309
  onPlay,
9287
9310
  onPause,
9311
+ onTimeUpdate,
9312
+ onVolumeChange,
9313
+ onLoadedMetadata,
9314
+ onRateChange,
9315
+ children,
9288
9316
  ...rest
9289
9317
  } = props;
9318
+ const containerRef = import_react39.default.useRef(null);
9290
9319
  const videoRef = import_react39.default.useRef(null);
9291
9320
  const [isPlaying, setIsPlaying] = import_react39.default.useState(Boolean(autoPlay));
9292
9321
  const [isLoaded, setIsLoaded] = import_react39.default.useState(false);
9322
+ const [currentTime, setCurrentTime] = import_react39.default.useState(0);
9323
+ const [duration, setDuration] = import_react39.default.useState(0);
9324
+ const [buffered, setBuffered] = import_react39.default.useState(0);
9325
+ const [volume, setVolume] = import_react39.default.useState(1);
9326
+ const [isMuted, setIsMuted] = import_react39.default.useState(Boolean(muted));
9327
+ const [isFullscreen, setIsFullscreen] = import_react39.default.useState(false);
9328
+ const [playbackRate, setPlaybackRate] = import_react39.default.useState(1);
9329
+ const [rateMenuOpen, setRateMenuOpen] = import_react39.default.useState(false);
9330
+ const [captionsOn, setCaptionsOn] = import_react39.default.useState(false);
9331
+ const [isPip, setIsPip] = import_react39.default.useState(false);
9293
9332
  const setRefs = import_react39.default.useCallback(
9294
9333
  (el) => {
9295
9334
  videoRef.current = el;
@@ -9298,6 +9337,25 @@ var Video = import_react39.default.forwardRef((props, ref) => {
9298
9337
  },
9299
9338
  [ref]
9300
9339
  );
9340
+ import_react39.default.useEffect(() => {
9341
+ const onFsChange = () => {
9342
+ setIsFullscreen(document.fullscreenElement === containerRef.current);
9343
+ };
9344
+ document.addEventListener("fullscreenchange", onFsChange);
9345
+ return () => document.removeEventListener("fullscreenchange", onFsChange);
9346
+ }, []);
9347
+ import_react39.default.useEffect(() => {
9348
+ const v = videoRef.current;
9349
+ if (!v) return;
9350
+ const onEnter = () => setIsPip(true);
9351
+ const onLeave = () => setIsPip(false);
9352
+ v.addEventListener("enterpictureinpicture", onEnter);
9353
+ v.addEventListener("leavepictureinpicture", onLeave);
9354
+ return () => {
9355
+ v.removeEventListener("enterpictureinpicture", onEnter);
9356
+ v.removeEventListener("leavepictureinpicture", onLeave);
9357
+ };
9358
+ }, []);
9301
9359
  const handlePlay = (e) => {
9302
9360
  setIsPlaying(true);
9303
9361
  onPlay?.(e);
@@ -9306,50 +9364,278 @@ var Video = import_react39.default.forwardRef((props, ref) => {
9306
9364
  setIsPlaying(false);
9307
9365
  onPause?.(e);
9308
9366
  };
9309
- const handleLoadedData = () => {
9367
+ const handleLoadedMetadata = (e) => {
9368
+ const v = e.currentTarget;
9369
+ setDuration(v.duration);
9310
9370
  setIsLoaded(true);
9371
+ onLoadedMetadata?.(e);
9372
+ };
9373
+ const handleTimeUpdate = (e) => {
9374
+ const v = e.currentTarget;
9375
+ setCurrentTime(v.currentTime);
9376
+ if (v.buffered.length > 0) {
9377
+ setBuffered(v.buffered.end(v.buffered.length - 1));
9378
+ }
9379
+ onTimeUpdate?.(e);
9380
+ };
9381
+ const handleVolumeChange = (e) => {
9382
+ const v = e.currentTarget;
9383
+ setVolume(v.volume);
9384
+ setIsMuted(v.muted);
9385
+ onVolumeChange?.(e);
9386
+ };
9387
+ const handleRateChange = (e) => {
9388
+ setPlaybackRate(e.currentTarget.playbackRate);
9389
+ onRateChange?.(e);
9311
9390
  };
9312
9391
  const togglePlay = () => {
9313
- if (!videoRef.current) return;
9314
- if (isPlaying) {
9315
- videoRef.current.pause();
9392
+ const v = videoRef.current;
9393
+ if (!v) return;
9394
+ if (v.paused) v.play();
9395
+ else v.pause();
9396
+ };
9397
+ const toggleMute = () => {
9398
+ const v = videoRef.current;
9399
+ if (!v) return;
9400
+ v.muted = !v.muted;
9401
+ };
9402
+ const toggleFullscreen = () => {
9403
+ const el = containerRef.current;
9404
+ if (!el) return;
9405
+ if (document.fullscreenElement === el) {
9406
+ document.exitFullscreen();
9316
9407
  } else {
9317
- videoRef.current.play();
9408
+ el.requestFullscreen?.();
9318
9409
  }
9319
9410
  };
9320
- return /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("div", { className: "lib-xplat-video custom-overlay", children: [
9321
- /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9322
- "video",
9323
- {
9324
- ref: setRefs,
9325
- src,
9326
- poster,
9327
- autoPlay,
9328
- muted,
9329
- loop,
9330
- playsInline: playsInline ?? true,
9331
- onPlay: handlePlay,
9332
- onPause: handlePause,
9333
- onLoadedData: handleLoadedData,
9334
- onClick: togglePlay,
9335
- ...rest
9411
+ const togglePip = async () => {
9412
+ const v = videoRef.current;
9413
+ if (!v) return;
9414
+ try {
9415
+ if (document.pictureInPictureElement === v) {
9416
+ await document.exitPictureInPicture?.();
9417
+ } else {
9418
+ await v.requestPictureInPicture?.();
9336
9419
  }
9337
- ),
9338
- /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9339
- "button",
9340
- {
9341
- type: "button",
9342
- className: clsx_default(
9343
- "play-overlay",
9344
- isPlaying && "is-playing",
9345
- !isLoaded && "is-loading"
9346
- ),
9347
- onClick: togglePlay,
9348
- "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
9349
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PauseIcon_default, {}) : /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("span", { className: "play-icon", children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PlayCircleIcon_default, {}) })
9420
+ } catch {
9421
+ }
9422
+ };
9423
+ const toggleCaptions = () => {
9424
+ const v = videoRef.current;
9425
+ if (!v) return;
9426
+ const next = !captionsOn;
9427
+ for (let i = 0; i < v.textTracks.length; i++) {
9428
+ const t = v.textTracks[i];
9429
+ if (t.kind === "captions" || t.kind === "subtitles") {
9430
+ t.mode = next ? "showing" : "hidden";
9350
9431
  }
9351
- )
9352
- ] });
9432
+ }
9433
+ setCaptionsOn(next);
9434
+ };
9435
+ const selectRate = (rate) => {
9436
+ const v = videoRef.current;
9437
+ if (!v) return;
9438
+ v.playbackRate = rate;
9439
+ setRateMenuOpen(false);
9440
+ };
9441
+ const handleSeek = (e) => {
9442
+ const v = videoRef.current;
9443
+ if (!v) return;
9444
+ const next = Number(e.target.value);
9445
+ v.currentTime = next;
9446
+ setCurrentTime(next);
9447
+ };
9448
+ const handleVolumeSlider = (e) => {
9449
+ const v = videoRef.current;
9450
+ if (!v) return;
9451
+ const next = Number(e.target.value);
9452
+ v.volume = next;
9453
+ if (next > 0 && v.muted) v.muted = false;
9454
+ if (next === 0) v.muted = true;
9455
+ };
9456
+ const progressPct = duration > 0 ? currentTime / duration * 100 : 0;
9457
+ const bufferedPct = duration > 0 ? buffered / duration * 100 : 0;
9458
+ const volumePct = (isMuted ? 0 : volume) * 100;
9459
+ const VolumeGlyph = isMuted || volume === 0 ? VolumeXIcon_default : volume < 0.5 ? VolumeIcon_default : Volume2Icon_default;
9460
+ const pipSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document && document.pictureInPictureEnabled;
9461
+ return /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)(
9462
+ "div",
9463
+ {
9464
+ ref: containerRef,
9465
+ className: clsx_default("lib-xplat-video", showControls && "has-controls"),
9466
+ children: [
9467
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9468
+ "video",
9469
+ {
9470
+ ref: setRefs,
9471
+ src,
9472
+ poster,
9473
+ autoPlay,
9474
+ muted,
9475
+ loop,
9476
+ playsInline: playsInline ?? true,
9477
+ onPlay: handlePlay,
9478
+ onPause: handlePause,
9479
+ onLoadedMetadata: handleLoadedMetadata,
9480
+ onTimeUpdate: handleTimeUpdate,
9481
+ onVolumeChange: handleVolumeChange,
9482
+ onRateChange: handleRateChange,
9483
+ onClick: togglePlay,
9484
+ ...rest,
9485
+ children
9486
+ }
9487
+ ),
9488
+ showCenterPlay && /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9489
+ "button",
9490
+ {
9491
+ type: "button",
9492
+ className: clsx_default(
9493
+ "center-play",
9494
+ isPlaying && "is-playing",
9495
+ !isLoaded && "is-loading"
9496
+ ),
9497
+ onClick: togglePlay,
9498
+ "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
9499
+ tabIndex: -1,
9500
+ children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("span", { className: "center-play-icon", children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PlayCircleIcon_default, {}) })
9501
+ }
9502
+ ),
9503
+ showControls && /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("div", { className: "controls", onClick: (e) => e.stopPropagation(), children: [
9504
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9505
+ "input",
9506
+ {
9507
+ type: "range",
9508
+ className: "seekbar",
9509
+ min: 0,
9510
+ max: duration || 0,
9511
+ step: 0.1,
9512
+ value: currentTime,
9513
+ onChange: handleSeek,
9514
+ "aria-label": "\uC7AC\uC0DD \uC704\uCE58",
9515
+ style: {
9516
+ ["--progress"]: `${progressPct}%`,
9517
+ ["--buffered"]: `${bufferedPct}%`
9518
+ }
9519
+ }
9520
+ ),
9521
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("div", { className: "controls-row", children: [
9522
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9523
+ "button",
9524
+ {
9525
+ type: "button",
9526
+ className: "control-btn",
9527
+ onClick: togglePlay,
9528
+ "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
9529
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PauseIcon_default, {}) : /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PlayIcon_default, {})
9530
+ }
9531
+ ),
9532
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("div", { className: "volume-group", children: [
9533
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9534
+ "button",
9535
+ {
9536
+ type: "button",
9537
+ className: "control-btn",
9538
+ onClick: toggleMute,
9539
+ "aria-label": isMuted ? "\uC74C\uC18C\uAC70 \uD574\uC81C" : "\uC74C\uC18C\uAC70",
9540
+ children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(VolumeGlyph, {})
9541
+ }
9542
+ ),
9543
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9544
+ "input",
9545
+ {
9546
+ type: "range",
9547
+ className: "volume-slider",
9548
+ min: 0,
9549
+ max: 1,
9550
+ step: 0.05,
9551
+ value: isMuted ? 0 : volume,
9552
+ onChange: handleVolumeSlider,
9553
+ "aria-label": "\uBCFC\uB968",
9554
+ style: { ["--volume"]: `${volumePct}%` }
9555
+ }
9556
+ )
9557
+ ] }),
9558
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("span", { className: "time", children: [
9559
+ formatTime(currentTime),
9560
+ " / ",
9561
+ formatTime(duration)
9562
+ ] }),
9563
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("div", { className: "controls-spacer" }),
9564
+ playbackRates && playbackRates.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)("div", { className: clsx_default("rate-group", rateMenuOpen && "is-open"), children: [
9565
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)(
9566
+ "button",
9567
+ {
9568
+ type: "button",
9569
+ className: "control-btn rate-btn",
9570
+ onClick: () => setRateMenuOpen((o) => !o),
9571
+ "aria-label": "\uC7AC\uC0DD \uC18D\uB3C4",
9572
+ "aria-expanded": rateMenuOpen,
9573
+ children: [
9574
+ playbackRate,
9575
+ "x"
9576
+ ]
9577
+ }
9578
+ ),
9579
+ rateMenuOpen && /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("ul", { className: "rate-menu", role: "menu", children: playbackRates.map((r2) => /* @__PURE__ */ (0, import_jsx_runtime346.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime346.jsxs)(
9580
+ "button",
9581
+ {
9582
+ type: "button",
9583
+ role: "menuitem",
9584
+ className: clsx_default("rate-item", r2 === playbackRate && "is-active"),
9585
+ onClick: () => selectRate(r2),
9586
+ children: [
9587
+ r2,
9588
+ "x"
9589
+ ]
9590
+ }
9591
+ ) }, r2)) })
9592
+ ] }),
9593
+ showCaptions && /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9594
+ "button",
9595
+ {
9596
+ type: "button",
9597
+ className: clsx_default("control-btn", captionsOn && "is-active"),
9598
+ onClick: toggleCaptions,
9599
+ "aria-label": captionsOn ? "\uC790\uB9C9 \uB044\uAE30" : "\uC790\uB9C9 \uCF1C\uAE30",
9600
+ "aria-pressed": captionsOn,
9601
+ children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(TypeIcon_default, {})
9602
+ }
9603
+ ),
9604
+ showPip && pipSupported && /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9605
+ "button",
9606
+ {
9607
+ type: "button",
9608
+ className: clsx_default("control-btn", isPip && "is-active"),
9609
+ onClick: togglePip,
9610
+ "aria-label": isPip ? "PIP \uC885\uB8CC" : "PIP",
9611
+ children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(PipIcon, {})
9612
+ }
9613
+ ),
9614
+ showDownload && /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9615
+ "a",
9616
+ {
9617
+ className: "control-btn",
9618
+ href: src,
9619
+ download: downloadFileName ?? true,
9620
+ "aria-label": "\uB2E4\uC6B4\uB85C\uB4DC",
9621
+ children: /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(DownloadIcon_default, {})
9622
+ }
9623
+ ),
9624
+ /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(
9625
+ "button",
9626
+ {
9627
+ type: "button",
9628
+ className: "control-btn",
9629
+ onClick: toggleFullscreen,
9630
+ "aria-label": isFullscreen ? "\uC804\uCCB4\uD654\uBA74 \uC885\uB8CC" : "\uC804\uCCB4\uD654\uBA74",
9631
+ children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(MinimizeIcon_default, {}) : /* @__PURE__ */ (0, import_jsx_runtime346.jsx)(MaximizeIcon_default, {})
9632
+ }
9633
+ )
9634
+ ] })
9635
+ ] })
9636
+ ]
9637
+ }
9638
+ );
9353
9639
  });
9354
9640
  Video.displayName = "Video";
9355
9641
  var Video_default = Video;
package/dist/index.css CHANGED
@@ -3681,6 +3681,20 @@
3681
3681
  min-width: 80px;
3682
3682
  padding: var(--spacing-space-2);
3683
3683
  vertical-align: middle;
3684
+ word-break: keep-all;
3685
+ overflow-wrap: anywhere;
3686
+ }
3687
+ .lib-xplat-table-wrapper > .lib-xplat-table > thead > tr > td.nowrap,
3688
+ .lib-xplat-table-wrapper > .lib-xplat-table > thead > tr > th.nowrap,
3689
+ .lib-xplat-table-wrapper > .lib-xplat-table > tbody > tr > td.nowrap,
3690
+ .lib-xplat-table-wrapper > .lib-xplat-table > tbody > tr > th.nowrap {
3691
+ white-space: nowrap;
3692
+ overflow-wrap: normal;
3693
+ }
3694
+ .lib-xplat-table-wrapper > .lib-xplat-table > thead > tr > td,
3695
+ .lib-xplat-table-wrapper > .lib-xplat-table > thead > tr > th,
3696
+ .lib-xplat-table-wrapper > .lib-xplat-table > tbody > tr > td,
3697
+ .lib-xplat-table-wrapper > .lib-xplat-table > tbody > tr > th {
3684
3698
  transition: background-color 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94);
3685
3699
  }
3686
3700
  .lib-xplat-table-wrapper > .lib-xplat-table > thead > tr > td.clickable,
@@ -4064,11 +4078,9 @@
4064
4078
  max-height: 100%;
4065
4079
  object-fit: contain;
4066
4080
  vertical-align: middle;
4067
- }
4068
- .lib-xplat-video.custom-overlay > video {
4069
4081
  cursor: pointer;
4070
4082
  }
4071
- .lib-xplat-video.custom-overlay > .play-overlay {
4083
+ .lib-xplat-video > .center-play {
4072
4084
  position: absolute;
4073
4085
  top: 0;
4074
4086
  left: 0;
@@ -4082,9 +4094,10 @@
4082
4094
  cursor: pointer;
4083
4095
  padding: 0;
4084
4096
  color: var(--semantic-icon-inverse);
4085
- transition: opacity 0.2s ease, background-color 0.2s ease;
4097
+ transition: opacity 0.2s ease;
4098
+ pointer-events: none;
4086
4099
  }
4087
- .lib-xplat-video.custom-overlay > .play-overlay::before {
4100
+ .lib-xplat-video > .center-play::before {
4088
4101
  content: "";
4089
4102
  position: absolute;
4090
4103
  inset: 0;
@@ -4092,34 +4105,217 @@
4092
4105
  opacity: 0;
4093
4106
  transition: opacity 0.2s ease;
4094
4107
  }
4095
- .lib-xplat-video.custom-overlay > .play-overlay:hover::before,
4096
- .lib-xplat-video.custom-overlay > .play-overlay:focus-visible::before {
4097
- opacity: 1;
4098
- }
4099
- .lib-xplat-video.custom-overlay > .play-overlay:focus-visible {
4100
- outline: 2px solid var(--semantic-interaction-focus-ring);
4101
- outline-offset: 2px;
4102
- }
4103
- .lib-xplat-video.custom-overlay > .play-overlay > .play-icon {
4108
+ .lib-xplat-video > .center-play > .center-play-icon {
4104
4109
  position: relative;
4105
4110
  z-index: 1;
4106
4111
  display: flex;
4107
- align-items: center;
4108
- justify-content: center;
4109
4112
  font-size: 64px;
4110
4113
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.4));
4111
4114
  }
4112
- .lib-xplat-video.custom-overlay > .play-overlay.is-playing {
4115
+ .lib-xplat-video > .center-play.is-playing {
4113
4116
  opacity: 0;
4114
- pointer-events: none;
4115
4117
  }
4116
- .lib-xplat-video.custom-overlay > .play-overlay.is-loading .play-icon {
4118
+ .lib-xplat-video > .center-play.is-loading .center-play-icon {
4117
4119
  opacity: 0.6;
4118
4120
  }
4119
- .lib-xplat-video.custom-overlay:hover > .play-overlay.is-playing {
4121
+ .lib-xplat-video > .controls {
4122
+ position: absolute;
4123
+ left: 0;
4124
+ right: 0;
4125
+ bottom: 0;
4126
+ padding: var(--spacing-space-2) var(--spacing-space-3);
4127
+ display: flex;
4128
+ flex-direction: column;
4129
+ gap: var(--spacing-space-1);
4130
+ background:
4131
+ linear-gradient(
4132
+ to top,
4133
+ rgba(0, 0, 0, 0.7),
4134
+ rgba(0, 0, 0, 0));
4135
+ color: var(--semantic-icon-inverse);
4136
+ opacity: 0;
4137
+ transition: opacity 0.2s ease;
4138
+ pointer-events: none;
4139
+ }
4140
+ .lib-xplat-video:hover > .controls,
4141
+ .lib-xplat-video:focus-within > .controls {
4120
4142
  opacity: 1;
4121
4143
  pointer-events: auto;
4122
4144
  }
4145
+ .lib-xplat-video .controls-row {
4146
+ display: flex;
4147
+ align-items: center;
4148
+ gap: var(--spacing-space-2);
4149
+ }
4150
+ .lib-xplat-video .control-btn {
4151
+ display: inline-flex;
4152
+ align-items: center;
4153
+ justify-content: center;
4154
+ width: 32px;
4155
+ height: 32px;
4156
+ padding: 0;
4157
+ border: none;
4158
+ background: transparent;
4159
+ color: inherit;
4160
+ cursor: pointer;
4161
+ border-radius: var(--spacing-radius-sm);
4162
+ transition: background-color 0.15s ease;
4163
+ }
4164
+ .lib-xplat-video .control-btn:hover {
4165
+ background-color: rgba(255, 255, 255, 0.15);
4166
+ }
4167
+ .lib-xplat-video .control-btn:focus-visible {
4168
+ outline: 2px solid var(--semantic-interaction-focus-ring);
4169
+ outline-offset: 2px;
4170
+ }
4171
+ .lib-xplat-video .controls-spacer {
4172
+ margin-left: auto;
4173
+ }
4174
+ .lib-xplat-video .control-btn.is-active {
4175
+ background-color: rgba(255, 255, 255, 0.25);
4176
+ }
4177
+ .lib-xplat-video .rate-group {
4178
+ position: relative;
4179
+ }
4180
+ .lib-xplat-video .rate-group .rate-btn {
4181
+ width: auto;
4182
+ padding: 0 var(--spacing-space-2);
4183
+ font-size: 12px;
4184
+ font-weight: 600;
4185
+ font-variant-numeric: tabular-nums;
4186
+ }
4187
+ .lib-xplat-video .rate-group .rate-menu {
4188
+ position: absolute;
4189
+ bottom: calc(100% + var(--spacing-space-1));
4190
+ right: 0;
4191
+ margin: 0;
4192
+ padding: var(--spacing-space-1);
4193
+ list-style: none;
4194
+ background-color: rgba(0, 0, 0, 0.85);
4195
+ border-radius: var(--spacing-radius-sm);
4196
+ min-width: 64px;
4197
+ }
4198
+ .lib-xplat-video .rate-group .rate-menu .rate-item {
4199
+ width: 100%;
4200
+ padding: var(--spacing-space-1) var(--spacing-space-2);
4201
+ border: none;
4202
+ background: transparent;
4203
+ color: inherit;
4204
+ text-align: left;
4205
+ cursor: pointer;
4206
+ border-radius: var(--spacing-radius-xs);
4207
+ font-size: 12px;
4208
+ font-variant-numeric: tabular-nums;
4209
+ }
4210
+ .lib-xplat-video .rate-group .rate-menu .rate-item:hover {
4211
+ background-color: rgba(255, 255, 255, 0.15);
4212
+ }
4213
+ .lib-xplat-video .rate-group .rate-menu .rate-item.is-active {
4214
+ color: var(--semantic-surface-brand-default);
4215
+ font-weight: 600;
4216
+ }
4217
+ .lib-xplat-video .time {
4218
+ font-size: 12px;
4219
+ font-variant-numeric: tabular-nums;
4220
+ white-space: nowrap;
4221
+ }
4222
+ .lib-xplat-video .volume-group {
4223
+ display: flex;
4224
+ align-items: center;
4225
+ }
4226
+ .lib-xplat-video .volume-group .volume-slider {
4227
+ width: 0;
4228
+ opacity: 0;
4229
+ transition: width 0.2s ease, opacity 0.2s ease;
4230
+ }
4231
+ .lib-xplat-video .volume-group:hover .volume-slider,
4232
+ .lib-xplat-video .volume-group:focus-within .volume-slider {
4233
+ width: 80px;
4234
+ opacity: 1;
4235
+ margin-left: var(--spacing-space-1);
4236
+ }
4237
+ .lib-xplat-video .seekbar,
4238
+ .lib-xplat-video .volume-slider {
4239
+ -webkit-appearance: none;
4240
+ appearance: none;
4241
+ background: transparent;
4242
+ cursor: pointer;
4243
+ }
4244
+ .lib-xplat-video .seekbar:focus,
4245
+ .lib-xplat-video .volume-slider:focus {
4246
+ outline: none;
4247
+ }
4248
+ .lib-xplat-video .seekbar::-webkit-slider-thumb,
4249
+ .lib-xplat-video .volume-slider::-webkit-slider-thumb {
4250
+ -webkit-appearance: none;
4251
+ appearance: none;
4252
+ width: 12px;
4253
+ height: 12px;
4254
+ border-radius: 50%;
4255
+ background: #fff;
4256
+ border: none;
4257
+ cursor: pointer;
4258
+ }
4259
+ .lib-xplat-video .seekbar::-moz-range-thumb,
4260
+ .lib-xplat-video .volume-slider::-moz-range-thumb {
4261
+ width: 12px;
4262
+ height: 12px;
4263
+ border-radius: 50%;
4264
+ background: #fff;
4265
+ border: none;
4266
+ cursor: pointer;
4267
+ }
4268
+ .lib-xplat-video .seekbar {
4269
+ width: 100%;
4270
+ height: 4px;
4271
+ }
4272
+ .lib-xplat-video .seekbar::-webkit-slider-runnable-track {
4273
+ height: 4px;
4274
+ border-radius: 2px;
4275
+ background:
4276
+ linear-gradient(
4277
+ to right,
4278
+ var(--semantic-surface-brand-default) 0%,
4279
+ var(--semantic-surface-brand-default) var(--progress, 0%),
4280
+ rgba(255, 255, 255, 0.4) var(--progress, 0%),
4281
+ rgba(255, 255, 255, 0.4) var(--buffered, 0%),
4282
+ rgba(255, 255, 255, 0.2) var(--buffered, 0%),
4283
+ rgba(255, 255, 255, 0.2) 100%);
4284
+ }
4285
+ .lib-xplat-video .seekbar::-moz-range-track {
4286
+ height: 4px;
4287
+ border-radius: 2px;
4288
+ background: rgba(255, 255, 255, 0.2);
4289
+ }
4290
+ .lib-xplat-video .seekbar::-moz-range-progress {
4291
+ height: 4px;
4292
+ border-radius: 2px;
4293
+ background: var(--semantic-surface-brand-default);
4294
+ }
4295
+ .lib-xplat-video .volume-slider {
4296
+ height: 4px;
4297
+ }
4298
+ .lib-xplat-video .volume-slider::-webkit-slider-runnable-track {
4299
+ height: 4px;
4300
+ border-radius: 2px;
4301
+ background:
4302
+ linear-gradient(
4303
+ to right,
4304
+ #fff 0%,
4305
+ #fff var(--volume, 0%),
4306
+ rgba(255, 255, 255, 0.3) var(--volume, 0%),
4307
+ rgba(255, 255, 255, 0.3) 100%);
4308
+ }
4309
+ .lib-xplat-video .volume-slider::-moz-range-track {
4310
+ height: 4px;
4311
+ border-radius: 2px;
4312
+ background: rgba(255, 255, 255, 0.3);
4313
+ }
4314
+ .lib-xplat-video .volume-slider::-moz-range-progress {
4315
+ height: 4px;
4316
+ border-radius: 2px;
4317
+ background: #fff;
4318
+ }
4123
4319
 
4124
4320
  /* src/layout/Grid/FullGrid/fullgrid.scss */
4125
4321
  .lib-xplat-full-grid {