@designbasekorea/figma-ui 0.6.4 → 0.6.8

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.esm.js CHANGED
@@ -1,10 +1,19 @@
1
- import React, { useRef, useState, useLayoutEffect, useEffect, createContext, useContext, useCallback, useMemo } from 'react';
2
- import { Button, Dropdown, Badge, Toggle, Spinner, SegmentControl, Logo, Toast, Input, Modal, Progressbar, RandomGradient } from '@designbasekorea/ui-wc/react';
1
+ import React, { useRef, useState, useLayoutEffect, useEffect, useId, createContext, useContext, useCallback, useMemo } from 'react';
2
+ import { Button, Dropdown, Badge, Toggle, Modal, Logo, Toast, Input, Spinner, SegmentControl, Progressbar, RandomGradient } from '@designbasekorea/ui-wc/react';
3
3
  export * from '@designbasekorea/ui-wc/react';
4
- import { ChevronDownIcon, AppWindowIcon, YoutubeIcon, InstagramIcon, FigmaIcon, MailIcon, ExternalLinkIcon, MoreHorizontalIcon, CloseIcon, CircleCheckFilledIcon, ResizableIcon, GripVerticalIcon, ChevronRightIcon } from '@designbasekorea/icons';
4
+ export { Accordion, ActivityHeatmap, AdBanner, AiChat, AiChatList, Alert, AnimationBackground, AnimationText, AppBadge, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomNavigation, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, ChatComposer, ChatList, ChatMessage, ChatOrb, ChatRoom, ChatSuggestions, ChatTyping, ChatWelcome, ChatWidget, Checkbox, Chip, CodeBlock, ColorPicker, ColorSwatch, Confirm, Container, ContextMenu, CookieBanner, Countdown, CursorFollower, DatePicker, Divider, DownloadProgress, Drawer, Dropdown, Dropzone, EmailVerify, EmptyState, FileUploader, FloatingActionButton, Footer, Form, Gantt, Gradient, Grid, GridBackground, GridItem, Image, ImageList, ImagePlaceholder, Indicator, Input, Kanban, Label, Lightbox, List, Logo, MarkdownEditor, Marquee, Masonry, MenuItem, Modal, ModalBody, ModalFooter, ModalHeader, Navbar, OnboardingModal, OtpInput, PageHeader, Pagination, PhoneVerify, Popover, Progress, ProgressStep, Progressbar, Radio, RandomGradient, RangeSlider, Rating, Reorder, ResizablePanels, ScrollArea, SearchBar, Section, SectionAppDownload, SectionContact, SectionCta, SectionFaq, SectionFeature, SectionFeatureGrid, SectionHero, SectionLogoCloud, SectionPricing, SectionStats, SectionTestimonials, SegmentControl, Select, Share, Sidebar, Signature, Skeleton, Spinner, SplitView, Stack, Stat, Stepper, Table, Tabs, Testimonial, Textarea, TimePicker, Timeline, Toast, Toggle, Toolbar, Tooltip, TopBanner, Tutorial, TwoFactor, VideoPlayer, YouTubePlayer, useContextMenu } from '@designbasekorea/ui-wc/react';
5
+ import { ChevronDownIcon, AppWindowIcon, YoutubeIcon, InstagramIcon, FigmaIcon, MailIcon, ExternalLinkIcon, TranslateIcon, MoreHorizontalIcon, CloseIcon, CircleCheckFilledIcon, ResizableIcon, GripVerticalIcon, ChevronRightIcon } from '@designbasekorea/icons';
5
6
 
6
7
  function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
7
8
 
