@designbasekorea/figma-ui 0.6.6 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,6 +6,14 @@ var icons = require('@designbasekorea/icons');
6
6
 
7
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}
8
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
+ }
9
17
  function resolveText(t, input, fallback) {
10
18
  if (input == null)
11
19
  return fallback;
@@ -15,10 +23,13 @@ function resolveText(t, input, fallback) {
15
23
  if (t) {
16
24
  const translated = t(input.key, input.values);
17
25
  if (translated && translated !== input.key) {
18
- return translated;
26
+ return interpolateText(translated, input.values);
19
27
  }
20
28
  }
21
- return fallback ?? input.key;
29
+ if (fallback) {
30
+ return interpolateText(fallback, input.values);
31
+ }
32
+ return input.key;
22
33
  }
23
34
 
24
35
  const DONATION_ICON_NAMES = {
@@ -384,62 +395,70 @@ const FigmaSidebar = ({ categories, categoryGroups = {}, categoryLabels = {}, ac
384
395
  };
385
396
  FigmaSidebar.displayName = 'FigmaSidebar';
386
397
 
387
- const PaymentBadge = ({ isActive, onClick, isLoading = false, text, t = (key) => key, className, }) => {
388
- const getBadgeText = () => {
389
- if (text)
390
- return text;
391
- return isActive
392
- ? (t('proUser') || '프로 계정')
393
- : (t('usingFree') || '무료 사용');
394
- };
395
- const getBadgeVariant = () => {
396
- return isActive ? 'success' : 'secondary';
397
- };
398
- return (React.createElement("div", { className: `designbase-figma-payment-badge ${className || ''} ${isActive ? 'active' : 'free'}`, onClick: onClick, style: { cursor: onClick ? 'pointer' : 'default' } }, isLoading ? (React.createElement(react.Spinner, { size: "s" })) : (React.createElement(react.Badge, { variant: getBadgeVariant(), size: "s", className: "designbase-figma-payment-badge__badge" }, getBadgeText()))));
399
- };
400
- PaymentBadge.displayName = 'PaymentBadge';
401
-
402
- let useTranslation = null;
398
+ let useTranslation$2 = null;
403
399
  try {
404
400
  const reactI18next = require('react-i18next');
405
- useTranslation = reactI18next.useTranslation;
401
+ useTranslation$2 = reactI18next.useTranslation;
406
402
  }
407
403
  catch (e) {
408
404
  }
409
- const DEFAULT_LANGUAGES = [
410
- { code: 'ko', label: 'KO' },
411
- { code: 'en', label: 'EN' },
405
+ const DEFAULT_LANGUAGES$1 = [
406
+ { code: 'ko', label: '한국어' },
407
+ { code: 'en', label: 'English' },
408
+ { code: 'ja', label: '日本語' },
412
409
  ];
413
- const LanguageSelector = ({ currentLanguage, languages = DEFAULT_LANGUAGES, onLanguageChange, size = 's', className, t }) => {
414
- const i18n = useTranslation ? useTranslation().i18n : null;
415
- const activeLanguage = currentLanguage || (i18n?.language) || 'ko';
416
- 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 resolvedCurrentLanguage = currentLanguage || i18n?.language || languages[0]?.code;
423
+ const [pendingLanguage, setPendingLanguage] = React.useState(resolvedCurrentLanguage);
424
+ React.useEffect(() => {
425
+ if (isOpen) {
426
+ setPendingLanguage(resolvedCurrentLanguage);
427
+ }
428
+ }, [isOpen, resolvedCurrentLanguage]);
429
+ const translate = (key, fallback) => {
430
+ const translated = t(key);
431
+ return translated && translated !== key ? translated : fallback;
432
+ };
433
+ const handleSelect = React.useCallback((languageCode) => {
434
+ setPendingLanguage(languageCode);
417
435
  if (i18n) {
418
436
  i18n.changeLanguage(languageCode);
419
437
  }
420
- if (onLanguageChange) {
421
- onLanguageChange(languageCode);
422
- }
423
- };
424
- const segmentOptions = languages.map((language) => ({
425
- value: language.code,
426
- label: resolveText(t, language.label),
427
- disabled: false,
428
- }));
429
- return (React.createElement("div", { className: `designbase-figma-language-selector ${className || ''}` },
430
- React.createElement(react.SegmentControl, { options: segmentOptions, value: activeLanguage, onChange: (event) => {
431
- const detail = event.detail;
432
- const value = typeof detail === 'object' && detail !== null && 'value' in detail &&
433
- typeof detail.value === 'string'
434
- ? detail.value
435
- : undefined;
436
- if (value) {
437
- handleLanguageChange(value);
438
- }
439
- }, size: size, className: "designbase-figma-language-selector__segment" })));
438
+ onLanguageChange?.(languageCode);
439
+ onClose();
440
+ }, [i18n, onClose, onLanguageChange]);
441
+ return (React.createElement(react.Modal, { isOpen: isOpen, onClose: onClose, title: title || translate('languageSettings', '언어 설정'), size: "s", className: clsx('designbase-figma-language-settings-modal', className) },
442
+ React.createElement(react.ModalBody, { className: "designbase-figma-language-settings-modal__content" },
443
+ React.createElement("p", { className: "designbase-figma-language-settings-modal__description" }, description || translate('languageSettingsDescription', '플러그인에 표시할 언어를 선택하세요.')),
444
+ React.createElement("ul", { className: "designbase-figma-language-settings-modal__list", role: "listbox", "aria-label": translate('languageSettings', '언어 설정') }, languages.map((language) => {
445
+ const label = resolveText(t, language.label, language.code.toUpperCase());
446
+ const isActive = language.code === pendingLanguage;
447
+ return (React.createElement("li", { key: language.code, role: "presentation" },
448
+ React.createElement(react.Button, { type: "button", variant: "tertiary", size: "m", fullWidth: true, className: clsx('designbase-figma-language-settings-modal__option', { 'designbase-figma-language-settings-modal__option--active': isActive }), role: "option", "aria-selected": isActive, onPress: () => handleSelect(language.code) },
449
+ React.createElement("span", { className: "designbase-figma-language-settings-modal__option-label" }, label),
450
+ React.createElement("span", { className: "designbase-figma-language-settings-modal__option-indicator", "aria-hidden": "true" }))));
451
+ })))));
440
452
  };
441
- LanguageSelector.displayName = 'LanguageSelector';
453
+ LanguageSettingsModal.displayName = 'LanguageSettingsModal';
442
454
 
455
+ let useTranslation$1 = null;
456
+ try {
457
+ const reactI18next = require('react-i18next');
458
+ useTranslation$1 = reactI18next.useTranslation;
459
+ }
460
+ catch (e) {
461
+ }
443
462
  const defaultLinks = [
444
463
  {
445
464
  name: 'officialWebsite',
@@ -473,50 +492,223 @@ const defaultTranslations$1 = {
473
492
  instagram: '인스타그램',
474
493
  figmaCommunity: '피그마 커뮤니티',
475
494
  contact: '문의하기',
495
+ languageSettings: '언어 설정',
476
496
  };
477
497
  const defaultT$1 = (key) => defaultTranslations$1[key] || key;
478
- const LogoDropdown = ({ logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', links = defaultLinks, position = 'top-left', className, t = defaultT$1, }) => {
498
+ const defaultLanguages = [
499
+ { code: 'ko', label: '한국어' },
500
+ { code: 'en', label: 'English' },
501
+ { code: 'ja', label: '日本語' },
502
+ ];
503
+ const LogoDropdown = ({ logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', links = defaultLinks, position = 'top-left', className, t = defaultT$1, showLanguageSelector = true, languages = defaultLanguages, currentLanguage, onLanguageChange, }) => {
479
504
  const [isOpen, setIsOpen] = React.useState(false);
480
- const toggleDropdown = () => setIsOpen(!isOpen);
505
+ const [isLanguageModalOpen, setIsLanguageModalOpen] = React.useState(false);
506
+ const [internalLanguage, setInternalLanguage] = React.useState();
507
+ const i18n = useTranslation$1 ? useTranslation$1().i18n : null;
508
+ const rootRef = React.useRef(null);
509
+ const triggerRef = React.useRef(null);
510
+ const menuId = `designbase-figma-logo-menu-${React.useId().replace(/:/g, '')}`;
511
+ const activeLanguage = currentLanguage ?? internalLanguage ?? i18n?.language ?? languages[0]?.code;
512
+ const translate = (key, fallback) => {
513
+ const translated = t(key);
514
+ return translated && translated !== key ? translated : fallback;
515
+ };
516
+ React.useEffect(() => {
517
+ if (!isOpen) {
518
+ return undefined;
519
+ }
520
+ const handlePointerDown = (event) => {
521
+ if (!rootRef.current?.contains(event.target)) {
522
+ setIsOpen(false);
523
+ }
524
+ };
525
+ const handleKeyDown = (event) => {
526
+ if (event.key === 'Escape') {
527
+ setIsOpen(false);
528
+ triggerRef.current?.focus();
529
+ }
530
+ };
531
+ document.addEventListener('mousedown', handlePointerDown);
532
+ document.addEventListener('keydown', handleKeyDown);
533
+ return () => {
534
+ document.removeEventListener('mousedown', handlePointerDown);
535
+ document.removeEventListener('keydown', handleKeyDown);
536
+ };
537
+ }, [isOpen]);
538
+ const toggleDropdown = () => setIsOpen((value) => !value);
481
539
  const handleLinkClick = (url) => {
540
+ setIsOpen(false);
482
541
  if (url.startsWith('mailto:')) {
483
542
  return;
484
543
  }
485
544
  window.open(url, '_blank', 'noopener,noreferrer');
486
545
  };
546
+ const handleLanguageSettingsClick = () => {
547
+ setIsOpen(false);
548
+ setIsLanguageModalOpen(true);
549
+ };
550
+ const handleLanguageChange = (languageCode) => {
551
+ if (currentLanguage === undefined) {
552
+ setInternalLanguage(languageCode);
553
+ }
554
+ if (i18n) {
555
+ i18n.changeLanguage(languageCode);
556
+ }
557
+ onLanguageChange?.(languageCode);
558
+ };
487
559
  const classes = clsx('designbase-figma-logo-dropdown', `designbase-figma-logo-dropdown--${position}`, {
488
560
  'designbase-figma-logo-dropdown--open': isOpen,
489
561
  }, className);
490
562
  const renderLogo = () => {
491
- if (logoSrc) {
492
- return (React.createElement("img", { className: "designbase-figma-logo-dropdown__logo", src: logoSrc, alt: logoAlt, onClick: toggleDropdown }));
493
- }
494
- return (React.createElement("div", { className: "designbase-figma-logo-dropdown__logo", onClick: toggleDropdown },
495
- React.createElement(react.Logo, { type: logoType, size: logoSize, variant: "default" })));
563
+ 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(react.Logo, { type: logoType, size: logoSize, variant: "default" }))));
496
564
  };
497
- return (React.createElement("div", { className: classes },
565
+ return (React.createElement("div", { ref: rootRef, className: classes },
498
566
  renderLogo(),
499
- 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" },
500
- React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", onClick: () => handleLinkClick(link.url) },
501
- link.icon && (React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" }, link.icon)),
502
- React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, t(link.name)),
503
- React.createElement(icons.ExternalLinkIcon, { size: 16, className: "designbase-figma-logo-dropdown__arrow" })))))))));
567
+ isOpen && (React.createElement("ul", { id: menuId, className: "designbase-figma-logo-dropdown__menu", role: "menu" },
568
+ links.map((link, index) => (React.createElement("li", { key: index, className: "designbase-figma-logo-dropdown__item", role: "none" },
569
+ React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", role: "menuitem", onClick: () => handleLinkClick(link.url) },
570
+ link.icon && (React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" }, link.icon)),
571
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, t(link.name)),
572
+ React.createElement(icons.ExternalLinkIcon, { size: 16, className: "designbase-figma-logo-dropdown__arrow" }))))),
573
+ showLanguageSelector && (React.createElement("li", { className: "designbase-figma-logo-dropdown__item designbase-figma-logo-dropdown__language-item", role: "none" },
574
+ React.createElement("button", { type: "button", className: "designbase-figma-logo-dropdown__link", role: "menuitem", onClick: handleLanguageSettingsClick },
575
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__icon" },
576
+ React.createElement(icons.TranslateIcon, { size: 20, "aria-hidden": "true" })),
577
+ React.createElement("span", { className: "designbase-figma-logo-dropdown__text" }, translate('languageSettings', '언어 설정'))))))),
578
+ showLanguageSelector && (React.createElement(LanguageSettingsModal, { isOpen: isLanguageModalOpen, onClose: () => setIsLanguageModalOpen(false), languages: languages, currentLanguage: activeLanguage, onLanguageChange: handleLanguageChange, t: t }))));
504
579
  };
