@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.js CHANGED
@@ -8577,6 +8577,8 @@ var TableCell = React33.memo((props) => {
8577
8577
  children,
8578
8578
  align = "center",
8579
8579
  isSticky = false,
8580
+ width,
8581
+ nowrap = false,
8580
8582
  onClick
8581
8583
  } = props;
8582
8584
  const { registerStickyCell, stickyCells } = useTableRowContext();
@@ -8620,9 +8622,13 @@ var TableCell = React33.memo((props) => {
8620
8622
  isSticky && "table-sticky",
8621
8623
  isLastSticky && stickyShadow && "right-shadow",
8622
8624
  onClick && "clickable",
8623
- enableHover && "cell-hover"
8625
+ enableHover && "cell-hover",
8626
+ nowrap && "nowrap"
8624
8627
  ),
8625
- style: isSticky ? { left } : void 0,
8628
+ style: {
8629
+ ...isSticky ? { left } : null,
8630
+ ...width != null ? { width: typeof width === "number" ? `${width}px` : width } : null
8631
+ },
8626
8632
  onClick,
8627
8633
  children
8628
8634
  }
@@ -8878,6 +8884,16 @@ var Tooltip_default = Tooltip;
8878
8884
  // src/components/Video/Video.tsx
8879
8885
  import React38 from "react";
8880
8886
  import { jsx as jsx346, jsxs as jsxs221 } from "react/jsx-runtime";
8887
+ var PipIcon = () => /* @__PURE__ */ jsxs221("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
8888
+ /* @__PURE__ */ jsx346("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
8889
+ /* @__PURE__ */ jsx346("rect", { x: "12", y: "11", width: "7", height: "6", rx: "1", fill: "currentColor" })
8890
+ ] });
8891
+ var formatTime = (sec) => {
8892
+ if (!Number.isFinite(sec) || sec < 0) return "0:00";
8893
+ const m = Math.floor(sec / 60);
8894
+ const s = Math.floor(sec % 60);
8895
+ return `${m}:${s.toString().padStart(2, "0")}`;
8896
+ };
8881
8897
  var Video = React38.forwardRef((props, ref) => {
8882
8898
  const {
8883
8899
  src,
@@ -8886,13 +8902,36 @@ var Video = React38.forwardRef((props, ref) => {
8886
8902
  muted,
8887
8903
  loop,
8888
8904
  playsInline,
8905
+ showControls = true,
8906
+ showCenterPlay = true,
8907
+ playbackRates,
8908
+ showCaptions = false,
8909
+ showPip = false,
8910
+ showDownload = false,
8911
+ downloadFileName,
8889
8912
  onPlay,
8890
8913
  onPause,
8914
+ onTimeUpdate,
8915
+ onVolumeChange,
8916
+ onLoadedMetadata,
8917
+ onRateChange,
8918
+ children,
8891
8919
  ...rest
8892
8920
  } = props;
8921
+ const containerRef = React38.useRef(null);
8893
8922
  const videoRef = React38.useRef(null);
8894
8923
  const [isPlaying, setIsPlaying] = React38.useState(Boolean(autoPlay));
8895
8924
  const [isLoaded, setIsLoaded] = React38.useState(false);
8925
+ const [currentTime, setCurrentTime] = React38.useState(0);
8926
+ const [duration, setDuration] = React38.useState(0);
8927
+ const [buffered, setBuffered] = React38.useState(0);
8928
+ const [volume, setVolume] = React38.useState(1);
8929
+ const [isMuted, setIsMuted] = React38.useState(Boolean(muted));
8930
+ const [isFullscreen, setIsFullscreen] = React38.useState(false);
8931
+ const [playbackRate, setPlaybackRate] = React38.useState(1);
8932
+ const [rateMenuOpen, setRateMenuOpen] = React38.useState(false);
8933
+ const [captionsOn, setCaptionsOn] = React38.useState(false);
8934
+ const [isPip, setIsPip] = React38.useState(false);
8896
8935
  const setRefs = React38.useCallback(
8897
8936
  (el) => {
8898
8937
  videoRef.current = el;
@@ -8901,6 +8940,25 @@ var Video = React38.forwardRef((props, ref) => {
8901
8940
  },
8902
8941
  [ref]
8903
8942
  );
8943
+ React38.useEffect(() => {
8944
+ const onFsChange = () => {
8945
+ setIsFullscreen(document.fullscreenElement === containerRef.current);
8946
+ };
8947
+ document.addEventListener("fullscreenchange", onFsChange);
8948
+ return () => document.removeEventListener("fullscreenchange", onFsChange);
8949
+ }, []);
8950
+ React38.useEffect(() => {
8951
+ const v = videoRef.current;
8952
+ if (!v) return;
8953
+ const onEnter = () => setIsPip(true);
8954
+ const onLeave = () => setIsPip(false);
8955
+ v.addEventListener("enterpictureinpicture", onEnter);
8956
+ v.addEventListener("leavepictureinpicture", onLeave);
8957
+ return () => {
8958
+ v.removeEventListener("enterpictureinpicture", onEnter);
8959
+ v.removeEventListener("leavepictureinpicture", onLeave);
8960
+ };
8961
+ }, []);
8904
8962
  const handlePlay = (e) => {
8905
8963
  setIsPlaying(true);
8906
8964
  onPlay?.(e);
@@ -8909,50 +8967,278 @@ var Video = React38.forwardRef((props, ref) => {
8909
8967
  setIsPlaying(false);
8910
8968
  onPause?.(e);
8911
8969
  };
8912
- const handleLoadedData = () => {
8970
+ const handleLoadedMetadata = (e) => {
8971
+ const v = e.currentTarget;
8972
+ setDuration(v.duration);
8913
8973
  setIsLoaded(true);
8974
+ onLoadedMetadata?.(e);
8975
+ };
8976
+ const handleTimeUpdate = (e) => {
8977
+ const v = e.currentTarget;
8978
+ setCurrentTime(v.currentTime);
8979
+ if (v.buffered.length > 0) {
8980
+ setBuffered(v.buffered.end(v.buffered.length - 1));
8981
+ }
8982
+ onTimeUpdate?.(e);
8983
+ };
8984
+ const handleVolumeChange = (e) => {
8985
+ const v = e.currentTarget;
8986
+ setVolume(v.volume);
8987
+ setIsMuted(v.muted);
8988
+ onVolumeChange?.(e);
8989
+ };
8990
+ const handleRateChange = (e) => {
8991
+ setPlaybackRate(e.currentTarget.playbackRate);
8992
+ onRateChange?.(e);
8914
8993
  };
8915
8994
  const togglePlay = () => {
8916
- if (!videoRef.current) return;
8917
- if (isPlaying) {
8918
- videoRef.current.pause();
8995
+ const v = videoRef.current;
8996
+ if (!v) return;
8997
+ if (v.paused) v.play();
8998
+ else v.pause();
8999
+ };
9000
+ const toggleMute = () => {
9001
+ const v = videoRef.current;
9002
+ if (!v) return;
9003
+ v.muted = !v.muted;
9004
+ };
9005
+ const toggleFullscreen = () => {
9006
+ const el = containerRef.current;
9007
+ if (!el) return;
9008
+ if (document.fullscreenElement === el) {
9009
+ document.exitFullscreen();
8919
9010
  } else {
8920
- videoRef.current.play();
9011
+ el.requestFullscreen?.();
8921
9012
  }
8922
9013
  };
8923
- return /* @__PURE__ */ jsxs221("div", { className: "lib-xplat-video custom-overlay", children: [
8924
- /* @__PURE__ */ jsx346(
8925
- "video",
8926
- {
8927
- ref: setRefs,
8928
- src,
8929
- poster,
8930
- autoPlay,
8931
- muted,
8932
- loop,
8933
- playsInline: playsInline ?? true,
8934
- onPlay: handlePlay,
8935
- onPause: handlePause,
8936
- onLoadedData: handleLoadedData,
8937
- onClick: togglePlay,
8938
- ...rest
9014
+ const togglePip = async () => {
9015
+ const v = videoRef.current;
9016
+ if (!v) return;
9017
+ try {
9018
+ if (document.pictureInPictureElement === v) {
9019
+ await document.exitPictureInPicture?.();
9020
+ } else {
9021
+ await v.requestPictureInPicture?.();
8939
9022
  }
8940
- ),
8941
- /* @__PURE__ */ jsx346(
8942
- "button",
8943
- {
8944
- type: "button",
8945
- className: clsx_default(
8946
- "play-overlay",
8947
- isPlaying && "is-playing",
8948
- !isLoaded && "is-loading"
8949
- ),
8950
- onClick: togglePlay,
8951
- "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
8952
- children: isPlaying ? /* @__PURE__ */ jsx346(PauseIcon_default, {}) : /* @__PURE__ */ jsx346("span", { className: "play-icon", children: /* @__PURE__ */ jsx346(PlayCircleIcon_default, {}) })
9023
+ } catch {
9024
+ }
9025
+ };
9026
+ const toggleCaptions = () => {
9027
+ const v = videoRef.current;
9028
+ if (!v) return;
9029
+ const next = !captionsOn;
9030
+ for (let i = 0; i < v.textTracks.length; i++) {
9031
+ const t = v.textTracks[i];
9032
+ if (t.kind === "captions" || t.kind === "subtitles") {
9033
+ t.mode = next ? "showing" : "hidden";
8953
9034
  }
8954
- )
8955
- ] });
9035
+ }
9036
+ setCaptionsOn(next);
9037
+ };
9038
+ const selectRate = (rate) => {
9039
+ const v = videoRef.current;
9040
+ if (!v) return;
9041
+ v.playbackRate = rate;
9042
+ setRateMenuOpen(false);
9043
+ };
9044
+ const handleSeek = (e) => {
9045
+ const v = videoRef.current;
9046
+ if (!v) return;
9047
+ const next = Number(e.target.value);
9048
+ v.currentTime = next;
9049
+ setCurrentTime(next);
9050
+ };
9051
+ const handleVolumeSlider = (e) => {
9052
+ const v = videoRef.current;
9053
+ if (!v) return;
9054
+ const next = Number(e.target.value);
9055
+ v.volume = next;
9056
+ if (next > 0 && v.muted) v.muted = false;
9057
+ if (next === 0) v.muted = true;
9058
+ };
9059
+ const progressPct = duration > 0 ? currentTime / duration * 100 : 0;
9060
+ const bufferedPct = duration > 0 ? buffered / duration * 100 : 0;
9061
+ const volumePct = (isMuted ? 0 : volume) * 100;
9062
+ const VolumeGlyph = isMuted || volume === 0 ? VolumeXIcon_default : volume < 0.5 ? VolumeIcon_default : Volume2Icon_default;
9063
+ const pipSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document && document.pictureInPictureEnabled;
9064
+ return /* @__PURE__ */ jsxs221(
9065
+ "div",
9066
+ {
9067
+ ref: containerRef,
9068
+ className: clsx_default("lib-xplat-video", showControls && "has-controls"),
9069
+ children: [
9070
+ /* @__PURE__ */ jsx346(
9071
+ "video",
9072
+ {
9073
+ ref: setRefs,
9074
+ src,
9075
+ poster,
9076
+ autoPlay,
9077
+ muted,
9078
+ loop,
9079
+ playsInline: playsInline ?? true,
9080
+ onPlay: handlePlay,
9081
+ onPause: handlePause,
9082
+ onLoadedMetadata: handleLoadedMetadata,
9083
+ onTimeUpdate: handleTimeUpdate,
9084
+ onVolumeChange: handleVolumeChange,
9085
+ onRateChange: handleRateChange,
9086
+ onClick: togglePlay,
9087
+ ...rest,
9088
+ children
9089
+ }
9090
+ ),
9091
+ showCenterPlay && /* @__PURE__ */ jsx346(
9092
+ "button",
9093
+ {
9094
+ type: "button",
9095
+ className: clsx_default(
9096
+ "center-play",
9097
+ isPlaying && "is-playing",
9098
+ !isLoaded && "is-loading"
9099
+ ),
9100
+ onClick: togglePlay,
9101
+ "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
9102
+ tabIndex: -1,
9103
+ children: /* @__PURE__ */ jsx346("span", { className: "center-play-icon", children: /* @__PURE__ */ jsx346(PlayCircleIcon_default, {}) })
9104
+ }
9105
+ ),
9106
+ showControls && /* @__PURE__ */ jsxs221("div", { className: "controls", onClick: (e) => e.stopPropagation(), children: [
9107
+ /* @__PURE__ */ jsx346(
9108
+ "input",
9109
+ {
9110
+ type: "range",
9111
+ className: "seekbar",
9112
+ min: 0,
9113
+ max: duration || 0,
9114
+ step: 0.1,
9115
+ value: currentTime,
9116
+ onChange: handleSeek,
9117
+ "aria-label": "\uC7AC\uC0DD \uC704\uCE58",
9118
+ style: {
9119
+ ["--progress"]: `${progressPct}%`,
9120
+ ["--buffered"]: `${bufferedPct}%`
9121
+ }
9122
+ }
9123
+ ),
9124
+ /* @__PURE__ */ jsxs221("div", { className: "controls-row", children: [
9125
+ /* @__PURE__ */ jsx346(
9126
+ "button",
9127
+ {
9128
+ type: "button",
9129
+ className: "control-btn",
9130
+ onClick: togglePlay,
9131
+ "aria-label": isPlaying ? "\uC77C\uC2DC\uC815\uC9C0" : "\uC7AC\uC0DD",
9132
+ children: isPlaying ? /* @__PURE__ */ jsx346(PauseIcon_default, {}) : /* @__PURE__ */ jsx346(PlayIcon_default, {})
9133
+ }
9134
+ ),
9135
+ /* @__PURE__ */ jsxs221("div", { className: "volume-group", children: [
9136
+ /* @__PURE__ */ jsx346(
9137
+ "button",
9138
+ {
9139
+ type: "button",
9140
+ className: "control-btn",
9141
+ onClick: toggleMute,
9142
+ "aria-label": isMuted ? "\uC74C\uC18C\uAC70 \uD574\uC81C" : "\uC74C\uC18C\uAC70",
9143
+ children: /* @__PURE__ */ jsx346(VolumeGlyph, {})
9144
+ }
9145
+ ),
9146
+ /* @__PURE__ */ jsx346(
9147
+ "input",
9148
+ {
9149
+ type: "range",
9150
+ className: "volume-slider",
9151
+ min: 0,
9152
+ max: 1,
9153
+ step: 0.05,
9154
+ value: isMuted ? 0 : volume,
9155
+ onChange: handleVolumeSlider,
9156
+ "aria-label": "\uBCFC\uB968",
9157
+ style: { ["--volume"]: `${volumePct}%` }
9158
+ }
9159
+ )
9160
+ ] }),
9161
+ /* @__PURE__ */ jsxs221("span", { className: "time", children: [
9162
+ formatTime(currentTime),
9163
+ " / ",
9164
+ formatTime(duration)
9165
+ ] }),
9166
+ /* @__PURE__ */ jsx346("div", { className: "controls-spacer" }),
9167
+ playbackRates && playbackRates.length > 0 && /* @__PURE__ */ jsxs221("div", { className: clsx_default("rate-group", rateMenuOpen && "is-open"), children: [
9168
+ /* @__PURE__ */ jsxs221(
9169
+ "button",
9170
+ {
9171
+ type: "button",
9172
+ className: "control-btn rate-btn",
9173
+ onClick: () => setRateMenuOpen((o) => !o),
9174
+ "aria-label": "\uC7AC\uC0DD \uC18D\uB3C4",
9175
+ "aria-expanded": rateMenuOpen,
9176
+ children: [
9177
+ playbackRate,
9178
+ "x"
9179
+ ]
9180
+ }
9181
+ ),
9182
+ rateMenuOpen && /* @__PURE__ */ jsx346("ul", { className: "rate-menu", role: "menu", children: playbackRates.map((r2) => /* @__PURE__ */ jsx346("li", { children: /* @__PURE__ */ jsxs221(
9183
+ "button",
9184
+ {
9185
+ type: "button",
9186
+ role: "menuitem",
9187
+ className: clsx_default("rate-item", r2 === playbackRate && "is-active"),
9188
+ onClick: () => selectRate(r2),
9189
+ children: [
9190
+ r2,
9191
+ "x"
9192
+ ]
9193
+ }
9194
+ ) }, r2)) })
9195
+ ] }),
9196
+ showCaptions && /* @__PURE__ */ jsx346(
9197
+ "button",
9198
+ {
9199
+ type: "button",
9200
+ className: clsx_default("control-btn", captionsOn && "is-active"),
9201
+ onClick: toggleCaptions,
9202
+ "aria-label": captionsOn ? "\uC790\uB9C9 \uB044\uAE30" : "\uC790\uB9C9 \uCF1C\uAE30",
9203
+ "aria-pressed": captionsOn,
9204
+ children: /* @__PURE__ */ jsx346(TypeIcon_default, {})
9205
+ }
9206
+ ),
9207
+ showPip && pipSupported && /* @__PURE__ */ jsx346(
9208
+ "button",
9209
+ {
9210
+ type: "button",
9211
+ className: clsx_default("control-btn", isPip && "is-active"),
9212
+ onClick: togglePip,
9213
+ "aria-label": isPip ? "PIP \uC885\uB8CC" : "PIP",
9214
+ children: /* @__PURE__ */ jsx346(PipIcon, {})
9215
+ }
9216
+ ),
9217
+ showDownload && /* @__PURE__ */ jsx346(
9218
+ "a",
9219
+ {
9220
+ className: "control-btn",
9221
+ href: src,
9222
+ download: downloadFileName ?? true,
9223
+ "aria-label": "\uB2E4\uC6B4\uB85C\uB4DC",
9224
+ children: /* @__PURE__ */ jsx346(DownloadIcon_default, {})
9225
+ }
9226
+ ),
9227
+ /* @__PURE__ */ jsx346(
9228
+ "button",
9229
+ {
9230
+ type: "button",
9231
+ className: "control-btn",
9232
+ onClick: toggleFullscreen,
9233
+ "aria-label": isFullscreen ? "\uC804\uCCB4\uD654\uBA74 \uC885\uB8CC" : "\uC804\uCCB4\uD654\uBA74",
9234
+ children: isFullscreen ? /* @__PURE__ */ jsx346(MinimizeIcon_default, {}) : /* @__PURE__ */ jsx346(MaximizeIcon_default, {})
9235
+ }
9236
+ )
9237
+ ] })
9238
+ ] })
9239
+ ]
9240
+ }
9241
+ );
8956
9242
  });
8957
9243
  Video.displayName = "Video";
8958
9244
  var Video_default = Video;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-plat/design-system",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "XPLAT UI Design System",
5
5
  "author": "XPLAT WOONG",
6
6
  "main": "dist/index.cjs",