@withwiz/gallery 0.1.0

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.
Files changed (77) hide show
  1. package/README.ko.md +484 -0
  2. package/README.md +484 -0
  3. package/dist/chunk-B3MQL6ZR.mjs +79 -0
  4. package/dist/chunk-B3MQL6ZR.mjs.map +1 -0
  5. package/dist/chunk-BC3TPJQY.mjs +122 -0
  6. package/dist/chunk-BC3TPJQY.mjs.map +1 -0
  7. package/dist/chunk-BTLELFKC.mjs +11 -0
  8. package/dist/chunk-BTLELFKC.mjs.map +1 -0
  9. package/dist/chunk-GDUGKVDQ.mjs +50 -0
  10. package/dist/chunk-GDUGKVDQ.mjs.map +1 -0
  11. package/dist/chunk-GFOWZGJX.js +50 -0
  12. package/dist/chunk-GFOWZGJX.js.map +1 -0
  13. package/dist/chunk-ILSZPLME.js +47 -0
  14. package/dist/chunk-ILSZPLME.js.map +1 -0
  15. package/dist/chunk-M7CCTDUG.js +122 -0
  16. package/dist/chunk-M7CCTDUG.js.map +1 -0
  17. package/dist/chunk-WVRTM6E7.js +19 -0
  18. package/dist/chunk-WVRTM6E7.js.map +1 -0
  19. package/dist/chunk-XLPMNJJV.js +11 -0
  20. package/dist/chunk-XLPMNJJV.js.map +1 -0
  21. package/dist/chunk-XSPWM4DD.js +79 -0
  22. package/dist/chunk-XSPWM4DD.js.map +1 -0
  23. package/dist/chunk-YUMB3GBN.mjs +47 -0
  24. package/dist/chunk-YUMB3GBN.mjs.map +1 -0
  25. package/dist/chunk-ZEZCUJJD.mjs +19 -0
  26. package/dist/chunk-ZEZCUJJD.mjs.map +1 -0
  27. package/dist/components/gallery.css +1074 -0
  28. package/dist/components/index.d.mts +156 -0
  29. package/dist/components/index.d.ts +156 -0
  30. package/dist/components/index.js +1215 -0
  31. package/dist/components/index.js.map +1 -0
  32. package/dist/components/index.mjs +1215 -0
  33. package/dist/components/index.mjs.map +1 -0
  34. package/dist/errors-B9gzUsX-.d.ts +39 -0
  35. package/dist/errors-DYrlDCzP.d.mts +39 -0
  36. package/dist/gallery.css +1074 -0
  37. package/dist/hooks/index.d.mts +65 -0
  38. package/dist/hooks/index.d.ts +65 -0
  39. package/dist/hooks/index.js +14 -0
  40. package/dist/hooks/index.js.map +1 -0
  41. package/dist/hooks/index.mjs +14 -0
  42. package/dist/hooks/index.mjs.map +1 -0
  43. package/dist/index.d.mts +17 -0
  44. package/dist/index.d.ts +17 -0
  45. package/dist/index.js +37 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/index.mjs +37 -0
  48. package/dist/index.mjs.map +1 -0
  49. package/dist/presets/ballet.d.mts +64 -0
  50. package/dist/presets/ballet.d.ts +64 -0
  51. package/dist/presets/ballet.js +162 -0
  52. package/dist/presets/ballet.js.map +1 -0
  53. package/dist/presets/ballet.mjs +162 -0
  54. package/dist/presets/ballet.mjs.map +1 -0
  55. package/dist/server/index.d.mts +157 -0
  56. package/dist/server/index.d.ts +157 -0
  57. package/dist/server/index.js +619 -0
  58. package/dist/server/index.js.map +1 -0
  59. package/dist/server/index.mjs +619 -0
  60. package/dist/server/index.mjs.map +1 -0
  61. package/dist/types/index.d.mts +137 -0
  62. package/dist/types/index.d.ts +137 -0
  63. package/dist/types/index.js +1 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/types/index.mjs +1 -0
  66. package/dist/types/index.mjs.map +1 -0
  67. package/dist/useGalleryLightbox-BYzPyBYB.d.mts +22 -0
  68. package/dist/useGalleryLightbox-BYzPyBYB.d.ts +22 -0
  69. package/dist/validators/index.d.mts +68 -0
  70. package/dist/validators/index.d.ts +68 -0
  71. package/dist/validators/index.js +7 -0
  72. package/dist/validators/index.js.map +1 -0
  73. package/dist/validators/index.mjs +7 -0
  74. package/dist/validators/index.mjs.map +1 -0
  75. package/package.json +85 -0
  76. package/prisma/gallery.schema.prisma +62 -0
  77. package/prisma/migrations/2026-05-24-enum-to-table.sql +65 -0