505
580
  LogoDropdown.displayName = 'LogoDropdown';
506
581
 
582
+ const fallbackText = (t, key, fallback) => {
583
+ const translated = t?.(key);
584
+ return translated && translated !== key ? translated : fallback;
585
+ };
586
+ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, maxDailyUsage = 20, showUsage = true, showPlan = true, isLoading = false, onLicensePageClick, t, className, }) => {
587
+ const [isOpen, setIsOpen] = React.useState(false);
588
+ const rootRef = React.useRef(null);
589
+ const triggerRef = React.useRef(null);
590
+ const popoverId = `designbase-figma-usage-popover-${React.useId().replace(/:/g, '')}`;
591
+ const isPaid = paymentStatus === 'PAID';
592
+ const isBeta = paymentStatus === 'BETA';
593
+ const hasDailyLimit = !isPaid && maxDailyUsage > 0;
594
+ const limit = Math.max(1, Math.round(maxDailyUsage));
595
+ const used = Math.min(limit, Math.max(0, Math.round(usageCount)));
596
+ const remaining = Math.max(0, limit - used);
597
+ const usedPercent = hasDailyLimit ? Math.round((used / limit) * 100) : 0;
598
+ const hasControl = showUsage || showPlan;
599
+ const planLabel = isPaid
600
+ ? fallbackText(t, 'proUser', '프로')
601
+ : isBeta
602
+ ? fallbackText(t, 'betaStatus', 'BETA')
603
+ : paymentStatus === 'TRIAL'
604
+ ? fallbackText(t, 'trialPlan', '체험판')
605
+ : fallbackText(t, 'free', '무료');
606
+ const planTitle = isBeta
607
+ ? planLabel
608
+ : `${planLabel} ${fallbackText(t, 'planSuffix', '플랜')}`;
609
+ const triggerLabel = isPaid
610
+ ? `${planLabel}, ${fallbackText(t, 'unlimitedUsage', '무제한 사용 가능')}`
611
+ : isBeta
612
+ ? planLabel
613
+ : hasDailyLimit
614
+ ? `${remaining}/${limit} ${fallbackText(t, 'remainingShort', '남음')}`
615
+ : planLabel;
616
+ const upgradeLabel = isPaid
617
+ ? fallbackText(t, 'managePlan', '플랜 관리')
618
+ : fallbackText(t, 'upgradePlan', '플랜 업그레이드');
619
+ React.useEffect(() => {
620
+ if (!isOpen)
621
+ return undefined;
622
+ const handlePointerDown = (event) => {
623
+ if (!rootRef.current?.contains(event.target)) {
624
+ setIsOpen(false);
625
+ }
626
+ };
627
+ const handleKeyDown = (event) => {
628
+ if (event.key === 'Escape') {
629
+ setIsOpen(false);
630
+ triggerRef.current?.focus();
631
+ }
632
+ };
633
+ document.addEventListener('mousedown', handlePointerDown);
634
+ document.addEventListener('keydown', handleKeyDown);
635
+ return () => {
636
+ document.removeEventListener('mousedown', handlePointerDown);
637
+ document.removeEventListener('keydown', handleKeyDown);
638
+ };
639
+ }, [isOpen]);
640
+ if (!hasControl)
641
+ return null;
642
+ const handleManageClick = () => {
643
+ setIsOpen(false);
644
+ onLicensePageClick?.();
645
+ };
646
+ const togglePopover = () => setIsOpen((value) => !value);
647
+ return (React.createElement("div", { ref: rootRef, className: clsx('designbase-figma-usage-status', {
648
+ 'designbase-figma-usage-status--open': isOpen,
649
+ 'designbase-figma-usage-status--unlimited': isPaid,
650
+ 'designbase-figma-usage-status--loading': isLoading,
651
+ }, className) },
652
+ React.createElement("div", { className: "designbase-figma-usage-status__control" },
653
+ 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,
654
+ React.createElement("span", { className: "designbase-figma-usage-status__mini-progress", "aria-hidden": "true" },
655
+ React.createElement("span", { style: { width: `${usedPercent}%` } })),
656
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-value" },
657
+ React.createElement("strong", null, remaining),
658
+ React.createElement("span", null,
659
+ "/",
660
+ limit)))) : (React.createElement("span", { className: "designbase-figma-usage-status__plan-label" }, planLabel)))),
661
+ 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))),
662
+ isOpen && (React.createElement("div", { id: popoverId, className: "designbase-figma-usage-status__popover", role: "dialog", "aria-label": fallbackText(t, 'usageDetails', '사용량 상세') },
663
+ React.createElement("div", { className: "designbase-figma-usage-status__popover-header" },
664
+ React.createElement("span", { className: "designbase-figma-usage-status__plan-title" }, planTitle),
665
+ !isBeta && (React.createElement(react.Button, { className: "designbase-figma-usage-status__upgrade", variant: isPaid ? 'secondary' : 'primary', size: "s", endIcon: "chevron-right", onPress: handleManageClick }, upgradeLabel))),
666
+ isPaid ? (React.createElement("div", { className: "designbase-figma-usage-status__unlimited-copy" },
667
+ React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E"),
668
+ 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" },
669
+ React.createElement("div", { className: "designbase-figma-usage-status__usage-summary" },
670
+ React.createElement("strong", { className: "designbase-figma-usage-status__usage-count" },
671
+ used,
672
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-divider" }, "/"),
673
+ limit),
674
+ React.createElement("span", { className: "designbase-figma-usage-status__usage-caption" }, fallbackText(t, 'usageCountCaption', '오늘 사용'))),
675
+ 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}` },
676
+ React.createElement("span", { style: { width: `${usedPercent}%` } })),
677
+ 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', '사용량 제한이 없습니다')))))));
678
+ };
679
+ UsageStatusPopover.displayName = 'UsageStatusPopover';
680
+
507
681
  const defaultTranslations = {
508
682
  unlimitedUsage: '무제한 사용',
683
+ proUser: '프로',
684
+ free: '무료',
685
+ trialPlan: '체험판',
686
+ planSuffix: '플랜',
687
+ usageDetails: '사용량 상세',
688
+ dailyUsage: '오늘 사용량',
689
+ usedToday: '오늘 사용',
690
+ remainingToday: '오늘 남은 사용량',
691
+ remainingUsage: '오늘 남은 사용량',
692
+ managePlan: '플랜 관리',
693
+ upgradePlan: '플랜 업그레이드',
694
+ remainingShort: '남음',
695
+ usageCountCaption: '오늘 사용',
696
+ remainingCopy: '{{count}}회 남음 · 매일 리셋',
697
+ usageUnavailable: '사용량 제한이 없습니다',
698
+ betaUsageDescription: '베타 기간에는 사용량 제한이 적용되지 않습니다',
509
699
  perDay: '일',
510
700
  resetsDaily: '매일 리셋',
511
701
  donationPrompt: '이 플러그인이 유용했나요?',
512
702
  betaStatus: 'BETA',
513
703
  };
514
- const defaultT = (key) => defaultTranslations[key] || key;
515
- 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, }) => {
516
- const isActive = paymentStatus === 'PAID';
517
- const isBeta = paymentStatus === 'BETA';
518
- const resolvedShowUsageInfo = showUsageInfo && !isLoading && !isBeta;
704
+ const defaultT = (key, values) => {
705
+ const template = defaultTranslations[key] || key;
706
+ return interpolateText(template, values);
707
+ };
708
+ 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, }) => {
709
+ const resolvedShowUsageInfo = showPaymentStatus && showUsageInfo;
519
710
  const resolvedShowPaymentBadge = showPaymentStatus && showPaymentBadge;
711
+ const resolvedLicensePageClick = paymentStatus === 'BETA' ? undefined : onLicensePageClick;
520
712
  const hasChildren = React.Children.count(children) > 0;
521
713
  const classes = clsx('designbase-figma-footer', {
522
714
  'designbase-figma-footer--has-children': hasChildren,
@@ -524,17 +716,9 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
524
716
  return (React.createElement("footer", { className: classes },
525
717
  React.createElement("div", { className: "designbase-figma-footer__wrap" },
526
718
  React.createElement("div", { className: "designbase-figma-footer__left" },
527
- React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, t: t }),
528
- showLanguageSelector && (React.createElement(LanguageSelector, { currentLanguage: currentLanguage, languages: languages, onLanguageChange: onLanguageChange, size: "s" }))),
719
+ React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, languages: languages, currentLanguage: currentLanguage, showLanguageSelector: showLanguageSelector, onLanguageChange: onLanguageChange, t: t })),
529
720
  React.createElement("div", { className: "designbase-figma-footer__right" },
530
- 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') },
531
- React.createElement("span", { className: "designbase-figma-footer__usage-count" }, usageCount),
532
- React.createElement("span", { className: "designbase-figma-footer__max-daily-usage" },
533
- "/",
534
- maxDailyUsage,
535
- " ",
536
- t('perDay')))))),
537
- showPaymentStatus && resolvedShowPaymentBadge && (React.createElement(PaymentBadge, { isActive: isActive, onClick: isBeta ? undefined : onLicensePageClick, isLoading: isLoading, text: isBeta ? (t('betaStatus') || 'BETA') : undefined, t: t })),
721
+ (resolvedShowUsageInfo || resolvedShowPaymentBadge) && (React.createElement(UsageStatusPopover, { paymentStatus: paymentStatus, usageCount: usageCount, maxDailyUsage: maxDailyUsage, showUsage: resolvedShowUsageInfo, showPlan: resolvedShowPaymentBadge, isLoading: isLoading, onLicensePageClick: resolvedLicensePageClick, t: t })),
538
722
  showDonation && (React.createElement(DonationBadge, { donationUrl: donationUrl, text: donationText, iconType: "heart", size: "s", iconOnly: true, showPrompt: false, t: t })))),
539
723
  children));
540
724
  };
@@ -658,6 +842,47 @@ const InteractionFeedback = ({ status = 'default', message, statusMessage, leftC
658
842
  };
659
843
  InteractionFeedback.displayName = 'InteractionFeedback';
660
844
 
845
+ let useTranslation = null;
846
+ try {
847
+ const reactI18next = require('react-i18next');
848
+ useTranslation = reactI18next.useTranslation;
849
+ }
850
+ catch (e) {
851
+ }
852
+ const DEFAULT_LANGUAGES = [
853
+ { code: 'ko', label: 'KO' },
854
+ { code: 'en', label: 'EN' },
855
+ ];
856
+ const LanguageSelector = ({ currentLanguage, languages = DEFAULT_LANGUAGES, onLanguageChange, size = 's', className, t }) => {
857
+ const i18n = useTranslation ? useTranslation().i18n : null;
858
+ const activeLanguage = currentLanguage || (i18n?.language) || 'ko';
859
+ const handleLanguageChange = (languageCode) => {
860
+ if (i18n) {
861
+ i18n.changeLanguage(languageCode);
862
+ }
863
+ if (onLanguageChange) {
864
+ onLanguageChange(languageCode);
865
+ }
866
+ };
867
+ const segmentOptions = languages.map((language) => ({
868
+ value: language.code,
869
+ label: resolveText(t, language.label),
870
+ disabled: false,
871
+ }));
872
+ return (React.createElement("div", { className: `designbase-figma-language-selector ${className || ''}` },
873
+ React.createElement(react.SegmentControl, { options: segmentOptions, value: activeLanguage, onChange: (event) => {
874
+ const detail = event.detail;
875
+ const value = typeof detail === 'object' && detail !== null && 'value' in detail &&
876
+ typeof detail.value === 'string'
877
+ ? detail.value
878
+ : undefined;
879
+ if (value) {
880
+ handleLanguageChange(value);
881
+ }
882
+ }, size: size, className: "designbase-figma-language-selector__segment" })));
883
+ };
884
+ LanguageSelector.displayName = 'LanguageSelector';
885
+
661
886
  function postPluginMessage(message) {
662
887
  if (typeof parent === 'undefined') {
663
888
  return;
@@ -673,17 +898,27 @@ function openExternalUrl(url) {
673
898
  }
674
899
  }
675
900
 
676
- const LicenseOffer = ({ membershipUrl, t, className }) => (React.createElement("section", { className: `designbase-figma-license-offer ${className || ''}`, "aria-labelledby": "designbase-license-offer-title" },
677
- React.createElement("div", { className: "designbase-figma-license-offer__eyebrow" }, "YouTube Membership"),
678
- React.createElement("h3", { id: "designbase-license-offer-title" }, resolveText(t, { key: 'license.youtube_membership_title' }, 'YouTube 멤버십으로 전체 플러그인 이용')),
679
- React.createElement("p", null, resolveText(t, { key: 'license.youtube_membership_description' }, 'YouTube 멤버십에 가입하면 Designbase 플러그인을 무제한으로 사용할 수 있습니다.')),
680
- React.createElement("ol", null,
681
- React.createElement("li", null, "Designbase YouTube \uCC44\uB110\uC758 \uBA64\uBC84\uC2ED\uC5D0 \uAC00\uC785\uD569\uB2C8\uB2E4."),
682
- React.createElement("li", null, "\uBA64\uBC84 \uC804\uC6A9 \uCEE4\uBBA4\uB2C8\uD2F0 \uAC8C\uC2DC\uAE00\uC5D0\uC11C \uC774\uBC88 \uB2EC \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD569\uB2C8\uB2E4."),
683
- React.createElement("li", null, "\uC544\uB798 \uC785\uB825\uCC3D\uC5D0 \uD0A4\uB97C \uBD99\uC5EC \uB123\uACE0 \uD65C\uC131\uD654\uD569\uB2C8\uB2E4.")),
684
- membershipUrl && (React.createElement(react.Button, { onPress: () => openExternalUrl(membershipUrl), variant: "secondary", size: "m", fullWidth: true },
685
- React.createElement("span", null, resolveText(t, { key: 'license.youtube_membership_cta' }, '멤버십 안내 보기')),
686
- React.createElement(icons.ExternalLinkIcon, { size: 16 })))));
901
+ const LicenseOffer = ({ joinUrl, guideUrl, membershipUrl, t, className, }) => {
902
+ const titleId = `designbase-license-offer-title-${React.useId().replace(/:/g, '')}`;
903
+ const resolvedJoinUrl = joinUrl || membershipUrl;
904
+ const steps = [
905
+ resolveText(t, { key: 'license.youtube_membership_step_1' }, 'Designbase YouTube 채널 멤버십에 가입합니다.'),
906
+ resolveText(t, { key: 'license.youtube_membership_step_2' }, '멤버 전용 커뮤니티에서 이번 달 키를 복사합니다.'),
907
+ resolveText(t, { key: 'license.youtube_membership_step_3' }, '아래 입력창에 키를 붙여 넣고 활성화합니다.'),
908
+ ];
909
+ return (React.createElement("section", { className: `designbase-figma-license-offer ${className || ''}`, "aria-labelledby": titleId },
910
+ React.createElement("h3", { id: titleId, className: "designbase-figma-license-offer__title" }, resolveText(t, { key: 'license.youtube_membership_howto' }, '이용 방법')),
911
+ React.createElement("ol", { className: "designbase-figma-license-offer__steps" }, steps.map((step, index) => (React.createElement("li", { key: index },
912
+ React.createElement("span", { className: "designbase-figma-license-offer__step-index", "aria-hidden": "true" },
913
+ index + 1,
914
+ "."),
915
+ React.createElement("span", null, step))))),
916
+ (resolvedJoinUrl || guideUrl) && (React.createElement("div", { className: "designbase-figma-license-offer__actions" },
917
+ resolvedJoinUrl && (React.createElement(react.Button, { onPress: () => openExternalUrl(resolvedJoinUrl), variant: "primary", size: "m", fullWidth: true }, resolveText(t, { key: 'license.youtube_membership_join' }, '멤버십 가입'))),
918
+ guideUrl && (React.createElement(react.Button, { onPress: () => openExternalUrl(guideUrl), variant: "secondary", size: "m", fullWidth: true },
919
+ React.createElement("span", null, resolveText(t, { key: 'license.youtube_membership_guide' }, '안내보기')),
920
+ React.createElement(icons.ExternalLinkIcon, { size: 16, "aria-hidden": "true" })))))));
921
+ };
687
922
  LicenseOffer.displayName = 'LicenseOffer';
688
923
 
689
924
  function maskLicenseKey(value) {
@@ -699,16 +934,30 @@ function formatDate(value) {
699
934
  return value;
700
935
  return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date);
701
936
  }
702
- function formatScope(value) {
937
+ function formatScope(value, t) {
938
+ if (!value)
939
+ return undefined;
940
+ if (value === 'all-products' || value === 'all_products') {
941
+ return resolveText(t, { key: 'license.all_plugins' }, '모든 플러그인');
942
+ }
943
+ return value;
944
+ }
945
+ function formatSource(value, t) {
703
946
  if (!value)
704
947
  return undefined;
705
- if (value === 'all-products' || value === 'all_products')
706
- return '전체 제품';
948
+ if (value === 'youtube_membership' || value === 'membership') {
949
+ return resolveText(t, { key: 'license.source_youtube_membership' }, 'YouTube 멤버십');
950
+ }
951
+ if (value === 'lemonsqueezy') {
952
+ return resolveText(t, { key: 'license.source_purchase' }, '구매 라이선스');
953
+ }
707
954
  return value;
708
955
  }
709
- const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSource, accessScope, expiresAt, lastVerifiedAt, validationMessage, allowRevealKey = true, t, className, }) => {
956
+ const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSource, accessScope, expiresAt, lastVerifiedAt, validationMessage, allowRevealKey = false, t, className, }) => {
710
957
  const [isKeyRevealed, setIsKeyRevealed] = React.useState(false);
711
- const isActive = status === 'PAID';
958
+ const normalizedLicenseStatus = licenseStatus?.toLowerCase();
959
+ const isInactiveStatus = ['expired', 'revoked', 'invalid', 'inactive'].includes(normalizedLicenseStatus || '');
960
+ const isActive = status === 'PAID' && !isInactiveStatus;
712
961
  const statusText = isActive
713
962
  ? resolveText(t, { key: 'license.status_active' }, 'Active')
714
963
  : resolveText(t, { key: 'license.status_inactive' }, 'License check failed');
@@ -716,30 +965,82 @@ const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSour
716
965
  return (React.createElement("section", { className: `designbase-figma-license-status-card designbase-figma-license-status-card--${statusClass} ${className || ''}`, "aria-label": statusText },
717
966
  React.createElement("div", { className: "designbase-figma-license-status-card__header" },
718
967
  React.createElement("div", null,
719
- React.createElement("div", { className: "designbase-figma-license-status-card__eyebrow" }, isActive ? 'License Activated' : 'License Status'),
968
+ React.createElement("div", { className: "designbase-figma-license-status-card__eyebrow" }, isActive
969
+ ? resolveText(t, { key: 'license.activated' }, '라이선스 활성화됨')
970
+ : resolveText(t, { key: 'license.status_label' }, '라이선스 상태')),
720
971
  React.createElement("h3", null, statusText)),
721
972
  React.createElement("span", { className: "designbase-figma-license-status-card__badge" }, licenseStatus || (isActive ? 'active' : 'inactive'))),
722
973
  validationMessage && !isActive && (React.createElement("p", { className: "designbase-figma-license-status-card__message" }, validationMessage)),
723
974
  licenseKey && (React.createElement("div", { className: "designbase-figma-license-status-card__key-row" },
724
975
  React.createElement("span", null, resolveText(t, { key: 'license.key_label' }, '라이선스 키')),
725
976
  React.createElement("code", null, allowRevealKey && isKeyRevealed ? licenseKey : maskLicenseKey(licenseKey)),
726
- allowRevealKey && (React.createElement(react.Button, { type: "button", onPress: () => setIsKeyRevealed((value) => !value), variant: "tertiary", size: "s" }, isKeyRevealed ? '숨기기' : '보기')))),
977
+ allowRevealKey && (React.createElement(react.Button, { type: "button", onPress: () => setIsKeyRevealed((value) => !value), variant: "tertiary", size: "s" }, isKeyRevealed
978
+ ? resolveText(t, { key: 'license.hide_key' }, '숨기기')
979
+ : resolveText(t, { key: 'license.reveal_key' }, '보기'))))),
727
980
  (licenseSource || accessScope || expiresAt || lastVerifiedAt) && (React.createElement("dl", { className: "designbase-figma-license-status-card__meta" },
728
- licenseSource && React.createElement(React.Fragment, null,
729
- React.createElement("dt", null, "Source"),
730
- React.createElement("dd", null, licenseSource)),
731
- formatScope(accessScope) && React.createElement(React.Fragment, null,
732
- React.createElement("dt", null, "Access"),
733
- React.createElement("dd", null, formatScope(accessScope))),
981
+ formatSource(licenseSource, t) && React.createElement(React.Fragment, null,
982
+ React.createElement("dt", null, resolveText(t, { key: 'license.source' }, '출처')),
983
+ React.createElement("dd", null, formatSource(licenseSource, t))),
984
+ formatScope(accessScope, t) && React.createElement(React.Fragment, null,
985
+ React.createElement("dt", null, resolveText(t, { key: 'license.access' }, '접근 범위')),
986
+ React.createElement("dd", null, formatScope(accessScope, t))),
734
987
  expiresAt && React.createElement(React.Fragment, null,
735
- React.createElement("dt", null, "Expires"),
988
+ React.createElement("dt", null, resolveText(t, { key: 'license.expires' }, '만료일')),
736
989
  React.createElement("dd", null, formatDate(expiresAt) || expiresAt)),
737
990
  lastVerifiedAt && React.createElement(React.Fragment, null,
738
- React.createElement("dt", null, "Last checked"),
991
+ React.createElement("dt", null, resolveText(t, { key: 'license.last_checked' }, '최근 확인')),
739
992
  React.createElement("dd", null, formatDate(lastVerifiedAt) || lastVerifiedAt))))));
740
993
  };
741
994
  LicenseStatusCard.displayName = 'LicenseStatusCard';
742
995
 
996
+ function resolvePaymentFeedbackVariant(errorCode, explicitVariant) {
997
+ if (explicitVariant)
998
+ return explicitVariant;
999
+ if (!errorCode)
1000
+ return 'success';
1001
+ const normalized = errorCode.toLowerCase();
1002
+ if (normalized.includes('network')
1003
+ || normalized.includes('server')
1004
+ || normalized.includes('rate')
1005
+ || normalized.includes('timeout')) {
1006
+ return 'warning';
1007
+ }
1008
+ if (normalized.includes('info')) {
1009
+ return 'info';
1010
+ }
1011
+ return 'error';
1012
+ }
1013
+ const PaymentStatusSection = ({ activationLimit, activationUsage, licenseKey, onDeactivate, isDeactivating = false, showDetails = false, actionMessage, actionMessageVariant, errorCode, className, t, }) => {
1014
+ const remainingActivations = Math.max(0, activationLimit - activationUsage);
1015
+ const resolvedActionMessage = actionMessage ? resolveText(t, actionMessage) : undefined;
1016
+ const feedbackVariant = resolvedActionMessage
1017
+ ? resolvePaymentFeedbackVariant(errorCode, actionMessageVariant)
1018
+ : undefined;
1019
+ return (React.createElement("div", { className: clsx('designbase-figma-payment-status', className) },
1020
+ React.createElement("div", { className: "designbase-figma-payment-status__license-key" },
1021
+ resolveText(t, { key: 'payment.license_key_label' }, '라이선스 키'),
1022
+ ": ",
1023
+ React.createElement("span", null, licenseKey)),
1024
+ showDetails && (React.createElement("div", { className: "designbase-figma-payment-status__details" },
1025
+ React.createElement("div", { className: "designbase-figma-payment-status__activation-info" },
1026
+ React.createElement("div", { className: "designbase-figma-payment-status__remaining" },
1027
+ remainingActivations,
1028
+ ' ',
1029
+ resolveText(t, { key: 'payment.activations_remaining' }, '활성'),
1030
+ ' ',
1031
+ resolveText(t, { key: 'payment.spots_remaining' }, '자리 남음')),
1032
+ React.createElement("div", { className: "designbase-figma-payment-status__usage" },
1033
+ activationUsage,
1034
+ "/",
1035
+ activationLimit)),
1036
+ React.createElement("div", { className: "designbase-figma-payment-status__action" },
1037
+ React.createElement(react.Button, { onPress: onDeactivate, variant: "secondary", size: "s", fullWidth: true, disabled: isDeactivating }, isDeactivating
1038
+ ? resolveText(t, { key: 'payment.deactivating' }, '비활성화중...')
1039
+ : resolveText(t, { key: 'payment.deactivate_license' }, '라이선스 비활성화')),
1040
+ 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)))))));
1041
+ };
1042
+ PaymentStatusSection.displayName = 'PaymentStatusSection';
1043
+
743
1044
  const defaultFeatures = [
744
1045
  { name: '사용 제한', free: '일일 제한', pro: '무제한' },
745
1046
  { name: '모든 주제 사용', free: false, pro: true },
@@ -934,15 +1235,27 @@ function useFigmaLicenseBridge(options = {}) {
934
1235
  if (type === messageTypes.verifyResult) {
935
1236
  setIsVerifying(false);
936
1237
  setIsStartingAuth(false);
937
- applyPatch(parseLicenseMessage(payload));
1238
+ applyPatch({
1239
+ ...parseLicenseMessage(payload),
1240
+ deactivateMessage: '',
1241
+ deactivateErrorCode: '',
1242
+ });
938
1243
  if (payload.success === true) {
939
1244
  applyPatch({ paymentStatus: 'PAID', isLicenseRegistered: true });
940
1245
  }
941
1246
  }
942
1247
  if (type === messageTypes.deactivateResult) {
943
1248
  setIsDeactivating(false);
944
- applyPatch(parseLicenseMessage(payload));
945
- if (payload.success === true) {
1249
+ const parsed = parseLicenseMessage(payload);
1250
+ const isSuccess = payload.success === true;
1251
+ applyPatch({
1252
+ ...parsed,
1253
+ deactivateMessage: parsed.lastMessage,
1254
+ deactivateErrorCode: isSuccess ? undefined : parsed.lastErrorCode,
1255
+ lastMessage: isSuccess ? undefined : parsed.lastMessage,
1256
+ lastErrorCode: isSuccess ? undefined : parsed.lastErrorCode,
1257
+ });
1258
+ if (isSuccess) {
946
1259
  applyPatch({
947
1260
  paymentStatus: 'UNPAID',
948
1261
  licenseKey: '',
@@ -950,6 +1263,8 @@ function useFigmaLicenseBridge(options = {}) {
950
1263
  isLicenseRegistered: false,
951
1264
  activationLimit: 0,
952
1265
  activationUsage: 0,
1266
+ deactivateMessage: parsed.lastMessage || '라이선스가 비활성화되었습니다.',
1267
+ deactivateErrorCode: undefined,
953
1268
  });
954
1269
  }
955
1270
  }
@@ -976,8 +1291,12 @@ function useFigmaLicenseBridge(options = {}) {
976
1291
  }, [messageTypes.verifyLicense]);
977
1292
  const deactivateLicense = React.useCallback(() => {
978
1293
  setIsDeactivating(true);
1294
+ applyPatch({
1295
+ deactivateMessage: '',
1296
+ deactivateErrorCode: '',
1297
+ });
979
1298
  postPluginMessage({ type: messageTypes.deactivateLicense });
980
- }, [messageTypes.deactivateLicense]);
1299
+ }, [applyPatch, messageTypes.deactivateLicense]);
981
1300
  const startDesignbaseAuth = React.useCallback(() => {
982
1301
  setIsStartingAuth(true);
983
1302
  postPluginMessage({ type: START_DESIGNBASE_AUTH_MESSAGE });
@@ -1047,7 +1366,7 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1047
1366
  setLicenseKeyDraft(value);
1048
1367
  setLicenseKeyInput(value);
1049
1368
  };
1050
- return (React.createElement("div", { className: `designbase-figma-page-license ${className || ''}` },
1369
+ return (React.createElement("div", { className: `designbase-figma-page-license designbase-figma-page-license--${mode} ${className || ''}`, "data-license-mode": mode },
1051
1370
  React.createElement("div", { className: "designbase-figma-page-license__content" },
1052
1371
  React.createElement("div", { className: "designbase-figma-page-license__header" },
1053
1372
  React.createElement("div", { className: "designbase-figma-page-license__title" },
@@ -1062,15 +1381,15 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1062
1381
  : isMembershipMode
1063
1382
  ? resolveText(t, { key: 'license.youtube_membership_description' }, 'YouTube 멤버십에 가입하면 Designbase 플러그인을 무제한으로 사용할 수 있습니다.')
1064
1383
  : resolveText(t, { key: 'license.purchase_for_unlimited' }, '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.')),
1065
- !isPaid && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
1384
+ !isPaid && !isMembershipMode && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
1066
1385
  !isMembershipMode && (React.createElement(PricingComparison, { features: pricingFeatures, pricing: pricing, paymentType: paymentType, t: t })),
1067
- state.lastMessage && (React.createElement("p", { className: `designbase-figma-page-license__feedback ${state.lastErrorCode ? 'designbase-figma-page-license__feedback--error' : ''}` }, state.lastMessage)),
1386
+ 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)),
1068
1387
  isPaid && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1069
1388
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1070
1389
  React.createElement("h3", null, resolveText(t, { key: 'license.activated' }, '라이선스 활성화됨')),
1071
1390
  React.createElement(react.Button, { onPress: () => setShowDetails(!showDetails), variant: "tertiary", size: "s", iconOnly: true },
1072
1391
  React.createElement(icons.MoreHorizontalIcon, { size: 16 }))),
1073
- 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, t: t }),
1392
+ 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 }),
1074
1393
  React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel),
1075
1394
  showDetails && (React.createElement("div", { className: "designbase-figma-page-license__details" },
1076
1395
  React.createElement("div", { className: "designbase-figma-page-license__activation-info" },
@@ -1084,11 +1403,13 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1084
1403
  state.activationUsage,
1085
1404
  "/",
1086
1405
  state.activationLimit)),
1087
- React.createElement(react.Button, { onPress: deactivateLicense, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
1088
- ? resolveText(t, { key: 'license.deactivating' }, '비활성화 중...')
1089
- : resolveText(t, { key: 'license.deactivate' }, '라이선스 비활성화')))))),
1406
+ React.createElement("div", { className: "designbase-figma-page-license__action" },
1407
+ React.createElement(react.Button, { onPress: deactivateLicense, variant: "secondary", size: "s", fullWidth: true, disabled: isDeactivating }, isDeactivating
1408
+ ? resolveText(t, { key: 'license.deactivating' }, '비활성화 중...')
1409
+ : resolveText(t, { key: 'license.deactivate' }, '라이선스 비활성화')),
1410
+ 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))))))),
1090
1411
  !isPaid && (React.createElement(React.Fragment, null,
1091
- isMembershipMode && (React.createElement(LicenseOffer, { membershipUrl: links.membershipUrl || links.purchaseUrl, t: t })),
1412
+ isMembershipMode && (React.createElement(LicenseOffer, { joinUrl: links.membershipUrl || links.purchaseUrl, guideUrl: links.helpUrl || links.purchaseUrl, t: t })),
1092
1413
  !isMembershipMode && (enableAccountAuth || links.purchaseUrl || links.accountUrl) && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1093
1414
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1094
1415
  React.createElement("h3", null, resolveText(t, { key: 'license.designbase_account' }, '디자인베이스 계정'))),
@@ -1103,41 +1424,33 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1103
1424
  links.accountUrl && (React.createElement(react.Button, { onPress: () => openExternalUrl(links.accountUrl), variant: "tertiary", size: "s", fullWidth: true },
1104
1425
  React.createElement("span", null, resolveText(t, { key: 'license.manage_account' }, '계정·멤버십 관리')),
1105
1426
  React.createElement(icons.ExternalLinkIcon, { size: 16 })))))),
1106
- showLicenseKeyInput && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1427
+ showLicenseKeyInput && (React.createElement("div", { className: isMembershipMode
1428
+ ? 'designbase-figma-page-license__key-form'
1429
+ : 'designbase-figma-page-license__section' },
1107
1430
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1108
1431
  React.createElement("h3", null, resolveText(t, { key: 'license.enter_key' }, '라이선스 키 입력'))),
1109
- 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 }))))),
1110
- links.helpUrl && (React.createElement("p", { className: "designbase-figma-page-license__disclaimer" },
1432
+ 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 }))))),
1433
+ links.helpUrl && !isMembershipMode && (React.createElement("p", { className: "designbase-figma-page-license__disclaimer" },
1111
1434
  React.createElement("button", { type: "button", className: "designbase-figma-page-license__link", onClick: () => openExternalUrl(links.helpUrl) }, resolveText(t, { key: 'license.help_link' }, '라이선스·결제 도움말'))))),
1112
- React.createElement(react.Button, { className: "designbase-figma-page-license__close", onPress: onClose, variant: "tertiary", size: "s", "aria-label": "Close" },
1113
- React.createElement(icons.CloseIcon, { size: 20 }))));
1435
+ React.createElement(react.Button, { className: "designbase-figma-page-license__close", onPress: onClose, variant: "tertiary", size: "s", iconOnly: true, "aria-label": "Close" },
1436
+ React.createElement(icons.CloseIcon, { size: 16 }))));
1114
1437
  };
1115
1438
  PageLicense.displayName = 'PageLicense';
1116
1439
 
1117
- const PaymentStatusSection = ({ status, usageCount, activationLimit, activationUsage, licenseKey, onDeactivate, isDeactivating = false, showDetails = false, className, t }) => {
1118
- const remainingActivations = activationLimit - activationUsage;
1119
- return (React.createElement("div", { className: `designbase-figma-payment-status ${className || ''}` },
1120
- React.createElement("div", { className: "designbase-figma-payment-status__license-key" },
1121
- resolveText(t, { key: 'payment.license_key_label' }, '라이선스 키'),
1122
- ": ",
1123
- React.createElement("span", null, licenseKey)),
1124
- showDetails && (React.createElement("div", { className: "designbase-figma-payment-status__details" },
1125
- React.createElement("div", { className: "designbase-figma-payment-status__activation-info" },
1126
- React.createElement("div", { className: "designbase-figma-payment-status__remaining" },
1127
- remainingActivations,
1128
- " ",
1129
- resolveText(t, { key: 'payment.activations_remaining' }, '활성'),
1130
- " ",
1131
- resolveText(t, { key: 'payment.spots_remaining' }, '자리 남음')),
1132
- React.createElement("div", { className: "designbase-figma-payment-status__usage" },
1133
- activationUsage,
1134
- "/",
1135
- activationLimit)),
1136
- React.createElement(react.Button, { onPress: onDeactivate, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
1137
- ? resolveText(t, { key: 'payment.deactivating' }, '비활성화중...')
1138
- : resolveText(t, { key: 'payment.deactivate_license' }, '라이선스 비활성화'))))));
1440
+ const PaymentBadge = ({ isActive, onClick, isLoading = false, text, t = (key) => key, className, }) => {
1441
+ const getBadgeText = () => {
1442
+ if (text)
1443
+ return text;
1444
+ return isActive
1445
+ ? (t('proUser') || '프로 계정')
1446
+ : (t('usingFree') || '무료 사용');
1447
+ };
1448
+ const getBadgeVariant = () => {
1449
+ return isActive ? 'success' : 'secondary';
1450
+ };
1451
+ return (React.createElement("div", { className: `designbase-figma-payment-badge ${className || ''} ${isActive ? 'active' : 'free'}`, onClick: onClick, style: { cursor: onClick ? 'pointer' : 'default' } }, isLoading ? (React.createElement(react.Spinner, { size: "s" })) : (React.createElement(react.Badge, { variant: getBadgeVariant(), size: "s", className: "designbase-figma-payment-badge__badge" }, getBadgeText()))));
1139
1452
  };
1140
- PaymentStatusSection.displayName = 'PaymentStatusSection';
1453
+ PaymentBadge.displayName = 'PaymentBadge';
1141
1454
 
1142
1455
  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 }) => {
1143
1456
  const [isCompleted, setIsCompleted] = React.useState(false);
@@ -1181,7 +1494,7 @@ const ProgressModal = ({ isOpen, onClose, progress, title = { key: 'progress.wor
1181
1494
  React.createElement(react.Spinner, { size: "m" }))))),
1182
1495
  React.createElement("p", { className: "designbase-figma-progress-modal__message" }, displayMessage),
1183
1496
  React.createElement("div", { className: "designbase-figma-progress-modal__status" }, `${processedNodes} / ${totalNodes} (${progressPercentage.toFixed(1)}%)`),
1184
- React.createElement(react.Progressbar, { value: progressPercentage, size: "m", variant: isCompleted ? 'success' : 'primary', style: "animated", fullWidth: true }),
1497
+ React.createElement(react.Progressbar, { value: progressPercentage, size: "m", variant: isCompleted ? 'success' : 'primary', barStyle: "animated", fullWidth: true }),
1185
1498
  displayHelpText && !isCompleted && (React.createElement("p", { className: "designbase-figma-progress-modal__help-text" }, displayHelpText)),
1186
1499
  React.createElement("div", { className: "designbase-figma-progress-modal__actions" }, isCompleted ? (React.createElement(react.Button, { onPress: handleConfirm, variant: "primary", size: "m", fullWidth: true }, displayConfirmText)) : (React.createElement(react.Button, { onPress: handleStop, variant: "secondary", size: "m", fullWidth: true }, displayStopText))))));
1187
1500
  };
@@ -1933,6 +2246,7 @@ exports.FigmaToastProvider = FigmaToastProvider;
1933
2246
  exports.FormWithSubmit = FormWithSubmit;
1934
2247
  exports.InteractionFeedback = InteractionFeedback;
1935
2248
  exports.LanguageSelector = LanguageSelector;
2249
+ exports.LanguageSettingsModal = LanguageSettingsModal;
1936
2250
  exports.LicenseOffer = LicenseOffer;
1937
2251
  exports.LicenseStatusCard = LicenseStatusCard;
1938
2252
  exports.LogoDropdown = LogoDropdown;
@@ -1946,7 +2260,10 @@ exports.START_DESIGNBASE_AUTH_MESSAGE = START_DESIGNBASE_AUTH_MESSAGE;
1946
2260
  exports.SettingsModal = SettingsModal;
1947
2261
  exports.TitleDescription = TitleDescription;
1948
2262
  exports.UpgradeBanner = UpgradeBanner;
2263
+ exports.UsageStatusPopover = UsageStatusPopover;
1949
2264
  exports.getActiveSection = getActiveSection;
2265
+ exports.interpolateText = interpolateText;
2266
+ exports.resolvePaymentFeedbackVariant = resolvePaymentFeedbackVariant;
1950
2267
  exports.resolveText = resolveText;
1951
2268
  exports.scrollToSection = scrollToSection;
1952
2269
  exports.useFigmaLicenseBridge = useFigmaLicenseBridge;