9
+ function interpolateText(template, values) {
10
+ if (!values)
11
+ return template;
12
+ return Object.entries(values).reduce((result, [key, value]) => {
13
+ const replacement = value == null ? '' : String(value);
14
+ return result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), replacement);
15
+ }, template);
16
+ }
8
17
  function resolveText(t, input, fallback) {
9
18
  if (input == null)
10
19
  return fallback;
@@ -14,10 +23,13 @@ function resolveText(t, input, fallback) {
14
23
  if (t) {
15
24
  const translated = t(input.key, input.values);
16
25
  if (translated && translated !== input.key) {
17
- return translated;
26
+ return interpolateText(translated, input.values);
18
27
  }
19
28
  }
20
- return fallback ?? input.key;
29
+ if (fallback) {
30
+ return interpolateText(fallback, input.values);
31
+ }
32
+ return input.key;
21
33
  }
22
34
 
23
35
  const DONATION_ICON_NAMES = {
@@ -383,58 +395,63 @@ const FigmaSidebar = ({ categories, categoryGroups = {}, categoryLabels = {}, ac
383
395
  };
384
396
  FigmaSidebar.displayName = 'FigmaSidebar';
385
397
 
386
- const PaymentBadge = ({ isActive, onClick, isLoading = false, text, t = (key) => key, className, }) => {
387
- const getBadgeText = () => {
388
- if (text)
389
- return text;
390
- return isActive
391
- ? (t('proUser') || '프로 계정')
392
- : (t('usingFree') || '무료 사용');
393
- };
394
- const getBadgeVariant = () => {
395
- return isActive ? 'success' : 'secondary';
396
- };
397
- return (React.createElement("div", { className: `designbase-figma-payment-badge ${className || ''} ${isActive ? 'active' : 'free'}`, onClick: onClick, style: { cursor: onClick ? 'pointer' : 'default' } }, isLoading ? (React.createElement(Spinner, { size: "s" })) : (React.createElement(Badge, { variant: getBadgeVariant(), size: "s", className: "designbase-figma-payment-badge__badge" }, getBadgeText()))));
398
- };
399
- PaymentBadge.displayName = 'PaymentBadge';
400
-
401
- let useTranslation = null;
398
+ let useTranslation$2 = null;
402
399
  try {
403
400
  const reactI18next = require('react-i18next');
404
- useTranslation = reactI18next.useTranslation;
401
+ useTranslation$2 = reactI18next.useTranslation;
405
402
  }
406
403
  catch (e) {
407
404
  }
408
- const DEFAULT_LANGUAGES = [
409
- { code: 'ko', label: 'KO' },
410
- { code: 'en', label: 'EN' },
405
+ const DEFAULT_LANGUAGES$1 = [
406
+ { code: 'ko', label: '한국어' },
407
+ { code: 'en', label: 'English' },
408
+ { code: 'ja', label: '日本語' },
411
409
  ];
412
- const LanguageSelector = ({ currentLanguage, languages = DEFAULT_LANGUAGES, onLanguageChange, size = 's', className, t }) => {
413
- const i18n = useTranslation ? useTranslation().i18n : null;
414
- const activeLanguage = currentLanguage || (i18n?.language) || 'ko';
415
- const handleLanguageChange = (languageCode) => {
410
+ const defaultT$2 = (key) => {
411
+ const translations = {
412
+ languageSettings: '언어 설정',
413
+ languageSettingsDescription: '플러그인에 표시할 언어를 선택하세요.',
414
+ 'language.korean': '한국어',
415
+ 'language.english': 'English',
416
+ 'language.japanese': '日本語',
417
+ };
418
+ return translations[key] || key;
419
+ };
420
+ const LanguageSettingsModal = ({ isOpen, onClose, languages = DEFAULT_LANGUAGES$1, currentLanguage, onLanguageChange, title, description, className, t = defaultT$2, }) => {
421
+ const i18n = useTranslation$2 ? useTranslation$2().i18n : null;
422
+ const activeLanguage = currentLanguage || i18n?.language || languages[0]?.code;
423
+ const translate = (key, fallback) => {
424
+ const translated = t(key);
425
+ return translated && translated !== key ? translated : fallback;
426
+ };
427
+ const handleSelect = (languageCode) => {
416
428
  if (i18n) {
417
429
  i18n.changeLanguage(languageCode);
418
430
  }
419
- if (onLanguageChange) {
420
- onLanguageChange(languageCode);
421
- }
431
+ onLanguageChange?.(languageCode);
432
+ onClose();
422
433
  };
423
- const segmentOptions = languages.map((language) => ({
424
- value: language.code,
425
- label: resolveText(t, language.label),
426
- disabled: false,
427
- }));
428
- return (React.createElement("div", { className: `designbase-figma-language-selector ${className || ''}` },
429
- React.createElement(SegmentControl, { options: segmentOptions, value: activeLanguage, onChange: (event) => {
430
- const value = event.detail?.value;
431
- if (value) {
432
- handleLanguageChange(value);
433
- }
434
- }, size: size, className: "designbase-figma-language-selector__segment" })));
434
+ return (React.createElement(Modal, { isOpen: isOpen, onClose: onClose, title: title || translate('languageSettings', '언어 설정'), size: "s", className: clsx('designbase-figma-language-settings-modal', className) },
435
+ React.createElement("div", { className: "designbase-figma-language-settings-modal__content" },
436
+ React.createElement("p", { className: "designbase-figma-language-settings-modal__description" }, description || translate('languageSettingsDescription', '플러그인에 표시할 언어를 선택하세요.')),
437
+ React.createElement("ul", { className: "designbase-figma-language-settings-modal__list", role: "listbox", "aria-label": translate('languageSettings', '언어 설정') }, languages.map((language) => {
438
+ const label = resolveText(t, language.label, language.code.toUpperCase());
439
+ const isActive = language.code === activeLanguage;
440
+ return (React.createElement("li", { key: language.code, role: "presentation" },
441
+ React.createElement("button", { type: "button", className: clsx('designbase-figma-language-settings-modal__option', { 'designbase-figma-language-settings-modal__option--active': isActive }), role: "option", "aria-selected": isActive, onClick: () => handleSelect(language.code) },
442
+ React.createElement("span", { className: "designbase-figma-language-settings-modal__option-label" }, label),
443
+ React.createElement("span", { className: "designbase-figma-language-settings-modal__option-indicator", "aria-hidden": "true" }))));
444
+ })))));
435
445
  };
436
- LanguageSelector.displayName = 'LanguageSelector';
446
+ LanguageSettingsModal.displayName = 'LanguageSettingsModal';
437
447
 
448
+ let useTranslation$1 = null;
449
+ try {
450
+ const reactI18next = require('react-i18next');
451
+ useTranslation$1 = reactI18next.useTranslation;
452
+ }
453
+ catch (e) {
454
+ }
438
455
  const defaultLinks = [
439
456
  {
440
457
  name: 'officialWebsite',
@@ -468,50 +485,219 @@ const defaultTranslations$1 = {
468
485
  instagram: '인스타그램',
469
486
  figmaCommunity: '피그마 커뮤니티',
470
487
  contact: '문의하기',
488
+ languageSettings: '언어 설정',
471
489
  };
472
490
  const defaultT$1 = (key) => defaultTranslations$1[key] || key;
473
- const LogoDropdown = ({ logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', links = defaultLinks, position = 'top-left', className, t = defaultT$1, }) => {
491
+ const defaultLanguages = [
492
+ { code: 'ko', label: '한국어' },
493
+ { code: 'en', label: 'English' },
494
+ { code: 'ja', label: '日本語' },
495
+ ];
496
+ const LogoDropdown = ({ logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', links = defaultLinks, position = 'top-left', className, t = defaultT$1, showLanguageSelector = true, languages = defaultLanguages, currentLanguage, onLanguageChange, }) => {
474
497
  const [isOpen, setIsOpen] = useState(false);
475
- const toggleDropdown = () => setIsOpen(!isOpen);
498
+ const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false);
499
+ const i18n = useTranslation$1 ? useTranslation$1().i18n : null;
500
+ const rootRef = useRef(null);
501
+ const triggerRef = useRef(null);
502
+ const menuId = `designbase-figma-logo-menu-${useId().replace(/:/g, '')}`;
503
+ const activeLanguage = currentLanguage || i18n?.language || languages[0]?.code;
504
+ const translate = (key, fallback) => {
505
+ const translated = t(key);
506
+ return translated && translated !== key ? translated : fallback;
507
+ };
508
+ useEffect(() => {
509
+ if (!isOpen) {
510
+ return undefined;
511
+ }
512
+ const handlePointerDown = (event) => {
513
+ if (!rootRef.current?.contains(event.target)) {
514
+ setIsOpen(false);
515
+ }
516
+ };
517
+ const handleKeyDown = (event) => {
518
+ if (event.key === 'Escape') {
519
+ setIsOpen(false);
520
+ triggerRef.current?.focus();
521
+ }
522
+ };
523
+ document.addEventListener('mousedown', handlePointerDown);
524
+ document.addEventListener('keydown', handleKeyDown);
525
+ return () => {
526
+ document.removeEventListener('mousedown', handlePointerDown);
527
+ document.removeEventListener('keydown', handleKeyDown);
528
+ };
529
+ }, [isOpen]);
530
+ const toggleDropdown = () => setIsOpen((value) => !value);
476
531
  const handleLinkClick = (url) => {
532
+ setIsOpen(false);
477
533
  if (url.startsWith('mailto:')) {
478
534
  return;
479
535
  }
480
536
  window.open(url, '_blank', 'noopener,noreferrer');
481
537
  };
538
+ const handleLanguageSettingsClick = () => {
539
+ setIsOpen(false);
540
+ setIsLanguageModalOpen(true);
541
+ };
542
+ const handleLanguageChange = (languageCode) => {
543
+ if (i18n) {
544
+ i18n.changeLanguage(languageCode);
545
+ }
546
+ onLanguageChange?.(languageCode);
547
+ };
482
548
  const classes = clsx('designbase-figma-logo-dropdown', `designbase-figma-logo-dropdown--${position}`, {
483
549
  'designbase-figma-logo-dropdown--open': isOpen,
484
550
  }, className);
485
551
  const renderLogo = () => {
486
- if (logoSrc) {
487
- return (React.createElement("img", { className: "designbase-figma-logo-dropdown__logo", src: logoSrc, alt: logoAlt, onClick: toggleDropdown }));
488
- }
489
- return (React.createElement("div", { className: "designbase-figma-logo-dropdown__logo", onClick: toggleDropdown },
490
- React.createElement(Logo, { type: logoType, size: logoSize, variant: "default" })));
552
+ return (React.createElement("button", { ref: triggerRef, type: "button", className: "designbase-figma-logo-dropdown__logo", "aria-label": logoAlt, "aria-expanded": isOpen, "aria-controls": menuId, "aria-haspopup": "menu", onClick: toggleDropdown }, logoSrc ? (React.createElement("img", { src: logoSrc, alt: "" })) : (React.createElement(Logo, { type: logoType, size: logoSize, variant: "default" }))));
491
553
  };
492
- return (React.createElement("div", { className: classes },
554
+ return (React.createElement("div", { ref: rootRef, className: classes },
493
555
  renderLogo(),
494
- isOpen && (React.createElement("ul", { className: "designbase-figma-logo-dropdown__menu" }, links.map((link, index) => (React.createElement("li", { key: index, className: "designbase-figma-logo-dropdown__item" },
495
- React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", onClick: () => handleLinkClick(link.url) },
496
- link.icon && (React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" }, link.icon)),
497
- React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, t(link.name)),
498
- React.createElement(ExternalLinkIcon, { size: 16, className: "designbase-figma-logo-dropdown__arrow" })))))))));
556
+ isOpen && (React.createElement("ul", { id: menuId, className: "designbase-figma-logo-dropdown__menu", role: "menu" },
557
+ links.map((link, index) => (React.createElement("li", { key: index, className: "designbase-figma-logo-dropdown__item", role: "none" },
558
+ React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", role: "menuitem", onClick: () => handleLinkClick(link.url) },
559
+ link.icon && (React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" }, link.icon)),
560
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, t(link.name)),
561
+ React.createElement(ExternalLinkIcon, { size: 16, className: "designbase-figma-logo-dropdown__arrow" }))))),
562
+ showLanguageSelector && (React.createElement("li", { className: "designbase-figma-logo-dropdown__item designbase-figma-logo-dropdown__language-item", role: "none" },
563
+ React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", role: "menuitem", onClick: handleLanguageSettingsClick },
564
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" },
565
+ React.createElement(TranslateIcon, { size: 20, "aria-hidden": "true" })),
566
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, translate('languageSettings', '언어 설정'))))))),
567
+ showLanguageSelector && (React.createElement(LanguageSettingsModal, { isOpen: isLanguageModalOpen, onClose: () => setIsLanguageModalOpen(false), languages: languages, currentLanguage: activeLanguage, onLanguageChange: handleLanguageChange, t: t }))));
499
568
  };
500
569
  LogoDropdown.displayName = 'LogoDropdown';
501
570
 
571
+ const fallbackText = (t, key, fallback) => {
572
+ const translated = t?.(key);
573
+ return translated && translated !== key ? translated : fallback;
574
+ };
575
+ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, maxDailyUsage = 20, showUsage = true, showPlan = true, isLoading = false, onLicensePageClick, t, className, }) => {
576
+ const [isOpen, setIsOpen] = useState(false);
577
+ const rootRef = useRef(null);
578
+ const triggerRef = useRef(null);
579
+ const popoverId = `designbase-figma-usage-popover-${useId().replace(/:/g, '')}`;
580
+ const isPaid = paymentStatus === 'PAID';
581
+ const isBeta = paymentStatus === 'BETA';
582
+ const hasDailyLimit = !isPaid && maxDailyUsage > 0;
583
+ const limit = Math.max(1, Math.round(maxDailyUsage));
584
+ const used = Math.min(limit, Math.max(0, Math.round(usageCount)));
585
+ const remaining = Math.max(0, limit - used);
586
+ const usedPercent = hasDailyLimit ? Math.round((used / limit) * 100) : 0;
587
+ const hasControl = showUsage || showPlan;
588
+ const planLabel = isPaid
589
+ ? fallbackText(t, 'proUser', '프로')
590
+ : isBeta
591
+ ? fallbackText(t, 'betaStatus', 'BETA')
592
+ : paymentStatus === 'TRIAL'
593
+ ? fallbackText(t, 'trialPlan', '체험판')
594
+ : fallbackText(t, 'free', '무료');
595
+ const planTitle = isBeta
596
+ ? planLabel
597
+ : `${planLabel} ${fallbackText(t, 'planSuffix', '플랜')}`;
598
+ const triggerLabel = isPaid
599
+ ? `${planLabel}, ${fallbackText(t, 'unlimitedUsage', '무제한 사용 가능')}`
600
+ : isBeta
601
+ ? planLabel
602
+ : hasDailyLimit
603
+ ? `${remaining}/${limit} ${fallbackText(t, 'remainingShort', '남음')}`
604
+ : planLabel;
605
+ const upgradeLabel = isPaid
606
+ ? fallbackText(t, 'managePlan', '플랜 관리')
607
+ : fallbackText(t, 'upgradePlan', '플랜 업그레이드');
608
+ useEffect(() => {
609
+ if (!isOpen)
610
+ return undefined;
611
+ const handlePointerDown = (event) => {
612
+ if (!rootRef.current?.contains(event.target)) {
613
+ setIsOpen(false);
614
+ }
615
+ };
616
+ const handleKeyDown = (event) => {
617
+ if (event.key === 'Escape') {
618
+ setIsOpen(false);
619
+ triggerRef.current?.focus();
620
+ }
621
+ };
622
+ document.addEventListener('mousedown', handlePointerDown);
623
+ document.addEventListener('keydown', handleKeyDown);
624
+ return () => {
625
+ document.removeEventListener('mousedown', handlePointerDown);
626
+ document.removeEventListener('keydown', handleKeyDown);
627
+ };
628
+ }, [isOpen]);
629
+ if (!hasControl)
630
+ return null;
631
+ const handleManageClick = () => {
632
+ setIsOpen(false);
633
+ onLicensePageClick?.();
634
+ };
635
+ const togglePopover = () => setIsOpen((value) => !value);
636
+ return (React.createElement("div", { ref: rootRef, className: clsx('designbase-figma-usage-status', {
637
+ 'designbase-figma-usage-status--open': isOpen,
638
+ 'designbase-figma-usage-status--unlimited': isPaid,
639
+ 'designbase-figma-usage-status--loading': isLoading,
640
+ }, className) },
641
+ React.createElement("div", { className: "designbase-figma-usage-status__control" },
642
+ showUsage && (React.createElement("button", { ref: triggerRef, type: "button", className: "designbase-figma-usage-status__trigger", "aria-expanded": isOpen, "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": isLoading ? fallbackText(t, 'loading', '불러오는 중') : triggerLabel, disabled: isLoading, onClick: togglePopover }, isLoading ? (React.createElement("span", { className: "designbase-figma-usage-status__skeleton", "aria-hidden": "true" })) : isPaid ? (React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E")) : hasDailyLimit ? (React.createElement(React.Fragment, null,
643
+ React.createElement("span", { className: "designbase-figma-usage-status__mini-progress", "aria-hidden": "true" },
644
+ React.createElement("span", { style: { width: `${usedPercent}%` } })),
645
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-value" },
646
+ React.createElement("strong", null, remaining),
647
+ React.createElement("span", null,
648
+ "/",
649
+ limit)))) : (React.createElement("span", { className: "designbase-figma-usage-status__plan-label" }, planLabel)))),
650
+ showPlan && !isLoading && (React.createElement("button", { type: "button", className: "designbase-figma-usage-status__plan-badge", "aria-expanded": isOpen, "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": planTitle, onClick: togglePopover }, planLabel))),
651
+ isOpen && (React.createElement("div", { id: popoverId, className: "designbase-figma-usage-status__popover", role: "dialog", "aria-label": fallbackText(t, 'usageDetails', '사용량 상세') },
652
+ React.createElement("div", { className: "designbase-figma-usage-status__popover-header" },
653
+ React.createElement("span", { className: "designbase-figma-usage-status__plan-title" }, planTitle),
654
+ !isBeta && (React.createElement(Button, { className: "designbase-figma-usage-status__upgrade", variant: isPaid ? 'secondary' : 'primary', size: "s", endIcon: "chevron-right", onPress: handleManageClick }, upgradeLabel))),
655
+ isPaid ? (React.createElement("div", { className: "designbase-figma-usage-status__unlimited-copy" },
656
+ React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E"),
657
+ React.createElement("span", null, fallbackText(t, 'unlimitedUsageTooltip', '모든 기능을 제한 없이 사용할 수 있습니다')))) : isBeta ? (React.createElement("p", { className: "designbase-figma-usage-status__note" }, fallbackText(t, 'betaUsageDescription', '베타 기간에는 사용량 제한이 적용되지 않습니다'))) : hasDailyLimit ? (React.createElement("div", { className: "designbase-figma-usage-status__usage-detail" },
658
+ React.createElement("div", { className: "designbase-figma-usage-status__usage-summary" },
659
+ React.createElement("strong", { className: "designbase-figma-usage-status__usage-count" },
660
+ used,
661
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-divider" }, "/"),
662
+ limit),
663
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-caption" }, fallbackText(t, 'usageCountCaption', '오늘 사용'))),
664
+ React.createElement("div", { className: "designbase-figma-usage-status__progress", role: "progressbar", "aria-label": fallbackText(t, 'usageCountCaption', '오늘 사용'), "aria-valuemin": 0, "aria-valuemax": limit, "aria-valuenow": used, "aria-valuetext": `${used} / ${limit}` },
665
+ React.createElement("span", { style: { width: `${usedPercent}%` } })),
666
+ React.createElement("p", { className: "designbase-figma-usage-status__remaining-copy" }, resolveText(t, { key: 'remainingCopy', values: { count: remaining } }, `${remaining}회 남음 · 매일 리셋`)))) : (React.createElement("p", { className: "designbase-figma-usage-status__note" }, fallbackText(t, 'usageUnavailable', '사용량 제한이 없습니다')))))));
667
+ };
668
+ UsageStatusPopover.displayName = 'UsageStatusPopover';
669
+
502
670
  const defaultTranslations = {
503
671
  unlimitedUsage: '무제한 사용',
672
+ proUser: '프로',
673
+ free: '무료',
674
+ trialPlan: '체험판',
675
+ planSuffix: '플랜',
676
+ usageDetails: '사용량 상세',
677
+ dailyUsage: '오늘 사용량',
678
+ usedToday: '오늘 사용',
679
+ remainingToday: '오늘 남은 사용량',
680
+ remainingUsage: '오늘 남은 사용량',
681
+ managePlan: '플랜 관리',
682
+ upgradePlan: '플랜 업그레이드',
683
+ remainingShort: '남음',
684
+ usageCountCaption: '오늘 사용',
685
+ remainingCopy: '{{count}}회 남음 · 매일 리셋',
686
+ usageUnavailable: '사용량 제한이 없습니다',
687
+ betaUsageDescription: '베타 기간에는 사용량 제한이 적용되지 않습니다',
504
688
  perDay: '일',
505
689
  resetsDaily: '매일 리셋',
506
690
  donationPrompt: '이 플러그인이 유용했나요?',
507
691
  betaStatus: 'BETA',
508
692
  };
509
- const defaultT = (key) => defaultTranslations[key] || key;
510
- const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount = 0, isLoading = false, showPaymentStatus = true, showUsageInfo = true, maxDailyUsage = 20, showPaymentBadge = true, showDonation = true, donationUrl = 'https://buymeacoffee.com/designbase', donationText = 'Buy me a coffee', showDonationText = true, donationPromptText, showLanguageSelector = false, currentLanguage = 'ko', languages, onLanguageChange, logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', logoLinks, className, t = defaultT, children, }) => {
511
- const isActive = paymentStatus === 'PAID';
512
- const isBeta = paymentStatus === 'BETA';
513
- const resolvedShowUsageInfo = showUsageInfo && !isLoading && !isBeta;
693
+ const defaultT = (key, values) => {
694
+ const template = defaultTranslations[key] || key;
695
+ return interpolateText(template, values);
696
+ };
697
+ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount = 0, isLoading = false, showPaymentStatus = true, showUsageInfo = true, maxDailyUsage = 20, showPaymentBadge = true, showDonation = true, donationUrl = 'https://buymeacoffee.com/designbase', donationText = 'Buy me a coffee', showDonationText = true, donationPromptText, showLanguageSelector = true, currentLanguage = 'ko', languages, onLanguageChange, logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', logoLinks, className, t = defaultT, children, }) => {
698
+ const resolvedShowUsageInfo = showPaymentStatus && showUsageInfo;
514
699
  const resolvedShowPaymentBadge = showPaymentStatus && showPaymentBadge;
700
+ const resolvedLicensePageClick = paymentStatus === 'BETA' ? undefined : onLicensePageClick;
515
701
  const hasChildren = React.Children.count(children) > 0;
516
702
  const classes = clsx('designbase-figma-footer', {
517
703
  'designbase-figma-footer--has-children': hasChildren,
@@ -519,17 +705,9 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
519
705
  return (React.createElement("footer", { className: classes },
520
706
  React.createElement("div", { className: "designbase-figma-footer__wrap" },
521
707
  React.createElement("div", { className: "designbase-figma-footer__left" },
522
- React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, t: t }),
523
- showLanguageSelector && (React.createElement(LanguageSelector, { currentLanguage: currentLanguage, languages: languages, onLanguageChange: onLanguageChange, size: "s" }))),
708
+ React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, languages: languages, currentLanguage: currentLanguage, showLanguageSelector: showLanguageSelector, onLanguageChange: onLanguageChange, t: t })),
524
709
  React.createElement("div", { className: "designbase-figma-footer__right" },
525
- showPaymentStatus && resolvedShowUsageInfo && (React.createElement("div", { className: "designbase-figma-footer__usage-info" }, isActive ? (React.createElement("span", { className: "designbase-figma-footer__unlimited-usage designbase-figma-footer__unlimited-usage--tooltip", title: t('unlimitedUsageTooltip') }, t('unlimitedUsage'))) : (React.createElement("span", { className: "designbase-figma-footer__usage-info designbase-figma-footer__usage-info--tooltip", title: t('resetsDaily') },
526
- React.createElement("span", { className: "designbase-figma-footer__usage-count" }, usageCount),
527
- React.createElement("span", { className: "designbase-figma-footer__max-daily-usage" },
528
- "/",
529
- maxDailyUsage,
530
- " ",
531
- t('perDay')))))),
532
- showPaymentStatus && resolvedShowPaymentBadge && (React.createElement(PaymentBadge, { isActive: isActive, onClick: isBeta ? undefined : onLicensePageClick, isLoading: isLoading, text: isBeta ? (t('betaStatus') || 'BETA') : undefined, t: t })),
710
+ (resolvedShowUsageInfo || resolvedShowPaymentBadge) && (React.createElement(UsageStatusPopover, { paymentStatus: paymentStatus, usageCount: usageCount, maxDailyUsage: maxDailyUsage, showUsage: resolvedShowUsageInfo, showPlan: resolvedShowPaymentBadge, isLoading: isLoading, onLicensePageClick: resolvedLicensePageClick, t: t })),
533
711
  showDonation && (React.createElement(DonationBadge, { donationUrl: donationUrl, text: donationText, iconType: "heart", size: "s", iconOnly: true, showPrompt: false, t: t })))),
534
712
  children));