@@ -0,0 +1,65 @@
1
+ import { DragEvent, ChangeEvent } from 'react';
2
+ export { L as LightboxImage, U as UseGalleryLightboxReturn, u as useGalleryLightbox } from '../useGalleryLightbox-BYzPyBYB.mjs';
3
+
4
+ /** IntersectionObserver 기반 스크롤 reveal hook.
5
+ *
6
+ * - SSR safe: `typeof window === "undefined"` 이면 effect 가 nothing.
7
+ * - jsdom 등 `IntersectionObserver` 미정의 환경에서도 effect 가 nothing (가드).
8
+ * - `once: true` (default) — 한번 visible 후 unobserve, 이후 다시 false 로 돌아가지 않음.
9
+ * - `once: false` — visible 진입/이탈에 따라 toggle.
10
+ *
11
+ * 반환되는 ref 는 React 19 의 `RefObject<HTMLElement | null>` 시그니처 — host 가 어떤
12
+ * 구체 element 타입에든 마운트 가능하도록 광폭 타입을 둔다.
13
+ */
14
+ declare function useScrollReveal(opts?: {
15
+ threshold?: number;
16
+ once?: boolean;
17
+ }): {
18
+ ref: React.RefObject<HTMLElement | null>;
19
+ isVisible: boolean;
20
+ };
21
+
22
+ type ImageDropZoneValidate = (file: File) => {
23
+ ok: true;
24
+ } | {
25
+ ok: false;
26
+ reason: string;
27
+ };
28
+ interface UseImageDropZoneOptions {
29
+ /** drop / file picker change 로 통과한 파일들이 전달되는 콜백. */
30
+ onFiles: (files: File[]) => void | Promise<void>;
31
+ /** 허용 MIME 타입. 기본 ["image/jpeg", "image/png", "image/webp"]. */
32
+ accept?: string[];
33
+ /** 파일당 최대 바이트. 기본 10 * 1024 * 1024 (10MB). */
34
+ maxSize?: number;
35
+ /** 다중 선택 허용. 기본 true. false 이면 첫 파일만 통과. */
36
+ multiple?: boolean;
37
+ /** host 가 추가 검증을 끼울 수 있는 hook. accept/maxSize 검증 통과 후 호출. */
38
+ validate?: ImageDropZoneValidate;
39
+ /** true 이면 모든 드래그/드롭/체인지 콜백이 no-op. */
40
+ disabled?: boolean;
41
+ }
42
+ interface UseImageDropZoneReturn {
43
+ /** 현재 드래그 over 상태. */
44
+ isDragging: boolean;
45
+ /** 드롭 영역에 전개할 prop 집합. */
46
+ dropProps: {
47
+ onDragEnter: (e: DragEvent) => void;
48
+ onDragOver: (e: DragEvent) => void;
49
+ onDragLeave: (e: DragEvent) => void;
50
+ onDrop: (e: DragEvent) => void;
51
+ };
52
+ /** `<input type="file">` 에 전개할 prop 집합. */
53
+ inputProps: {
54
+ type: "file";
55
+ accept: string;
56
+ multiple: boolean;
57
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void;
58
+ };
59
+ /** 직전 drop / change 에서 reject 된 파일들의 사유. 매 새 이벤트마다 초기화. */
60
+ rejectedReasons: string[];
61
+ }
62
+ /** host 독립 이미지 드롭존 hook. 업로드 자체는 수행하지 않고 검증 통과 파일만 onFiles 로 위임. */
63
+ declare function useImageDropZone(opts: UseImageDropZoneOptions): UseImageDropZoneReturn;
64
+
65
+ export { type ImageDropZoneValidate, type UseImageDropZoneOptions, type UseImageDropZoneReturn, useImageDropZone, useScrollReveal };
@@ -0,0 +1,65 @@
1
+ import { DragEvent, ChangeEvent } from 'react';
2
+ export { L as LightboxImage, U as UseGalleryLightboxReturn, u as useGalleryLightbox } from '../useGalleryLightbox-BYzPyBYB.js';
3
+
4
+ /** IntersectionObserver 기반 스크롤 reveal hook.
5
+ *
6
+ * - SSR safe: `typeof window === "undefined"` 이면 effect 가 nothing.
7
+ * - jsdom 등 `IntersectionObserver` 미정의 환경에서도 effect 가 nothing (가드).
8
+ * - `once: true` (default) — 한번 visible 후 unobserve, 이후 다시 false 로 돌아가지 않음.
9
+ * - `once: false` — visible 진입/이탈에 따라 toggle.
10
+ *
11
+ * 반환되는 ref 는 React 19 의 `RefObject<HTMLElement | null>` 시그니처 — host 가 어떤
12
+ * 구체 element 타입에든 마운트 가능하도록 광폭 타입을 둔다.
13
+ */
14
+ declare function useScrollReveal(opts?: {
15
+ threshold?: number;
16
+ once?: boolean;
17
+ }): {
18
+ ref: React.RefObject<HTMLElement | null>;
19
+ isVisible: boolean;
20
+ };
21
+
22
+ type ImageDropZoneValidate = (file: File) => {
23
+ ok: true;
24
+ } | {
25
+ ok: false;
26
+ reason: string;
27
+ };
28
+ interface UseImageDropZoneOptions {
29
+ /** drop / file picker change 로 통과한 파일들이 전달되는 콜백. */
30
+ onFiles: (files: File[]) => void | Promise<void>;
31
+ /** 허용 MIME 타입. 기본 ["image/jpeg", "image/png", "image/webp"]. */
32
+ accept?: string[];
33
+ /** 파일당 최대 바이트. 기본 10 * 1024 * 1024 (10MB). */
34
+ maxSize?: number;
35
+ /** 다중 선택 허용. 기본 true. false 이면 첫 파일만 통과. */
36
+ multiple?: boolean;
37
+ /** host 가 추가 검증을 끼울 수 있는 hook. accept/maxSize 검증 통과 후 호출. */
38
+ validate?: ImageDropZoneValidate;
39
+ /** true 이면 모든 드래그/드롭/체인지 콜백이 no-op. */
40
+ disabled?: boolean;
41
+ }
42
+ interface UseImageDropZoneReturn {
43
+ /** 현재 드래그 over 상태. */
44
+ isDragging: boolean;
45
+ /** 드롭 영역에 전개할 prop 집합. */
46
+ dropProps: {
47
+ onDragEnter: (e: DragEvent) => void;
48
+ onDragOver: (e: DragEvent) => void;
49
+ onDragLeave: (e: DragEvent) => void;
50
+ onDrop: (e: DragEvent) => void;
51
+ };
52
+ /** `<input type="file">` 에 전개할 prop 집합. */
53
+ inputProps: {
54
+ type: "file";
55
+ accept: string;
56
+ multiple: boolean;
57
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void;
58
+ };
59
+ /** 직전 drop / change 에서 reject 된 파일들의 사유. 매 새 이벤트마다 초기화. */
60
+ rejectedReasons: string[];
61
+ }
62
+ /** host 독립 이미지 드롭존 hook. 업로드 자체는 수행하지 않고 검증 통과 파일만 onFiles 로 위임. */
63
+ declare function useImageDropZone(opts: UseImageDropZoneOptions): UseImageDropZoneReturn;
64
+
65
+ export { type ImageDropZoneValidate, type UseImageDropZoneOptions, type UseImageDropZoneReturn, useImageDropZone, useScrollReveal };
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
3
+
4
+ var _chunkM7CCTDUGjs = require('../chunk-M7CCTDUG.js');
5
+
6
+
7
+
8
+ var _chunkXSPWM4DDjs = require('../chunk-XSPWM4DD.js');
9
+
10
+
11
+
12
+
13
+ exports.useGalleryLightbox = _chunkXSPWM4DDjs.useGalleryLightbox; exports.useImageDropZone = _chunkM7CCTDUGjs.useImageDropZone; exports.useScrollReveal = _chunkXSPWM4DDjs.useScrollReveal;
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/hooks/index.js"],"names":[],"mappings":"AAAA;AACE;AACF,uDAA6B;AAC7B;AACE;AACA;AACF,uDAA6B;AAC7B;AACE;AACA;AACA;AACF,2LAAC","file":"/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/hooks/index.js"}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import {
3
+ useImageDropZone
4
+ } from "../chunk-BC3TPJQY.mjs";
5
+ import {
6
+ useGalleryLightbox,
7
+ useScrollReveal
8
+ } from "../chunk-B3MQL6ZR.mjs";
9
+ export {
10
+ useGalleryLightbox,
11
+ useImageDropZone,
12
+ useScrollReveal
13
+ };
14
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,17 @@
1
+ export { C as CategoryInUseError, a as CategoryNotFoundError, G as GalleryError, b as GalleryNotFoundError, P as PermissionDeniedError, g as getGalleryConfig, s as setGalleryConfig } from './errors-DYrlDCzP.mjs';
2
+ export { ApiContext, ApiWrapper, CreateCategoryInput, CreateGalleryInput, GalleryCategoryItem, GalleryConfig, GalleryDetail, GalleryI18nKey, GalleryListItem, GallerySlotName, PaginatedResult, PaginationMeta, PrismaLike, RouteHandler, SortOrder, UpdateCategoryInput, UpdateGalleryInput } from './types/index.mjs';
3
+ import { ClassValue } from 'clsx';
4
+ import 'next/server';
5
+ import 'react';
6
+
7
+ declare function cn(...inputs: ClassValue[]): string;
8
+
9
+ type ImageVariant = "lg" | "md" | "sm" | "thumb";
10
+ /** URL 의 파일 확장자 앞에 variant suffix 를 삽입.
11
+ * - "lg" 는 원본을 그대로 반환 (관례)
12
+ * - 확장자 없는 경로는 그대로 반환
13
+ * 예: getVariantUrl("/a/b.webp", "md") -> "/a/b_md.webp"
14
+ */
15
+ declare function getVariantUrl(url: string, variant: ImageVariant): string;
16
+
17
+ export { type ImageVariant, cn, getVariantUrl };
@@ -0,0 +1,17 @@
1
+ export { C as CategoryInUseError, a as CategoryNotFoundError, G as GalleryError, b as GalleryNotFoundError, P as PermissionDeniedError, g as getGalleryConfig, s as setGalleryConfig } from './errors-B9gzUsX-.js';
2
+ export { ApiContext, ApiWrapper, CreateCategoryInput, CreateGalleryInput, GalleryCategoryItem, GalleryConfig, GalleryDetail, GalleryI18nKey, GalleryListItem, GallerySlotName, PaginatedResult, PaginationMeta, PrismaLike, RouteHandler, SortOrder, UpdateCategoryInput, UpdateGalleryInput } from './types/index.js';
3
+ import { ClassValue } from 'clsx';
4
+ import 'next/server';
5
+ import 'react';
6
+
7
+ declare function cn(...inputs: ClassValue[]): string;
8
+
9
+ type ImageVariant = "lg" | "md" | "sm" | "thumb";
10
+ /** URL 의 파일 확장자 앞에 variant suffix 를 삽입.
11
+ * - "lg" 는 원본을 그대로 반환 (관례)
12
+ * - 확장자 없는 경로는 그대로 반환
13
+ * 예: getVariantUrl("/a/b.webp", "md") -> "/a/b_md.webp"
14
+ */
15
+ declare function getVariantUrl(url: string, variant: ImageVariant): string;
16
+
17
+ export { type ImageVariant, cn, getVariantUrl };
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunkGFOWZGJXjs = require('./chunk-GFOWZGJX.js');
8
+
9
+
10
+
11
+ var _chunkWVRTM6E7js = require('./chunk-WVRTM6E7.js');
12
+
13
+
14
+ var _chunkXLPMNJJVjs = require('./chunk-XLPMNJJV.js');
15
+
16
+ // src/utils/image-variants.ts
17
+ function getVariantUrl(url, variant) {
18
+ if (variant === "lg") return url;
19
+ const dotIdx = url.lastIndexOf(".");
20
+ const slashIdx = url.lastIndexOf("/");
21
+ if (dotIdx === -1 || dotIdx < slashIdx) return url;
22
+ const base = url.slice(0, dotIdx);
23
+ const ext = url.slice(dotIdx);
24
+ return `${base}_${variant}${ext}`;
25
+ }
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+ exports.CategoryInUseError = _chunkGFOWZGJXjs.CategoryInUseError; exports.CategoryNotFoundError = _chunkGFOWZGJXjs.CategoryNotFoundError; exports.GalleryError = _chunkGFOWZGJXjs.GalleryError; exports.GalleryNotFoundError = _chunkGFOWZGJXjs.GalleryNotFoundError; exports.PermissionDeniedError = _chunkGFOWZGJXjs.PermissionDeniedError; exports.cn = _chunkXLPMNJJVjs.cn; exports.getGalleryConfig = _chunkWVRTM6E7js.getGalleryConfig; exports.getVariantUrl = getVariantUrl; exports.setGalleryConfig = _chunkWVRTM6E7js.setGalleryConfig;
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/index.js","../src/utils/image-variants.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;ACRO,SAAS,aAAA,CAAc,GAAA,EAAa,OAAA,EAA+B;AACxE,EAAA,GAAA,CAAI,QAAA,IAAY,IAAA,EAAM,OAAO,GAAA;AAC7B,EAAA,MAAM,OAAA,EAAS,GAAA,CAAI,WAAA,CAAY,GAAG,CAAA;AAClC,EAAA,MAAM,SAAA,EAAW,GAAA,CAAI,WAAA,CAAY,GAAG,CAAA;AACpC,EAAA,GAAA,CAAI,OAAA,IAAW,CAAA,EAAA,GAAM,OAAA,EAAS,QAAA,EAAU,OAAO,GAAA;AAC/C,EAAA,MAAM,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA;AAChC,EAAA,MAAM,IAAA,EAAM,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA;AAC5B,EAAA,OAAO,CAAA,EAAA;AACT;ADUU;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/index.js","sourcesContent":[null,"export type ImageVariant = \"lg\" | \"md\" | \"sm\" | \"thumb\";\n\n/** URL 의 파일 확장자 앞에 variant suffix 를 삽입.\n * - \"lg\" 는 원본을 그대로 반환 (관례)\n * - 확장자 없는 경로는 그대로 반환\n * 예: getVariantUrl(\"/a/b.webp\", \"md\") -> \"/a/b_md.webp\"\n */\nexport function getVariantUrl(url: string, variant: ImageVariant): string {\n if (variant === \"lg\") return url;\n const dotIdx = url.lastIndexOf(\".\");\n const slashIdx = url.lastIndexOf(\"/\");\n if (dotIdx === -1 || dotIdx < slashIdx) return url;\n const base = url.slice(0, dotIdx);\n const ext = url.slice(dotIdx);\n return `${base}_${variant}${ext}`;\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,37 @@
1
+ import {
2
+ CategoryInUseError,
3
+ CategoryNotFoundError,
4
+ GalleryError,
5
+ GalleryNotFoundError,
6
+ PermissionDeniedError
7
+ } from "./chunk-GDUGKVDQ.mjs";
8
+ import {
9
+ getGalleryConfig,
10
+ setGalleryConfig
11
+ } from "./chunk-ZEZCUJJD.mjs";
12
+ import {
13
+ cn
14
+ } from "./chunk-BTLELFKC.mjs";
15
+
16
+ // src/utils/image-variants.ts
17
+ function getVariantUrl(url, variant) {
18
+ if (variant === "lg") return url;
19
+ const dotIdx = url.lastIndexOf(".");
20
+ const slashIdx = url.lastIndexOf("/");
21
+ if (dotIdx === -1 || dotIdx < slashIdx) return url;
22
+ const base = url.slice(0, dotIdx);
23
+ const ext = url.slice(dotIdx);
24
+ return `${base}_${variant}${ext}`;
25
+ }
26
+ export {
27
+ CategoryInUseError,
28
+ CategoryNotFoundError,
29
+ GalleryError,
30
+ GalleryNotFoundError,
31
+ PermissionDeniedError,
32
+ cn,
33
+ getGalleryConfig,
34
+ getVariantUrl,
35
+ setGalleryConfig
36
+ };
37
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/image-variants.ts"],"sourcesContent":["export type ImageVariant = \"lg\" | \"md\" | \"sm\" | \"thumb\";\n\n/** URL 의 파일 확장자 앞에 variant suffix 를 삽입.\n * - \"lg\" 는 원본을 그대로 반환 (관례)\n * - 확장자 없는 경로는 그대로 반환\n * 예: getVariantUrl(\"/a/b.webp\", \"md\") -> \"/a/b_md.webp\"\n */\nexport function getVariantUrl(url: string, variant: ImageVariant): string {\n if (variant === \"lg\") return url;\n const dotIdx = url.lastIndexOf(\".\");\n const slashIdx = url.lastIndexOf(\"/\");\n if (dotIdx === -1 || dotIdx < slashIdx) return url;\n const base = url.slice(0, dotIdx);\n const ext = url.slice(dotIdx);\n return `${base}_${variant}${ext}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAOO,SAAS,cAAc,KAAa,SAA+B;AACxE,MAAI,YAAY,KAAM,QAAO;AAC7B,QAAM,SAAS,IAAI,YAAY,GAAG;AAClC,QAAM,WAAW,IAAI,YAAY,GAAG;AACpC,MAAI,WAAW,MAAM,SAAS,SAAU,QAAO;AAC/C,QAAM,OAAO,IAAI,MAAM,GAAG,MAAM;AAChC,QAAM,MAAM,IAAI,MAAM,MAAM;AAC5B,SAAO,GAAG,IAAI,IAAI,OAAO,GAAG,GAAG;AACjC;","names":[]}
@@ -0,0 +1,64 @@
1
+ import { JSX } from 'react';
2
+ import { L as LightboxImage } from '../useGalleryLightbox-BYzPyBYB.mjs';
3
+
4
+ /**
5
+ * @withwiz/gallery/presets/ballet
6
+ *
7
+ * 발레용 reference public 컴포넌트. ballet 의
8
+ * `src/components/sections/Gallery.tsx`
9
+ * 의 모자이크 + 라이트박스 결합을 host 독립으로 추출.
10
+ *
11
+ * 사용 예 (host RSC + client mount):
12
+ *
13
+ * import { PublicGalleryMosaic } from "@withwiz/gallery/presets/ballet";
14
+ * import { getFeaturedGalleries } from "@withwiz/gallery/server";
15
+ * import "@withwiz/gallery/components/gallery.css";
16
+ *
17
+ * export default async function HomePage() {
18
+ * const featured = await getFeaturedGalleries(config, 7);
19
+ * const images = featured.map((g) => ({ src: g.imageUrl, alt: g.caption ?? "갤러리" }));
20
+ * return <PublicGalleryMosaic images={images} count={7} />;
21
+ * }
22
+ *
23
+ * yeroom 등 다른 host 는 이 preset 을 fork 하거나 `useGalleryLightbox` hook 만
24
+ * 가져다 직접 그릴 수 있다.
25
+ *
26
+ * Host-independence:
27
+ * - `next/image`, `next/navigation`, `useI18n`, `@withwiz/pms` 등 직접 의존 0
28
+ * - 이미지는 plain `<img>` 사용. host 가 next/image 로 그리고 싶다면 fork.
29
+ * - 모든 라벨은 props.i18n 으로만 (fallback 한글).
30
+ */
31
+
32
+ declare const FALLBACK_I18N: {
33
+ readonly sectionLabel: "갤러리";
34
+ readonly moments: "공연의 순간들";
35
+ readonly expandAria: "이미지 확대";
36
+ readonly lbClose: "닫기";
37
+ readonly lbPrev: "이전 이미지";
38
+ readonly lbNext: "다음 이미지";
39
+ };
40
+ interface PublicGalleryMosaicProps {
41
+ /** 표시할 이미지 목록 (host 가 loaders 등에서 받아온 src/alt 매핑) */
42
+ images: LightboxImage[];
43
+ /** 모자이크 그리드 칸 수 (default 7). images.length > count 이면 slice. */
44
+ count?: number;
45
+ /** UI 라벨 i18n override. 미주입 키는 한글 fallback. */
46
+ i18n?: Partial<Record<keyof typeof FALLBACK_I18N, string>>;
47
+ /** 외부 className 추가 */
48
+ className?: string;
49
+ /** 스크롤 reveal (IntersectionObserver) 사용 여부 (default true). false 면 즉시 visible. */
50
+ scrollReveal?: boolean;
51
+ /** 섹션 헤더 비노출 (default false). host 가 자체 헤더를 두는 경우 true. */
52
+ hideHeader?: boolean;
53
+ }
54
+ /**
55
+ * 발레 home gallery 의 모자이크 + 라이트박스.
56
+ *
57
+ * - useGalleryLightbox 가 ESC / Arrow 키바인딩과 wrap-around 를 담당.
58
+ * - useScrollReveal 로 fade-in (scrollReveal=true 일 때).
59
+ * - lightbox open 시 body scroll lock (overflow:hidden).
60
+ * - images 가 빈 배열이면 null 반환.
61
+ */
62
+ declare function PublicGalleryMosaic(props: PublicGalleryMosaicProps): JSX.Element | null;
63
+
64
+ export { PublicGalleryMosaic, type PublicGalleryMosaicProps };
@@ -0,0 +1,64 @@
1
+ import { JSX } from 'react';
2
+ import { L as LightboxImage } from '../useGalleryLightbox-BYzPyBYB.js';
3
+
4
+ /**
5
+ * @withwiz/gallery/presets/ballet
6
+ *
7
+ * 발레용 reference public 컴포넌트. ballet 의
8
+ * `src/components/sections/Gallery.tsx`
9
+ * 의 모자이크 + 라이트박스 결합을 host 독립으로 추출.
10
+ *
11
+ * 사용 예 (host RSC + client mount):
12
+ *
13
+ * import { PublicGalleryMosaic } from "@withwiz/gallery/presets/ballet";
14
+ * import { getFeaturedGalleries } from "@withwiz/gallery/server";
15
+ * import "@withwiz/gallery/components/gallery.css";
16
+ *
17
+ * export default async function HomePage() {
18
+ * const featured = await getFeaturedGalleries(config, 7);
19
+ * const images = featured.map((g) => ({ src: g.imageUrl, alt: g.caption ?? "갤러리" }));
20
+ * return <PublicGalleryMosaic images={images} count={7} />;
21
+ * }
22
+ *
23
+ * yeroom 등 다른 host 는 이 preset 을 fork 하거나 `useGalleryLightbox` hook 만
24
+ * 가져다 직접 그릴 수 있다.
25
+ *
26
+ * Host-independence:
27
+ * - `next/image`, `next/navigation`, `useI18n`, `@withwiz/pms` 등 직접 의존 0
28
+ * - 이미지는 plain `<img>` 사용. host 가 next/image 로 그리고 싶다면 fork.
29
+ * - 모든 라벨은 props.i18n 으로만 (fallback 한글).
30
+ */
31
+
32
+ declare const FALLBACK_I18N: {
33
+ readonly sectionLabel: "갤러리";
34
+ readonly moments: "공연의 순간들";
35
+ readonly expandAria: "이미지 확대";
36
+ readonly lbClose: "닫기";
37
+ readonly lbPrev: "이전 이미지";
38
+ readonly lbNext: "다음 이미지";
39
+ };
40
+ interface PublicGalleryMosaicProps {
41
+ /** 표시할 이미지 목록 (host 가 loaders 등에서 받아온 src/alt 매핑) */
42
+ images: LightboxImage[];
43
+ /** 모자이크 그리드 칸 수 (default 7). images.length > count 이면 slice. */
44
+ count?: number;
45
+ /** UI 라벨 i18n override. 미주입 키는 한글 fallback. */
46
+ i18n?: Partial<Record<keyof typeof FALLBACK_I18N, string>>;
47
+ /** 외부 className 추가 */
48
+ className?: string;
49
+ /** 스크롤 reveal (IntersectionObserver) 사용 여부 (default true). false 면 즉시 visible. */
50
+ scrollReveal?: boolean;
51
+ /** 섹션 헤더 비노출 (default false). host 가 자체 헤더를 두는 경우 true. */
52
+ hideHeader?: boolean;
53
+ }
54
+ /**
55
+ * 발레 home gallery 의 모자이크 + 라이트박스.
56
+ *
57
+ * - useGalleryLightbox 가 ESC / Arrow 키바인딩과 wrap-around 를 담당.
58
+ * - useScrollReveal 로 fade-in (scrollReveal=true 일 때).
59
+ * - lightbox open 시 body scroll lock (overflow:hidden).
60
+ * - images 가 빈 배열이면 null 반환.
61
+ */
62
+ declare function PublicGalleryMosaic(props: PublicGalleryMosaicProps): JSX.Element | null;
63
+
64
+ export { PublicGalleryMosaic, type PublicGalleryMosaicProps };
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
3
+
4
+ var _chunkXLPMNJJVjs = require('../chunk-XLPMNJJV.js');
5
+
6
+
7
+
8
+ var _chunkXSPWM4DDjs = require('../chunk-XSPWM4DD.js');
9
+
10
+ // src/presets/ballet.tsx
11
+ var _react = require('react');
12
+ var _jsxruntime = require('react/jsx-runtime');
13
+ var FALLBACK_I18N = {
14
+ sectionLabel: "\uAC24\uB7EC\uB9AC",
15
+ moments: "\uACF5\uC5F0\uC758 \uC21C\uAC04\uB4E4",
16
+ expandAria: "\uC774\uBBF8\uC9C0 \uD655\uB300",
17
+ lbClose: "\uB2EB\uAE30",
18
+ lbPrev: "\uC774\uC804 \uC774\uBBF8\uC9C0",
19
+ lbNext: "\uB2E4\uC74C \uC774\uBBF8\uC9C0"
20
+ };
21
+ function PublicGalleryMosaic(props) {
22
+ const {
23
+ images: rawImages,
24
+ count = 7,
25
+ i18n: i18nOverride,
26
+ className,
27
+ scrollReveal = true,
28
+ hideHeader = false
29
+ } = props;
30
+ const i18n = { ...FALLBACK_I18N, ..._nullishCoalesce(i18nOverride, () => ( {})) };
31
+ const images = rawImages.slice(0, count);
32
+ const lightbox = _chunkXSPWM4DDjs.useGalleryLightbox.call(void 0, images);
33
+ const { ref: revealRef, isVisible } = _chunkXSPWM4DDjs.useScrollReveal.call(void 0, { once: true });
34
+ _react.useEffect.call(void 0, () => {
35
+ if (!lightbox.isOpen) return;
36
+ if (typeof document === "undefined") return;
37
+ const prev = document.body.style.overflow;
38
+ document.body.style.overflow = "hidden";
39
+ return () => {
40
+ document.body.style.overflow = prev;
41
+ };
42
+ }, [lightbox.isOpen]);
43
+ if (images.length === 0) return null;
44
+ const showReveal = scrollReveal ? isVisible : true;
45
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
46
+ "section",
47
+ {
48
+ ref: revealRef,
49
+ className: _chunkXLPMNJJVjs.cn.call(void 0,
50
+ "gallery-public-mosaic-section",
51
+ showReveal && "gallery-public-mosaic-section--visible",
52
+ className
53
+ ),
54
+ "aria-label": i18n.sectionLabel,
55
+ children: [
56
+ !hideHeader && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "header", { className: "gallery-public-mosaic__head", children: [
57
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-public-mosaic__label", children: i18n.sectionLabel }),
58
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "gallery-public-mosaic__title", children: i18n.moments })
59
+ ] }),
60
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-public-mosaic", "data-count": images.length, children: images.map((img, i) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
61
+ "div",
62
+ {
63
+ className: "gallery-public-mosaic__item",
64
+ role: "button",
65
+ tabIndex: 0,
66
+ "aria-label": _nullishCoalesce(img.alt, () => ( i18n.expandAria)),
67
+ onClick: () => lightbox.open(i),
68
+ onKeyDown: (e) => {
69
+ if (e.key === "Enter" || e.key === " ") {
70
+ e.preventDefault();
71
+ lightbox.open(i);
72
+ }
73
+ },
74
+ children: [
75
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
76
+ "img",
77
+ {
78
+ src: img.src,
79
+ alt: _nullishCoalesce(img.alt, () => ( "")),
80
+ className: "gallery-public-mosaic__img",
81
+ loading: "lazy"
82
+ }
83
+ ),
84
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-public-mosaic__hover", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-public-mosaic__icon", "aria-hidden": "true", children: "+" }) })
85
+ ]
86
+ },
87
+ `${img.src}-${i}`
88
+ )) }),
89
+ lightbox.isOpen && lightbox.current && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
90
+ "div",
91
+ {
92
+ className: "gallery-lightbox",
93
+ role: "dialog",
94
+ "aria-modal": "true",
95
+ "aria-label": i18n.moments,
96
+ onClick: lightbox.close,
97
+ children: [
98
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
99
+ "button",
100
+ {
101
+ type: "button",
102
+ className: "gallery-lightbox__close",
103
+ "aria-label": i18n.lbClose,
104
+ onClick: (e) => {
105
+ e.stopPropagation();
106
+ lightbox.close();
107
+ },
108
+ children: "\xD7"
109
+ }
110
+ ),
111
+ images.length > 1 && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
112
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
113
+ "button",
114
+ {
115
+ type: "button",
116
+ className: "gallery-lightbox__nav gallery-lightbox__prev",
117
+ "aria-label": i18n.lbPrev,
118
+ onClick: (e) => {
119
+ e.stopPropagation();
120
+ lightbox.prev();
121
+ },
122
+ children: "\u2039"
123
+ }
124
+ ),
125
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
126
+ "button",
127
+ {
128
+ type: "button",
129
+ className: "gallery-lightbox__nav gallery-lightbox__next",
130
+ "aria-label": i18n.lbNext,
131
+ onClick: (e) => {
132
+ e.stopPropagation();
133
+ lightbox.next();
134
+ },
135
+ children: "\u203A"
136
+ }
137
+ )
138
+ ] }),
139
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-lightbox__content", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
140
+ "img",
141
+ {
142
+ src: lightbox.current.src,
143
+ alt: _nullishCoalesce(lightbox.current.alt, () => ( "")),
144
+ className: "gallery-lightbox__img"
145
+ }
146
+ ) }),
147
+ images.length > 1 && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-lightbox__counter", children: [
148
+ lightbox.currentIndex + 1,
149
+ " / ",
150
+ images.length
151
+ ] })
152
+ ]
153
+ }
154
+ )
155
+ ]
156
+ }
157
+ );
158
+ }
159
+
160
+
161
+ exports.PublicGalleryMosaic = PublicGalleryMosaic;
162
+ //# sourceMappingURL=ballet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/presets/ballet.js","../../src/presets/ballet.tsx"],"names":[],"mappings":"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","file":"/Users/uni4love/project/workspace/211-withwiz/node-packages/withwiz-gallery/dist/presets/ballet.js","sourcesContent":[null,"/**\n * @withwiz/gallery/presets/ballet\n *\n * 발레용 reference public 컴포넌트. ballet 의\n * `src/components/sections/Gallery.tsx`\n * 의 모자이크 + 라이트박스 결합을 host 독립으로 추출.\n *\n * 사용 예 (host RSC + client mount):\n *\n * import { PublicGalleryMosaic } from \"@withwiz/gallery/presets/ballet\";\n * import { getFeaturedGalleries } from \"@withwiz/gallery/server\";\n * import \"@withwiz/gallery/components/gallery.css\";\n *\n * export default async function HomePage() {\n * const featured = await getFeaturedGalleries(config, 7);\n * const images = featured.map((g) => ({ src: g.imageUrl, alt: g.caption ?? \"갤러리\" }));\n * return <PublicGalleryMosaic images={images} count={7} />;\n * }\n *\n * yeroom 등 다른 host 는 이 preset 을 fork 하거나 `useGalleryLightbox` hook 만\n * 가져다 직접 그릴 수 있다.\n *\n * Host-independence:\n * - `next/image`, `next/navigation`, `useI18n`, `@withwiz/pms` 등 직접 의존 0\n * - 이미지는 plain `<img>` 사용. host 가 next/image 로 그리고 싶다면 fork.\n * - 모든 라벨은 props.i18n 으로만 (fallback 한글).\n */\n\nimport { useEffect, type JSX } from \"react\";\nimport { useGalleryLightbox, type LightboxImage } from \"../hooks/useGalleryLightbox\";\nimport { useScrollReveal } from \"../hooks/useScrollReveal\";\nimport { cn } from \"../utils/cn\";\n\nconst FALLBACK_I18N = {\n sectionLabel: \"갤러리\",\n moments: \"공연의 순간들\",\n expandAria: \"이미지 확대\",\n lbClose: \"닫기\",\n lbPrev: \"이전 이미지\",\n lbNext: \"다음 이미지\",\n} as const;\n\nexport interface PublicGalleryMosaicProps {\n /** 표시할 이미지 목록 (host 가 loaders 등에서 받아온 src/alt 매핑) */\n images: LightboxImage[];\n /** 모자이크 그리드 칸 수 (default 7). images.length > count 이면 slice. */\n count?: number;\n /** UI 라벨 i18n override. 미주입 키는 한글 fallback. */\n i18n?: Partial<Record<keyof typeof FALLBACK_I18N, string>>;\n /** 외부 className 추가 */\n className?: string;\n /** 스크롤 reveal (IntersectionObserver) 사용 여부 (default true). false 면 즉시 visible. */\n scrollReveal?: boolean;\n /** 섹션 헤더 비노출 (default false). host 가 자체 헤더를 두는 경우 true. */\n hideHeader?: boolean;\n}\n\n/**\n * 발레 home gallery 의 모자이크 + 라이트박스.\n *\n * - useGalleryLightbox 가 ESC / Arrow 키바인딩과 wrap-around 를 담당.\n * - useScrollReveal 로 fade-in (scrollReveal=true 일 때).\n * - lightbox open 시 body scroll lock (overflow:hidden).\n * - images 가 빈 배열이면 null 반환.\n */\nexport function PublicGalleryMosaic(props: PublicGalleryMosaicProps): JSX.Element | null {\n const {\n images: rawImages,\n count = 7,\n i18n: i18nOverride,\n className,\n scrollReveal = true,\n hideHeader = false,\n } = props;\n\n const i18n = { ...FALLBACK_I18N, ...(i18nOverride ?? {}) };\n\n // count 만큼 잘라 사용 (images.length > count 인 경우 super-fluous 제거)\n const images = rawImages.slice(0, count);\n\n const lightbox = useGalleryLightbox(images);\n const { ref: revealRef, isVisible } = useScrollReveal({ once: true });\n\n // body scroll lock — lightbox 가 열린 동안 host 페이지의 스크롤을 막는다.\n // SSR safe: document 가 정의된 환경에서만 동작.\n useEffect(() => {\n if (!lightbox.isOpen) return;\n if (typeof document === \"undefined\") return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [lightbox.isOpen]);\n\n if (images.length === 0) return null;\n\n const showReveal = scrollReveal ? isVisible : true;\n\n return (\n <section\n ref={revealRef}\n className={cn(\n \"gallery-public-mosaic-section\",\n showReveal && \"gallery-public-mosaic-section--visible\",\n className,\n )}\n aria-label={i18n.sectionLabel}\n >\n {!hideHeader && (\n <header className=\"gallery-public-mosaic__head\">\n <div className=\"gallery-public-mosaic__label\">{i18n.sectionLabel}</div>\n <h2 className=\"gallery-public-mosaic__title\">{i18n.moments}</h2>\n </header>\n )}\n\n <div className=\"gallery-public-mosaic\" data-count={images.length}>\n {images.map((img, i) => (\n <div\n key={`${img.src}-${i}`}\n className=\"gallery-public-mosaic__item\"\n role=\"button\"\n tabIndex={0}\n aria-label={img.alt ?? i18n.expandAria}\n onClick={() => lightbox.open(i)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n lightbox.open(i);\n }\n }}\n >\n <img\n src={img.src}\n alt={img.alt ?? \"\"}\n className=\"gallery-public-mosaic__img\"\n loading=\"lazy\"\n />\n <div className=\"gallery-public-mosaic__hover\">\n <div className=\"gallery-public-mosaic__icon\" aria-hidden=\"true\">\n +\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {lightbox.isOpen && lightbox.current && (\n <div\n className=\"gallery-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={i18n.moments}\n onClick={lightbox.close}\n >\n <button\n type=\"button\"\n className=\"gallery-lightbox__close\"\n aria-label={i18n.lbClose}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.close();\n }}\n >\n ×\n </button>\n\n {images.length > 1 && (\n <>\n <button\n type=\"button\"\n className=\"gallery-lightbox__nav gallery-lightbox__prev\"\n aria-label={i18n.lbPrev}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.prev();\n }}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className=\"gallery-lightbox__nav gallery-lightbox__next\"\n aria-label={i18n.lbNext}\n onClick={(e) => {\n e.stopPropagation();\n lightbox.next();\n }}\n >\n ›\n </button>\n </>\n )}\n\n <div className=\"gallery-lightbox__content\" onClick={(e) => e.stopPropagation()}>\n <img\n src={lightbox.current.src}\n alt={lightbox.current.alt ?? \"\"}\n className=\"gallery-lightbox__img\"\n />\n </div>\n\n {images.length > 1 && (\n <div className=\"gallery-lightbox__counter\">\n {lightbox.currentIndex + 1} / {images.length}\n </div>\n )}\n </div>\n )}\n </section>\n );\n}\n"]}