535
713
  };
@@ -653,6 +831,205 @@ const InteractionFeedback = ({ status = 'default', message, statusMessage, leftC
653
831
  };
654
832
  InteractionFeedback.displayName = 'InteractionFeedback';
655
833
 
834
+ let useTranslation = null;
835
+ try {
836
+ const reactI18next = require('react-i18next');
837
+ useTranslation = reactI18next.useTranslation;
838
+ }
839
+ catch (e) {
840
+ }
841
+ const DEFAULT_LANGUAGES = [
842
+ { code: 'ko', label: 'KO' },
843
+ { code: 'en', label: 'EN' },
844
+ ];
845
+ const LanguageSelector = ({ currentLanguage, languages = DEFAULT_LANGUAGES, onLanguageChange, size = 's', className, t }) => {
846
+ const i18n = useTranslation ? useTranslation().i18n : null;
847
+ const activeLanguage = currentLanguage || (i18n?.language) || 'ko';
848
+ const handleLanguageChange = (languageCode) => {
849
+ if (i18n) {
850
+ i18n.changeLanguage(languageCode);
851
+ }
852
+ if (onLanguageChange) {
853
+ onLanguageChange(languageCode);
854
+ }
855
+ };
856
+ const segmentOptions = languages.map((language) => ({
857
+ value: language.code,
858
+ label: resolveText(t, language.label),
859
+ disabled: false,
860
+ }));
861
+ return (React.createElement("div", { className: `designbase-figma-language-selector ${className || ''}` },
862
+ React.createElement(SegmentControl, { options: segmentOptions, value: activeLanguage, onChange: (event) => {
863
+ const detail = event.detail;
864
+ const value = typeof detail === 'object' && detail !== null && 'value' in detail &&
865
+ typeof detail.value === 'string'
866
+ ? detail.value
867
+ : undefined;
868
+ if (value) {
869
+ handleLanguageChange(value);
870
+ }
871
+ }, size: size, className: "designbase-figma-language-selector__segment" })));
872
+ };
873
+ LanguageSelector.displayName = 'LanguageSelector';
874
+
875
+ function postPluginMessage(message) {
876
+ if (typeof parent === 'undefined') {
877
+ return;
878
+ }
879
+ parent.postMessage({ pluginMessage: message }, '*');
880
+ }
881
+ function openExternalUrl(url) {
882
+ if (!url)
883
+ return;
884
+ postPluginMessage({ type: 'open-external-url', url });
885
+ if (typeof window !== 'undefined') {
886
+ window.open(url, '_blank', 'noopener,noreferrer');
887
+ }
888
+ }
889
+
890
+ const LicenseOffer = ({ joinUrl, guideUrl, membershipUrl, t, className, }) => {
891
+ const titleId = `designbase-license-offer-title-${useId().replace(/:/g, '')}`;
892
+ const resolvedJoinUrl = joinUrl || membershipUrl;
893
+ const steps = [
894
+ resolveText(t, { key: 'license.youtube_membership_step_1' }, 'Designbase YouTube 채널 멤버십에 가입합니다.'),
895
+ resolveText(t, { key: 'license.youtube_membership_step_2' }, '멤버 전용 커뮤니티에서 이번 달 키를 복사합니다.'),
896
+ resolveText(t, { key: 'license.youtube_membership_step_3' }, '아래 입력창에 키를 붙여 넣고 활성화합니다.'),
897
+ ];
898
+ return (React.createElement("section", { className: `designbase-figma-license-offer ${className || ''}`, "aria-labelledby": titleId },
899
+ React.createElement("h3", { id: titleId, className: "designbase-figma-license-offer__title" }, resolveText(t, { key: 'license.youtube_membership_howto' }, '이용 방법')),
900
+ React.createElement("ol", { className: "designbase-figma-license-offer__steps" }, steps.map((step, index) => (React.createElement("li", { key: index },
901
+ React.createElement("span", { className: "designbase-figma-license-offer__step-index", "aria-hidden": "true" },
902
+ index + 1,
903
+ "."),
904
+ React.createElement("span", null, step))))),
905
+ (resolvedJoinUrl || guideUrl) && (React.createElement("div", { className: "designbase-figma-license-offer__actions" },
906
+ resolvedJoinUrl && (React.createElement(Button, { onPress: () => openExternalUrl(resolvedJoinUrl), variant: "primary", size: "m", fullWidth: true }, resolveText(t, { key: 'license.youtube_membership_join' }, '멤버십 가입'))),
907
+ guideUrl && (React.createElement(Button, { onPress: () => openExternalUrl(guideUrl), variant: "secondary", size: "m", fullWidth: true },
908
+ React.createElement("span", null, resolveText(t, { key: 'license.youtube_membership_guide' }, '안내보기')),
909
+ React.createElement(ExternalLinkIcon, { size: 16, "aria-hidden": "true" })))))));
910
+ };
911
+ LicenseOffer.displayName = 'LicenseOffer';
912
+
913
+ function maskLicenseKey(value) {
914
+ if (value.length <= 8)
915
+ return '••••••••';
916
+ return `${value.slice(0, 4)}••••${value.slice(-4)}`;
917
+ }
918
+ function formatDate(value) {
919
+ if (!value)
920
+ return undefined;
921
+ const date = new Date(value);
922
+ if (Number.isNaN(date.getTime()))
923
+ return value;
924
+ return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date);
925
+ }
926
+ function formatScope(value, t) {
927
+ if (!value)
928
+ return undefined;
929
+ if (value === 'all-products' || value === 'all_products') {
930
+ return resolveText(t, { key: 'license.all_plugins' }, '모든 플러그인');
931
+ }
932
+ return value;
933
+ }
934
+ function formatSource(value, t) {
935
+ if (!value)
936
+ return undefined;
937
+ if (value === 'youtube_membership' || value === 'membership') {
938
+ return resolveText(t, { key: 'license.source_youtube_membership' }, 'YouTube 멤버십');
939
+ }
940
+ if (value === 'lemonsqueezy') {
941
+ return resolveText(t, { key: 'license.source_purchase' }, '구매 라이선스');
942
+ }
943
+ return value;
944
+ }
945
+ const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSource, accessScope, expiresAt, lastVerifiedAt, validationMessage, allowRevealKey = false, t, className, }) => {
946
+ const [isKeyRevealed, setIsKeyRevealed] = useState(false);
947
+ const normalizedLicenseStatus = licenseStatus?.toLowerCase();
948
+ const isInactiveStatus = ['expired', 'revoked', 'invalid', 'inactive'].includes(normalizedLicenseStatus || '');
949
+ const isActive = status === 'PAID' && !isInactiveStatus;
950
+ const statusText = isActive
951
+ ? resolveText(t, { key: 'license.status_active' }, 'Active')
952
+ : resolveText(t, { key: 'license.status_inactive' }, 'License check failed');
953
+ const statusClass = isActive ? 'active' : 'inactive';
954
+ return (React.createElement("section", { className: `designbase-figma-license-status-card designbase-figma-license-status-card--${statusClass} ${className || ''}`, "aria-label": statusText },
955
+ React.createElement("div", { className: "designbase-figma-license-status-card__header" },
956
+ React.createElement("div", null,
957
+ React.createElement("div", { className: "designbase-figma-license-status-card__eyebrow" }, isActive
958
+ ? resolveText(t, { key: 'license.activated' }, '라이선스 활성화됨')
959
+ : resolveText(t, { key: 'license.status_label' }, '라이선스 상태')),
960
+ React.createElement("h3", null, statusText)),
961
+ React.createElement("span", { className: "designbase-figma-license-status-card__badge" }, licenseStatus || (isActive ? 'active' : 'inactive'))),
962
+ validationMessage && !isActive && (React.createElement("p", { className: "designbase-figma-license-status-card__message" }, validationMessage)),
963
+ licenseKey && (React.createElement("div", { className: "designbase-figma-license-status-card__key-row" },
964
+ React.createElement("span", null, resolveText(t, { key: 'license.key_label' }, '라이선스 키')),
965
+ React.createElement("code", null, allowRevealKey && isKeyRevealed ? licenseKey : maskLicenseKey(licenseKey)),
966
+ allowRevealKey && (React.createElement(Button, { type: "button", onPress: () => setIsKeyRevealed((value) => !value), variant: "tertiary", size: "s" }, isKeyRevealed
967
+ ? resolveText(t, { key: 'license.hide_key' }, '숨기기')
968
+ : resolveText(t, { key: 'license.reveal_key' }, '보기'))))),
969
+ (licenseSource || accessScope || expiresAt || lastVerifiedAt) && (React.createElement("dl", { className: "designbase-figma-license-status-card__meta" },
970
+ formatSource(licenseSource, t) && React.createElement(React.Fragment, null,
971
+ React.createElement("dt", null, resolveText(t, { key: 'license.source' }, '출처')),
972
+ React.createElement("dd", null, formatSource(licenseSource, t))),
973
+ formatScope(accessScope, t) && React.createElement(React.Fragment, null,
974
+ React.createElement("dt", null, resolveText(t, { key: 'license.access' }, '접근 범위')),
975
+ React.createElement("dd", null, formatScope(accessScope, t))),
976
+ expiresAt && React.createElement(React.Fragment, null,
977
+ React.createElement("dt", null, resolveText(t, { key: 'license.expires' }, '만료일')),
978
+ React.createElement("dd", null, formatDate(expiresAt) || expiresAt)),
979
+ lastVerifiedAt && React.createElement(React.Fragment, null,
980
+ React.createElement("dt", null, resolveText(t, { key: 'license.last_checked' }, '최근 확인')),
981
+ React.createElement("dd", null, formatDate(lastVerifiedAt) || lastVerifiedAt))))));
982
+ };
983
+ LicenseStatusCard.displayName = 'LicenseStatusCard';
984
+
985
+ function resolvePaymentFeedbackVariant(errorCode, explicitVariant) {
986
+ if (explicitVariant)
987
+ return explicitVariant;
988
+ if (!errorCode)
989
+ return 'success';
990
+ const normalized = errorCode.toLowerCase();
991
+ if (normalized.includes('network')
992
+ || normalized.includes('server')
993
+ || normalized.includes('rate')
994
+ || normalized.includes('timeout')) {
995
+ return 'warning';
996
+ }
997
+ if (normalized.includes('info')) {
998
+ return 'info';
999
+ }
1000
+ return 'error';
1001
+ }
1002
+ const PaymentStatusSection = ({ activationLimit, activationUsage, licenseKey, onDeactivate, isDeactivating = false, showDetails = false, actionMessage, actionMessageVariant, errorCode, className, t, }) => {
1003
+ const remainingActivations = Math.max(0, activationLimit - activationUsage);
1004
+ const resolvedActionMessage = actionMessage ? resolveText(t, actionMessage) : undefined;
1005
+ const feedbackVariant = resolvedActionMessage
1006
+ ? resolvePaymentFeedbackVariant(errorCode, actionMessageVariant)
1007
+ : undefined;
1008
+ return (React.createElement("div", { className: clsx('designbase-figma-payment-status', className) },
1009
+ React.createElement("div", { className: "designbase-figma-payment-status__license-key" },
1010
+ resolveText(t, { key: 'payment.license_key_label' }, '라이선스 키'),
1011
+ ": ",
1012
+ React.createElement("span", null, licenseKey)),
1013
+ showDetails && (React.createElement("div", { className: "designbase-figma-payment-status__details" },
1014
+ React.createElement("div", { className: "designbase-figma-payment-status__activation-info" },
1015
+ React.createElement("div", { className: "designbase-figma-payment-status__remaining" },
1016
+ remainingActivations,
1017
+ ' ',
1018
+ resolveText(t, { key: 'payment.activations_remaining' }, '활성'),
1019
+ ' ',
1020
+ resolveText(t, { key: 'payment.spots_remaining' }, '자리 남음')),
1021
+ React.createElement("div", { className: "designbase-figma-payment-status__usage" },
1022
+ activationUsage,
1023
+ "/",
1024
+ activationLimit)),
1025
+ React.createElement("div", { className: "designbase-figma-payment-status__action" },
1026
+ React.createElement(Button, { onPress: onDeactivate, variant: "secondary", size: "s", fullWidth: true, disabled: isDeactivating }, isDeactivating
1027
+ ? resolveText(t, { key: 'payment.deactivating' }, '비활성화중...')
1028
+ : resolveText(t, { key: 'payment.deactivate_license' }, '라이선스 비활성화')),
1029
+ resolvedActionMessage && feedbackVariant && (React.createElement("p", { className: clsx('designbase-figma-payment-status__feedback', `designbase-figma-payment-status__feedback--${feedbackVariant}`), role: feedbackVariant === 'error' ? 'alert' : 'status', "aria-live": "polite" }, resolvedActionMessage)))))));
1030
+ };
1031
+ PaymentStatusSection.displayName = 'PaymentStatusSection';
1032
+
656
1033
  const defaultFeatures = [
657
1034
  { name: '사용 제한', free: '일일 제한', pro: '무제한' },
658
1035
  { name: '모든 주제 사용', free: false, pro: true },
@@ -686,7 +1063,11 @@ const PricingComparison = ({ features = defaultFeatures, pricing = defaultPricin
686
1063
  return (React.createElement("div", { className: `designbase-figma-pricing-comparison ${className || ''}` },
687
1064
  paymentType === 'subscription' && (React.createElement("div", { className: "designbase-figma-pricing-comparison__billing-toggle" },
688
1065
  React.createElement(SegmentControl, { value: isYearly ? 'yearly' : 'monthly', onChange: (event) => {
689
- const value = event.detail?.value;
1066
+ const detail = event.detail;
1067
+ const value = typeof detail === 'object' && detail !== null && 'value' in detail &&
1068
+ typeof detail.value === 'string'
1069
+ ? detail.value
1070
+ : undefined;
690
1071
  setIsYearly(value === 'yearly');
691
1072
  }, options: [
692
1073
  {
@@ -723,21 +1104,6 @@ const PricingComparison = ({ features = defaultFeatures, pricing = defaultPricin
723
1104
  };
724
1105
  PricingComparison.displayName = 'PricingComparison';
725
1106
 
726
- function postPluginMessage(message) {
727
- if (typeof parent === 'undefined') {
728
- return;
729
- }
730
- parent.postMessage({ pluginMessage: message }, '*');
731
- }
732
- function openExternalUrl(url) {
733
- if (!url)
734
- return;
735
- postPluginMessage({ type: 'open-external-url', url });
736
- if (typeof window !== 'undefined') {
737
- window.open(url, '_blank', 'noopener,noreferrer');
738
- }
739
- }
740
-
741
1107
  const DEFAULT_LICENSE_MESSAGE_TYPES = {
742
1108
  initialize: 'initialize',
743
1109
  verifyLicense: 'verify-license',
@@ -810,6 +1176,12 @@ function parseLicenseMessage(data) {
810
1176
  usageReason: readString(data, ['usageReason']) || undefined,
811
1177
  lastMessage: readString(data, ['message']) || undefined,
812
1178
  lastErrorCode: readString(data, ['errorCode']) || undefined,
1179
+ licenseStatus: readString(data, ['licenseStatus', 'license_status']) || undefined,
1180
+ licenseSource: readString(data, ['licenseSource', 'license_source', 'source']) || undefined,
1181
+ accessScope: readString(data, ['accessScope', 'access_scope', 'scope']) || undefined,
1182
+ expiresAt: readString(data, ['expiresAt', 'expires_at']) || undefined,
1183
+ lastVerifiedAt: readString(data, ['lastVerifiedAt', 'last_verified_at']) || undefined,
1184
+ validationMessage: readString(data, ['validationMessage', 'validation_message']) || undefined,
813
1185
  };
814
1186
  }
815
1187
  function useFigmaLicenseBridge(options = {}) {
@@ -852,15 +1224,27 @@ function useFigmaLicenseBridge(options = {}) {
852
1224
  if (type === messageTypes.verifyResult) {
853
1225
  setIsVerifying(false);
854
1226
  setIsStartingAuth(false);
855
- applyPatch(parseLicenseMessage(payload));
1227
+ applyPatch({
1228
+ ...parseLicenseMessage(payload),
1229
+ deactivateMessage: '',
1230
+ deactivateErrorCode: '',
1231
+ });
856
1232
  if (payload.success === true) {
857
1233
  applyPatch({ paymentStatus: 'PAID', isLicenseRegistered: true });
858
1234
  }
859
1235
  }
860
1236
  if (type === messageTypes.deactivateResult) {
861
1237
  setIsDeactivating(false);
862
- applyPatch(parseLicenseMessage(payload));
863
- if (payload.success === true) {
1238
+ const parsed = parseLicenseMessage(payload);
1239
+ const isSuccess = payload.success === true;
1240
+ applyPatch({
1241
+ ...parsed,
1242
+ deactivateMessage: parsed.lastMessage,
1243
+ deactivateErrorCode: isSuccess ? undefined : parsed.lastErrorCode,
1244
+ lastMessage: isSuccess ? undefined : parsed.lastMessage,
1245
+ lastErrorCode: isSuccess ? undefined : parsed.lastErrorCode,
1246
+ });
1247
+ if (isSuccess) {
864
1248
  applyPatch({
865
1249
  paymentStatus: 'UNPAID',
866
1250
  licenseKey: '',
@@ -868,6 +1252,8 @@ function useFigmaLicenseBridge(options = {}) {
868
1252
  isLicenseRegistered: false,
869
1253
  activationLimit: 0,
870
1254
  activationUsage: 0,
1255
+ deactivateMessage: parsed.lastMessage || '라이선스가 비활성화되었습니다.',
1256
+ deactivateErrorCode: undefined,
871
1257
  });
872
1258
  }
873
1259
  }
@@ -894,8 +1280,12 @@ function useFigmaLicenseBridge(options = {}) {
894
1280
  }, [messageTypes.verifyLicense]);
895
1281
  const deactivateLicense = useCallback(() => {
896
1282
  setIsDeactivating(true);
1283
+ applyPatch({
1284
+ deactivateMessage: '',
1285
+ deactivateErrorCode: '',
1286
+ });
897
1287
  postPluginMessage({ type: messageTypes.deactivateLicense });
898
- }, [messageTypes.deactivateLicense]);
1288
+ }, [applyPatch, messageTypes.deactivateLicense]);
899
1289
  const startDesignbaseAuth = useCallback(() => {
900
1290
  setIsStartingAuth(true);
901
1291
  postPluginMessage({ type: START_DESIGNBASE_AUTH_MESSAGE });
@@ -915,28 +1305,29 @@ function useFigmaLicenseBridge(options = {}) {
915
1305
  function formatUsageLabel(state, t) {
916
1306
  const { remainingUsage, usageUsed, usageLimit, usageReset } = state;
917
1307
  if (usageLimit === 'unlimited') {
918
- return resolveText(t, { key: 'license.usage_unlimited' }, '사용 횟수 제한 없음');
1308
+ return resolveText(t, { key: 'license.usage_unlimited' }, '사용 횟수 제한 없음') ?? '사용 횟수 제한 없음';
919
1309
  }
920
1310
  const limit = typeof usageLimit === 'number' ? usageLimit : remainingUsage + usageUsed;
921
1311
  const remaining = remainingUsage ?? Math.max(0, limit - usageUsed);
922
1312
  if (usageReset === 'daily') {
923
1313
  const fallback = `오늘 남은 횟수 ${remaining}/${limit}`;
924
- const translated = resolveText(t, { key: 'license.usage_daily' }, fallback);
1314
+ const translated = resolveText(t, { key: 'license.usage_daily' }, fallback) ?? fallback;
925
1315
  return translated.includes(String(remaining)) ? translated : fallback;
926
1316
  }
927
1317
  if (usageReset === 'monthly') {
928
1318
  const fallback = `이번 달 남은 횟수 ${remaining}/${limit}`;
929
- const translated = resolveText(t, { key: 'license.usage_monthly' }, fallback);
1319
+ const translated = resolveText(t, { key: 'license.usage_monthly' }, fallback) ?? fallback;
930
1320
  return translated.includes(String(remaining)) ? translated : fallback;
931
1321
  }
932
1322
  return `남은 횟수 ${remaining}/${limit}`;
933
1323
  }
934
- const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicenseKey = '', wordpressLinks, paymentPageUrl, enableAccountAuth = true, showLicenseKeyInput = true, pricingFeatures, pricing, paymentType = 'subscription', messageTypes, onStateChange, setPaymentStatus, setUsageCount, onLicenseSubmit, usageCount: initialUsageCount = 0, t, className, }) => {
1324
+ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicenseKey = '', wordpressLinks, paymentPageUrl, mode = 'pricing', membershipUrl, enableAccountAuth = true, showLicenseKeyInput = true, pricingFeatures, pricing, paymentType = 'subscription', messageTypes, onStateChange, setPaymentStatus, setUsageCount, onLicenseSubmit, usageCount: initialUsageCount = 0, t, className, }) => {
935
1325
  const links = useMemo(() => ({
936
1326
  purchaseUrl: wordpressLinks?.purchaseUrl ?? paymentPageUrl,
1327
+ membershipUrl: membershipUrl ?? wordpressLinks?.membershipUrl,
937
1328
  accountUrl: wordpressLinks?.accountUrl,
938
1329
  helpUrl: wordpressLinks?.helpUrl,
939
- }), [wordpressLinks, paymentPageUrl]);
1330
+ }), [wordpressLinks, paymentPageUrl, membershipUrl]);
940
1331
  const { state, isVerifying, isDeactivating, isStartingAuth, verifyLicense, deactivateLicense, startDesignbaseAuth, setLicenseKeyInput, } = useFigmaLicenseBridge({
941
1332
  messageTypes,
942
1333
  initialState: {
@@ -950,7 +1341,8 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
950
1341
  });
951
1342
  const [showDetails, setShowDetails] = useState(false);
952
1343
  const [licenseKeyDraft, setLicenseKeyDraft] = useState(initialLicenseKey);
953
- const isPaid = state.isLicenseRegistered || state.paymentStatus === 'PAID';
1344
+ const isPaid = state.isLicenseRegistered && state.paymentStatus === 'PAID';
1345
+ const isMembershipMode = mode === 'youtube-membership';
954
1346
  const usageLabel = formatUsageLabel(state, t);
955
1347
  const handleLicenseSubmit = async (submittedLicenseKey) => {
956
1348
  if (onLicenseSubmit) {
@@ -963,29 +1355,30 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
963
1355
  setLicenseKeyDraft(value);
964
1356
  setLicenseKeyInput(value);
965
1357
  };
966
- return (React.createElement("div", { className: `designbase-figma-page-license ${className || ''}` },
1358
+ return (React.createElement("div", { className: `designbase-figma-page-license designbase-figma-page-license--${mode} ${className || ''}`, "data-license-mode": mode },
967
1359
  React.createElement("div", { className: "designbase-figma-page-license__content" },
968
1360
  React.createElement("div", { className: "designbase-figma-page-license__header" },
969
1361
  React.createElement("div", { className: "designbase-figma-page-license__title" },
970
- React.createElement("h2", null, isPaid
971
- ? resolveText(t, { key: 'license.pro_account' }, '프로 계정')
972
- : resolveText(t, { key: 'license.upgrade_to_pro' }, '프로로 업그레이드')),
1362
+ React.createElement("h2", null, isMembershipMode
1363
+ ? resolveText(t, { key: 'license.youtube_membership' }, 'YouTube 멤버십')
1364
+ : isPaid
1365
+ ? resolveText(t, { key: 'license.pro_account' }, '프로 계정')
1366
+ : resolveText(t, { key: 'license.upgrade_to_pro' }, '프로로 업그레이드')),
973
1367
  state.planTier && state.planTier !== 'FREE' && (React.createElement("span", { className: "designbase-figma-page-license__tier" }, state.planTier))),
974
1368
  React.createElement("p", { className: "designbase-figma-page-license__description" }, isPaid
975
1369
  ? resolveText(t, { key: 'license.all_features_available' }, '모든 기능을 사용할 수 있습니다.')
976
- : resolveText(t, { key: 'license.purchase_for_unlimited' }, '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.')),
977
- !isPaid && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
978
- React.createElement(PricingComparison, { features: pricingFeatures, pricing: pricing, paymentType: paymentType, t: t }),
979
- state.lastMessage && (React.createElement("p", { className: `designbase-figma-page-license__feedback ${state.lastErrorCode ? 'designbase-figma-page-license__feedback--error' : ''}` }, state.lastMessage)),
1370
+ : isMembershipMode
1371
+ ? resolveText(t, { key: 'license.youtube_membership_description' }, 'YouTube 멤버십에 가입하면 Designbase 플러그인을 무제한으로 사용할 수 있습니다.')
1372
+ : resolveText(t, { key: 'license.purchase_for_unlimited' }, '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.')),
1373
+ !isPaid && !isMembershipMode && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
1374
+ !isMembershipMode && (React.createElement(PricingComparison, { features: pricingFeatures, pricing: pricing, paymentType: paymentType, t: t })),
1375
+ state.lastMessage && !state.deactivateMessage && (React.createElement("p", { className: `designbase-figma-page-license__feedback ${state.lastErrorCode ? 'designbase-figma-page-license__feedback--error' : ''}`, "aria-live": "polite" }, state.lastMessage)),
980
1376
  isPaid && (React.createElement("div", { className: "designbase-figma-page-license__section" },
981
1377
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
982
1378
  React.createElement("h3", null, resolveText(t, { key: 'license.activated' }, '라이선스 활성화됨')),
983
- React.createElement(Button, { onClick: () => setShowDetails(!showDetails), variant: "tertiary", size: "s", iconOnly: true },
1379
+ React.createElement(Button, { onPress: () => setShowDetails(!showDetails), variant: "tertiary", size: "s", iconOnly: true },
984
1380
  React.createElement(MoreHorizontalIcon, { size: 16 }))),
985
- state.licenseKey && (React.createElement("div", { className: "designbase-figma-page-license__license-key" },
986
- resolveText(t, { key: 'license.key_label' }, '라이선스 키'),
987
- ": ",
988
- React.createElement("span", null, state.licenseKey))),
1381
+ React.createElement(LicenseStatusCard, { status: state.paymentStatus, licenseKey: state.licenseKey, licenseStatus: state.licenseStatus, licenseSource: state.licenseSource, accessScope: state.accessScope, expiresAt: state.expiresAt, lastVerifiedAt: state.lastVerifiedAt, validationMessage: state.validationMessage, allowRevealKey: false, t: t }),
989
1382
  React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel),
990
1383
  showDetails && (React.createElement("div", { className: "designbase-figma-page-license__details" },
991
1384
  React.createElement("div", { className: "designbase-figma-page-license__activation-info" },
@@ -999,59 +1392,54 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
999
1392
  state.activationUsage,
1000
1393
  "/",
1001
1394
  state.activationLimit)),
1002
- React.createElement(Button, { onClick: deactivateLicense, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
1003
- ? resolveText(t, { key: 'license.deactivating' }, '비활성화 중...')
1004
- : resolveText(t, { key: 'license.deactivate' }, '라이선스 비활성화')))))),
1395
+ React.createElement("div", { className: "designbase-figma-page-license__action" },
1396
+ React.createElement(Button, { onPress: deactivateLicense, variant: "secondary", size: "s", fullWidth: true, disabled: isDeactivating }, isDeactivating
1397
+ ? resolveText(t, { key: 'license.deactivating' }, '비활성화 중...')
1398
+ : resolveText(t, { key: 'license.deactivate' }, '라이선스 비활성화')),
1399
+ state.deactivateMessage && (React.createElement("p", { className: `designbase-figma-page-license__action-feedback designbase-figma-page-license__action-feedback--${resolvePaymentFeedbackVariant(state.deactivateErrorCode)}`, role: state.deactivateErrorCode ? 'alert' : 'status', "aria-live": "polite" }, state.deactivateMessage))))))),
1005
1400
  !isPaid && (React.createElement(React.Fragment, null,
1006
- (enableAccountAuth || links.purchaseUrl || links.accountUrl) && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1401
+ isMembershipMode && (React.createElement(LicenseOffer, { joinUrl: links.membershipUrl || links.purchaseUrl, guideUrl: links.helpUrl || links.purchaseUrl, t: t })),
1402
+ !isMembershipMode && (enableAccountAuth || links.purchaseUrl || links.accountUrl) && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1007
1403
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1008
1404
  React.createElement("h3", null, resolveText(t, { key: 'license.designbase_account' }, '디자인베이스 계정'))),
1009
1405
  React.createElement("p", { className: "designbase-figma-page-license__hint" }, resolveText(t, { key: 'license.designbase_account_hint' }, 'WordPress 계정으로 로그인하거나 멤버십을 구매하면 플러그인이 자동으로 활성화됩니다.')),
1010
1406
  React.createElement("div", { className: "designbase-figma-page-license__actions" },
1011
- enableAccountAuth && (React.createElement(Button, { onClick: startDesignbaseAuth, variant: "primary", size: "m", disabled: isStartingAuth || isVerifying, fullWidth: true }, isStartingAuth
1407
+ enableAccountAuth && (React.createElement(Button, { onPress: startDesignbaseAuth, variant: "primary", size: "m", disabled: isStartingAuth || isVerifying, fullWidth: true }, isStartingAuth
1012
1408
  ? resolveText(t, { key: 'license.authenticating' }, '인증 중...')
1013
1409
  : resolveText(t, { key: 'license.login_with_designbase' }, '디자인베이스로 로그인'))),
1014
- links.purchaseUrl && (React.createElement(Button, { onClick: () => openExternalUrl(links.purchaseUrl), variant: "secondary", size: "m", fullWidth: true },
1015
- React.createElement("span", null, resolveText(t, { key: 'license.purchase_membership' }, '멤버십 구매')),
1410
+ links.purchaseUrl && (React.createElement(Button, { onPress: () => openExternalUrl(links.purchaseUrl), variant: "secondary", size: "m", fullWidth: true },
1411
+ React.createElement("span", null, resolveText(t, { key: 'license.purchase_membership' }, '멤버십 안내')),
1016
1412
  React.createElement(ExternalLinkIcon, { size: 16 }))),
1017
- links.accountUrl && (React.createElement(Button, { onClick: () => openExternalUrl(links.accountUrl), variant: "tertiary", size: "s", fullWidth: true },
1413
+ links.accountUrl && (React.createElement(Button, { onPress: () => openExternalUrl(links.accountUrl), variant: "tertiary", size: "s", fullWidth: true },
1018
1414
  React.createElement("span", null, resolveText(t, { key: 'license.manage_account' }, '계정·멤버십 관리')),
1019
1415
  React.createElement(ExternalLinkIcon, { size: 16 })))))),
1020
- showLicenseKeyInput && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1416
+ showLicenseKeyInput && (React.createElement("div", { className: isMembershipMode
1417
+ ? 'designbase-figma-page-license__key-form'
1418
+ : 'designbase-figma-page-license__section' },
1021
1419
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1022
1420
  React.createElement("h3", null, resolveText(t, { key: 'license.enter_key' }, '라이선스 키 입력'))),
1023
- React.createElement(FormWithSubmit, { onLicenseSubmit: handleLicenseSubmit, disabled: false, isSubmitting: isVerifying, value: licenseKeyDraft, onValueChange: handleLicenseKeyChange, label: { key: 'license.key' }, submitText: { key: 'license.submit' }, submittingText: { key: 'license.verifying' }, helperText: { key: 'license.enter_from_email' }, t: t }))))),
1024
- links.helpUrl && (React.createElement("p", { className: "designbase-figma-page-license__disclaimer" },
1421
+ React.createElement(FormWithSubmit, { onLicenseSubmit: handleLicenseSubmit, disabled: false, isSubmitting: isVerifying, value: licenseKeyDraft, onValueChange: handleLicenseKeyChange, label: { key: 'license.key' }, submitText: { key: 'license.submit' }, submittingText: { key: 'license.verifying' }, helperText: { key: isMembershipMode ? 'license.enter_from_youtube' : 'license.enter_from_email' }, t: t }))))),
1422
+ links.helpUrl && !isMembershipMode && (React.createElement("p", { className: "designbase-figma-page-license__disclaimer" },
1025
1423
  React.createElement("button", { type: "button", className: "designbase-figma-page-license__link", onClick: () => openExternalUrl(links.helpUrl) }, resolveText(t, { key: 'license.help_link' }, '라이선스·결제 도움말'))))),
1026
- React.createElement(Button, { className: "designbase-figma-page-license__close", onPress: onClose, variant: "tertiary", size: "s", "aria-label": "Close" },
1027
- React.createElement(CloseIcon, { size: 20 }))));
1424
+ React.createElement(Button, { className: "designbase-figma-page-license__close", onPress: onClose, variant: "tertiary", size: "s", iconOnly: true, "aria-label": "Close" },
1425
+ React.createElement(CloseIcon, { size: 16 }))));
1028
1426
  };
1029
1427
  PageLicense.displayName = 'PageLicense';
1030
1428
 
1031
- const PaymentStatusSection = ({ status, usageCount, activationLimit, activationUsage, licenseKey, onDeactivate, isDeactivating = false, showDetails = false, className, t }) => {
1032
- const remainingActivations = activationLimit - activationUsage;
1033
- return (React.createElement("div", { className: `designbase-figma-payment-status ${className || ''}` },
1034
- React.createElement("div", { className: "designbase-figma-payment-status__license-key" },
1035
- resolveText(t, { key: 'payment.license_key_label' }, '라이선스 키'),
1036
- ": ",
1037
- React.createElement("span", null, licenseKey)),
1038
- showDetails && (React.createElement("div", { className: "designbase-figma-payment-status__details" },
1039
- React.createElement("div", { className: "designbase-figma-payment-status__activation-info" },
1040
- React.createElement("div", { className: "designbase-figma-payment-status__remaining" },
1041
- remainingActivations,
1042
- " ",
1043
- resolveText(t, { key: 'payment.activations_remaining' }, '활성'),
1044
- " ",
1045
- resolveText(t, { key: 'payment.spots_remaining' }, '자리 남음')),
1046
- React.createElement("div", { className: "designbase-figma-payment-status__usage" },
1047
- activationUsage,
1048
- "/",
1049
- activationLimit)),
1050
- React.createElement(Button, { onClick: onDeactivate, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
1051
- ? resolveText(t, { key: 'payment.deactivating' }, '비활성화중...')
1052
- : resolveText(t, { key: 'payment.deactivate_license' }, '라이선스 비활성화'))))));
1429
+ const PaymentBadge = ({ isActive, onClick, isLoading = false, text, t = (key) => key, className, }) => {
1430
+ const getBadgeText = () => {
1431
+ if (text)
1432
+ return text;
1433
+ return isActive
1434
+ ? (t('proUser') || '프로 계정')
1435
+ : (t('usingFree') || '무료 사용');
1436
+ };
1437
+ const getBadgeVariant = () => {
1438
+ return isActive ? 'success' : 'secondary';
1439
+ };
1440
+ return (React.createElement("div", { className: `designbase-figma-payment-badge ${className || ''} ${isActive ? 'active' : 'free'}`, onClick: onClick, style: { cursor: onClick ? 'pointer' : 'default' } }, isLoading ? (React.createElement(Spinner, { size: "s" })) : (React.createElement(Badge, { variant: getBadgeVariant(), size: "s", className: "designbase-figma-payment-badge__badge" }, getBadgeText()))));
1053
1441
  };
1054
- PaymentStatusSection.displayName = 'PaymentStatusSection';
1442
+ PaymentBadge.displayName = 'PaymentBadge';
1055
1443
 
1056
1444
  const ProgressModal = ({ isOpen, onClose, progress, title = { key: 'progress.working' }, completedMessage = { key: 'progress.completed' }, processingMessage = { key: 'progress.processing' }, stopButtonText = { key: 'progress.stop' }, confirmButtonText = { key: 'progress.confirm' }, helpText, onStop, onComplete, loadingIcon, completeIcon, className, t, autoComplete = true, autoCloseOnComplete = false }) => {
1057
1445
  const [isCompleted, setIsCompleted] = useState(false);
@@ -1095,7 +1483,7 @@ const ProgressModal = ({ isOpen, onClose, progress, title = { key: 'progress.wor
1095
1483
  React.createElement(Spinner, { size: "m" }))))),
1096
1484
  React.createElement("p", { className: "designbase-figma-progress-modal__message" }, displayMessage),
1097
1485
  React.createElement("div", { className: "designbase-figma-progress-modal__status" }, `${processedNodes} / ${totalNodes} (${progressPercentage.toFixed(1)}%)`),
1098
- React.createElement(Progressbar, { value: progressPercentage, size: "m", variant: isCompleted ? 'success' : 'primary', style: "animated", fullWidth: true }),
1486
+ React.createElement(Progressbar, { value: progressPercentage, size: "m", variant: isCompleted ? 'success' : 'primary', barStyle: "animated", fullWidth: true }),
1099
1487
  displayHelpText && !isCompleted && (React.createElement("p", { className: "designbase-figma-progress-modal__help-text" }, displayHelpText)),
1100
1488
  React.createElement("div", { className: "designbase-figma-progress-modal__actions" }, isCompleted ? (React.createElement(Button, { onPress: handleConfirm, variant: "primary", size: "m", fullWidth: true }, displayConfirmText)) : (React.createElement(Button, { onPress: handleStop, variant: "secondary", size: "m", fullWidth: true }, displayStopText))))));
1101
1489
  };
@@ -1302,5 +1690,5 @@ const UpgradeBanner = ({ onClick, variant = 'button', isLoading = false, title =
1302
1690
  };
1303
1691
  UpgradeBanner.displayName = 'UpgradeBanner';
1304
1692
 
1305
- export { DEFAULT_LICENSE_MESSAGE_TYPES, DonationBadge, FigmaContainer, FigmaFooter, FigmaHeader, FigmaPageHead, FigmaSection, FigmaSidebar, FigmaToast, FigmaToastProvider, FormWithSubmit, InteractionFeedback, LanguageSelector, LogoDropdown, PageLicense, PaymentBadge, PaymentStatusSection, PricingComparison, ProgressModal, ResizablePlugin, START_DESIGNBASE_AUTH_MESSAGE, SettingsModal, TitleDescription, UpgradeBanner, getActiveSection, resolveText, scrollToSection, useFigmaLicenseBridge, useFigmaToast, useToast };
1693
+ export { DEFAULT_LICENSE_MESSAGE_TYPES, DonationBadge, FigmaContainer, FigmaFooter, FigmaHeader, FigmaPageHead, FigmaSection, FigmaSidebar, FigmaToast, FigmaToastProvider, FormWithSubmit, InteractionFeedback, LanguageSelector, LanguageSettingsModal, LicenseOffer, LicenseStatusCard, LogoDropdown, PageLicense, PaymentBadge, PaymentStatusSection, PricingComparison, ProgressModal, ResizablePlugin, START_DESIGNBASE_AUTH_MESSAGE, SettingsModal, TitleDescription, UpgradeBanner, UsageStatusPopover, getActiveSection, interpolateText, resolvePaymentFeedbackVariant, resolveText, scrollToSection, useFigmaLicenseBridge, useFigmaToast, useToast };
1306
1694
  //# sourceMappingURL=index.esm.js.map