@designbasekorea/figma-ui 0.6.3 → 0.6.4

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,7 +1,7 @@
1
- import React, { useRef, useState, useLayoutEffect, useEffect, createContext, useContext, useCallback } from 'react';
2
- import { HeartFilledIcon, CoffeeFilledIcon, ChevronLeftIcon, MoreHorizontalIcon, ChevronDownIcon, AppWindowIcon, YoutubeIcon, InstagramIcon, FigmaIcon, MailIcon, ExternalLinkIcon, CloseIcon, CircleCheckFilledIcon, ResizableIcon, GripVerticalIcon, ChevronRightIcon } from '@designbasekorea/icons';
3
- import { Button, Dropdown, Badge, Toggle, Spinner, SegmentControl, Logo, ToastContainer, Toast, Input, Modal, Progressbar, RandomGradient } from '@designbasekorea/ui';
4
- export * from '@designbasekorea/ui';
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';
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';
5
5
 
6
6
  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
7
 
@@ -11,10 +11,20 @@ function resolveText(t, input, fallback) {
11
11
  if (typeof input === 'string') {
12
12
  return input;
13
13
  }
14
- return t ? t(input.key, input.values) : input.key;
14
+ if (t) {
15
+ const translated = t(input.key, input.values);
16
+ if (translated && translated !== input.key) {
17
+ return translated;
18
+ }
19
+ }
20
+ return fallback ?? input.key;
15
21
  }
16
22
 
17
- const DonationBadge = ({ donationUrl = 'https://buymeacoffee.com/designbase', text = { key: 'donation.buy_me_a_coffee' }, iconType = 'heart', size = 's', variant = 'tertiary', fullWidth = false, disabled = false, showPrompt = false, promptText = { key: 'donation.prompt' }, className, onClick, t }) => {
23
+ const DONATION_ICON_NAMES = {
24
+ heart: 'star-filled',
25
+ coffee: 'cart',
26
+ };
27
+ const DonationBadge = ({ donationUrl = 'https://buymeacoffee.com/designbase', text = { key: 'donation.buy_me_a_coffee' }, iconType = 'heart', size = 's', variant = 'tertiary', fullWidth = false, iconOnly = false, disabled = false, showPrompt = false, promptText = { key: 'donation.prompt' }, className, onClick, t }) => {
18
28
  const handleClick = () => {
19
29
  if (onClick) {
20
30
  onClick();
@@ -23,15 +33,12 @@ const DonationBadge = ({ donationUrl = 'https://buymeacoffee.com/designbase', te
23
33
  window.open(donationUrl, '_blank', 'noopener,noreferrer');
24
34
  }
25
35
  };
26
- const Icon = iconType === 'heart' ? HeartFilledIcon : CoffeeFilledIcon;
27
36
  const displayText = resolveText(t, text, 'Buy me a coffee');
28
37
  const displayPromptText = resolveText(t, promptText, '도움이 되었다면 커피 한 잔 사주세요!');
29
38
  const buttonSize = size;
30
39
  return (React.createElement("div", { className: clsx('designbase-figma-donation-badge-container', className) },
31
- showPrompt && (React.createElement("div", { className: "designbase-figma-donation-badge__prompt" }, displayPromptText)),
32
- React.createElement(Button, { variant: variant, size: buttonSize, fullWidth: fullWidth, disabled: disabled, onPress: handleClick, startIcon: Icon, className: clsx('designbase-figma-donation-badge', `designbase-figma-donation-badge--${iconType}`), "aria-label": displayText, style: {
33
- '--designbase-figma-donation-badge-icon-color': 'var(--db-brand-primary)'
34
- } }, displayText)));
40
+ showPrompt && !iconOnly && (React.createElement("div", { className: "designbase-figma-donation-badge__prompt" }, displayPromptText)),
41
+ React.createElement(Button, { variant: variant, size: buttonSize, fullWidth: fullWidth, disabled: disabled, onPress: handleClick, iconOnly: iconOnly, startIcon: DONATION_ICON_NAMES[iconType], className: clsx('designbase-figma-donation-badge', `designbase-figma-donation-badge--${iconType}`), "aria-label": displayText }, iconOnly ? undefined : displayText)));
35
42
  };
36
43
  DonationBadge.displayName = 'DonationBadge';
37
44
 
@@ -69,10 +76,11 @@ const FigmaHeader = ({ children, actions = [], searchBar, sticky = true, classNa
69
76
  'designbase-figma-header--with-search': !!searchBar,
70
77
  'designbase-figma-header--with-actions': actions.length > 0,
71
78
  }, className);
79
+ const hasToolbar = !!children || actions.length > 0;
72
80
  return (React.createElement("header", { className: classes },
73
- React.createElement("div", { className: "designbase-figma-header__container" },
81
+ hasToolbar && (React.createElement("div", { className: "designbase-figma-header__container" },
74
82
  children && (React.createElement("div", { className: "designbase-figma-header__center" }, children)),
75
- actions.length > 0 && (React.createElement("div", { className: "designbase-figma-header__right" }, actions.map((action, index) => (React.createElement(Button, { key: index, variant: "tertiary", size: "s", iconOnly: true, "aria-label": `액션 ${index + 1}` }, action)))))),
83
+ actions.length > 0 && (React.createElement("div", { className: "designbase-figma-header__right" }, actions.map((action, index) => (React.createElement(Button, { key: `${action.startIcon}-${index}`, variant: "tertiary", size: "s", iconOnly: true, startIcon: action.startIcon, "aria-label": action.ariaLabel, onPress: action.onPress }))))))),
76
84
  searchBar && (React.createElement("div", { className: "designbase-figma-header__search-container" }, searchBar))));
77
85
  };
78
86
  FigmaHeader.displayName = 'FigmaHeader';
@@ -81,22 +89,24 @@ const FigmaPageHead = ({ onBack, title, description, actions = [], moreActions =
81
89
  const classes = clsx('designbase-figma-page-head', className);
82
90
  const displayTitle = resolveText(t, title);
83
91
  const displayDescription = description ? resolveText(t, description) : undefined;
84
- const dropdownItems = moreActions.map(action => ({
92
+ const dropdownItems = moreActions.map((action) => ({
85
93
  id: action.id,
86
94
  label: resolveText(t, action.label),
87
- onClick: action.onClick
88
95
  }));
96
+ const handleDropdownChange = (event) => {
97
+ const actionId = event.detail?.id ?? event.detail?.value;
98
+ const action = moreActions.find((item) => item.id === actionId);
99
+ action?.onClick();
100
+ };
89
101
  return (React.createElement("div", { className: classes },
90
102
  React.createElement("div", { className: "designbase-figma-page-head__left" },
91
- React.createElement(Button, { variant: "tertiary", size: "s", iconOnly: true, onPress: onBack, "aria-label": "\uB4A4\uB85C\uAC00\uAE30" },
92
- React.createElement(ChevronLeftIcon, { size: 14 })),
103
+ React.createElement(Button, { variant: "tertiary", size: "s", iconOnly: true, startIcon: "chevron-left", onPress: onBack, "aria-label": "\uB4A4\uB85C\uAC00\uAE30" }),
93
104
  React.createElement("div", { className: "designbase-figma-page-head__titles" },
94
105
  React.createElement("h2", null, displayTitle),
95
106
  displayDescription && React.createElement("p", null, displayDescription))),
96
107
  React.createElement("div", { className: "designbase-figma-page-head__actions" },
97
108
  actions.map((action, index) => (React.createElement(React.Fragment, { key: index }, action))),
98
- moreActions.length > 0 && (React.createElement(Dropdown, { trigger: React.createElement("button", { type: "button", className: "designbase-figma-page-head__more-button", "aria-label": "\uB354\uBCF4\uAE30" },
99
- React.createElement(MoreHorizontalIcon, { size: 16 })), items: dropdownItems, placement: "bottom-right", size: "s" })))));
109
+ moreActions.length > 0 && (React.createElement(Dropdown, { items: dropdownItems, triggerIcon: "more-horizontal", iconOnly: true, triggerVariant: "tertiary", placement: "bottom-right", size: "s", onChange: handleDropdownChange })))));
100
110
  };
101
111
  FigmaPageHead.displayName = 'FigmaPageHead';
102
112
 
@@ -413,10 +423,15 @@ const LanguageSelector = ({ currentLanguage, languages = DEFAULT_LANGUAGES, onLa
413
423
  const segmentOptions = languages.map((language) => ({
414
424
  value: language.code,
415
425
  label: resolveText(t, language.label),
416
- disabled: false
426
+ disabled: false,
417
427
  }));
418
428
  return (React.createElement("div", { className: `designbase-figma-language-selector ${className || ''}` },
419
- React.createElement(SegmentControl, { options: segmentOptions, value: activeLanguage, onChange: handleLanguageChange, size: size, className: "designbase-figma-language-selector__segment" })));
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" })));
420
435
  };
421
436
  LanguageSelector.displayName = 'LanguageSelector';
422
437
 
@@ -506,22 +521,29 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
506
521
  React.createElement("div", { className: "designbase-figma-footer__left" },
507
522
  React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, t: t }),
508
523
  showLanguageSelector && (React.createElement(LanguageSelector, { currentLanguage: currentLanguage, languages: languages, onLanguageChange: onLanguageChange, size: "s" }))),
509
- showPaymentStatus && (React.createElement("div", { className: "designbase-figma-footer__payment-states" },
510
- 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(React.Fragment, null,
511
- React.createElement("span", { className: "designbase-figma-footer__usage-info designbase-figma-footer__usage-info--tooltip", title: t('resetsDaily') },
512
- React.createElement("span", { className: "designbase-figma-footer__usage-count" }, usageCount),
513
- React.createElement("span", { className: "designbase-figma-footer__max-daily-usage" },
514
- "/",
515
- maxDailyUsage,
516
- " ",
517
- t('perDay'))))))),
518
- resolvedShowPaymentBadge && (React.createElement(PaymentBadge, { isActive: isActive, onClick: isBeta ? undefined : onLicensePageClick, isLoading: isLoading, text: isBeta ? (t('betaStatus') || 'BETA') : undefined, t: t })))),
519
- showDonation && (React.createElement("div", { className: "designbase-figma-footer__donation" },
520
- React.createElement(DonationBadge, { donationUrl: donationUrl, text: donationText, iconType: "heart", size: "s", showPrompt: showDonationText && !isActive, promptText: donationPromptText, t: t })))),
524
+ 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 })),
533
+ showDonation && (React.createElement(DonationBadge, { donationUrl: donationUrl, text: donationText, iconType: "heart", size: "s", iconOnly: true, showPrompt: false, t: t })))),
521
534
  children));
522
535
  };
523
536
  FigmaFooter.displayName = 'FigmaFooter';
524
537
 
538
+ const FigmaToastStack = ({ toasts, position, maxToasts, onRemoveToast, }) => {
539
+ const visibleToasts = [...toasts]
540
+ .sort((a, b) => b.createdAt - a.createdAt)
541
+ .slice(0, maxToasts);
542
+ if (visibleToasts.length === 0)
543
+ return null;
544
+ return (React.createElement("div", { className: `figma-toast-container figma-toast-container--${position}` }, visibleToasts.map((toast) => (React.createElement("div", { key: toast.id, className: "figma-toast-container__item" },
545
+ React.createElement(Toast, { toastId: toast.id, title: toast.title, description: toast.description, status: toast.status, duration: toast.duration, showProgress: toast.showProgress, showClose: toast.showCloseButton, onClose: () => onRemoveToast(toast.id) }))))));
546
+ };
525
547
  const ToastContext = createContext(null);
526
548
  const useToast = () => {
527
549
  const context = useContext(ToastContext);
@@ -539,13 +561,13 @@ const FigmaToastProvider = ({ children, position = 'top-right', maxToasts = 5, }
539
561
  id,
540
562
  createdAt: Date.now(),
541
563
  };
542
- setToasts(prev => {
564
+ setToasts((prev) => {
543
565
  const updated = [newToast, ...prev];
544
566
  return updated.slice(0, maxToasts);
545
567
  });
546
568
  }, [maxToasts]);
547
569
  const removeToast = useCallback((id) => {
548
- setToasts(prev => prev.filter(toast => toast.id !== id));
570
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
549
571
  }, []);
550
572
  const showToast = useCallback((message, options = {}) => {
551
573
  addToast({
@@ -564,17 +586,17 @@ const FigmaToastProvider = ({ children, position = 'top-right', maxToasts = 5, }
564
586
  };
565
587
  return (React.createElement(ToastContext.Provider, { value: contextValue },
566
588
  children,
567
- React.createElement(ToastContainer, { toasts: toasts, onRemoveToast: removeToast, position: position, maxToasts: maxToasts })));
589
+ React.createElement(FigmaToastStack, { toasts: toasts, onRemoveToast: removeToast, position: position, maxToasts: maxToasts })));
568
590
  };
569
591
  const FigmaToast = ({ message, isVisible, onClose, type = 'info', duration = 3000, className, }) => {
570
592
  if (!isVisible)
571
593
  return null;
572
594
  return (React.createElement("div", { className: `figma-toast-fixed-wrap ${className || ''}` },
573
- React.createElement(Toast, { id: "figma-toast-simple", title: message, status: type, duration: duration, showProgress: false, showCloseButton: true, onClose: onClose, position: "top-right" })));
595
+ React.createElement(Toast, { toastId: "figma-toast-simple", title: message, status: type, duration: duration, showProgress: false, showClose: true, onClose: onClose })));
574
596
  };
575
597
  const useFigmaToast = () => {
576
598
  const [toast, setToast] = useState({ message: '', isVisible: false });
577
- const showToast = useCallback((message, type = 'info') => {
599
+ const showToast = useCallback((message, _type = 'info') => {
578
600
  setToast({ message, isVisible: true });
579
601
  setTimeout(() => setToast({ message: '', isVisible: false }), 5000);
580
602
  }, []);
@@ -605,7 +627,12 @@ const FormWithSubmit = ({ onLicenseSubmit, disabled = false, isSubmitting = fals
605
627
  return (React.createElement("form", { className: `designbase-figma-form-with-submit ${className || ''}`, onSubmit: handleSubmit },
606
628
  React.createElement("div", { className: "designbase-figma-form-with-submit__field" },
607
629
  React.createElement("label", { className: "designbase-figma-form-with-submit__label" }, displayLabel),
608
- React.createElement(Input, { value: inputValue, onChange: handleChange, placeholder: displayPlaceholder, helperText: displayHelperText, disabled: disabled || isSubmitting, size: "m" })),
630
+ React.createElement(Input, { value: inputValue, onChange: (event) => {
631
+ const value = event.detail?.value;
632
+ if (value !== undefined) {
633
+ handleChange(value);
634
+ }
635
+ }, placeholder: displayPlaceholder, helperText: displayHelperText, disabled: disabled || isSubmitting, size: "m" })),
609
636
  React.createElement(Button, { type: "submit", variant: "primary", size: "m", loading: isSubmitting, disabled: !inputValue.trim() || isSubmitting || disabled, fullWidth: true }, isSubmitting ? displaySubmittingText : displaySubmitText)));
610
637
  };
611
638
  FormWithSubmit.displayName = 'FormWithSubmit';
@@ -658,18 +685,17 @@ const PricingComparison = ({ features = defaultFeatures, pricing = defaultPricin
658
685
  };
659
686
  return (React.createElement("div", { className: `designbase-figma-pricing-comparison ${className || ''}` },
660
687
  paymentType === 'subscription' && (React.createElement("div", { className: "designbase-figma-pricing-comparison__billing-toggle" },
661
- React.createElement(SegmentControl, { value: isYearly ? 'yearly' : 'monthly', onChange: (value) => setIsYearly(value === 'yearly'), options: [
688
+ React.createElement(SegmentControl, { value: isYearly ? 'yearly' : 'monthly', onChange: (event) => {
689
+ const value = event.detail?.value;
690
+ setIsYearly(value === 'yearly');
691
+ }, options: [
662
692
  {
663
693
  value: 'monthly',
664
694
  label: t('monthlyBilling') || '월간 결제',
665
695
  },
666
696
  {
667
697
  value: 'yearly',
668
- label: (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
669
- t('yearlyBilling') || '연간 결제',
670
- React.createElement("span", { className: "designbase-figma-pricing-comparison__discount-badge" },
671
- getDiscountPercentage(),
672
- "% \uC808\uC57D"))),
698
+ label: `${t('yearlyBilling') || '연간 결제'} (${getDiscountPercentage()}% 절약)`,
673
699
  },
674
700
  ], size: "s" }))),
675
701
  paymentType === 'lifetime' && (React.createElement("div", { className: "designbase-figma-pricing-comparison__lifetime-info" },
@@ -697,123 +723,306 @@ const PricingComparison = ({ features = defaultFeatures, pricing = defaultPricin
697
723
  };
698
724
  PricingComparison.displayName = 'PricingComparison';
699
725
 
700
- const PageLicense = ({ status: initialStatus, onClose, usageCount: initialUsageCount = 0, onLicenseSubmit, licenseKey: initialLicenseKey = '', setPaymentStatus, setUsageCount, setShowLicensePage, paymentPageUrl, pricingFeatures, pricing, paymentType = 'subscription', t, className, }) => {
701
- const [isSubmitting, setIsSubmitting] = useState(false);
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
+ const DEFAULT_LICENSE_MESSAGE_TYPES = {
742
+ initialize: 'initialize',
743
+ verifyLicense: 'verify-license',
744
+ deactivateLicense: 'deactivate-license',
745
+ updateStatus: 'update-plugin-status',
746
+ verifyResult: 'license-verification-result',
747
+ deactivateResult: 'license-deactivation-result',
748
+ };
749
+ const START_DESIGNBASE_AUTH_MESSAGE = 'start-designbase-auth';
750
+ const INITIAL_LICENSE_PAGE_STATE = {
751
+ paymentStatus: 'FREE',
752
+ licenseKey: '',
753
+ planTier: 'FREE',
754
+ hasLicenseKey: false,
755
+ isLicenseRegistered: false,
756
+ activationLimit: 0,
757
+ activationUsage: 0,
758
+ usageCount: 0,
759
+ remainingUsage: 0,
760
+ usageUsed: 0,
761
+ usageLimit: 0,
762
+ usageReset: 'none',
763
+ canUseFeature: true,
764
+ };
765
+
766
+ function readString(data, keys) {
767
+ for (const key of keys) {
768
+ const value = data[key];
769
+ if (typeof value === 'string') {
770
+ return value;
771
+ }
772
+ }
773
+ return '';
774
+ }
775
+ function readNumber(data, keys) {
776
+ for (const key of keys) {
777
+ const value = data[key];
778
+ if (typeof value === 'number' && Number.isFinite(value)) {
779
+ return value;
780
+ }
781
+ }
782
+ return undefined;
783
+ }
784
+ function readBoolean(data, keys) {
785
+ for (const key of keys) {
786
+ const value = data[key];
787
+ if (typeof value === 'boolean') {
788
+ return value;
789
+ }
790
+ }
791
+ return undefined;
792
+ }
793
+ function parseLicenseMessage(data) {
794
+ const paymentStatus = readString(data, ['paymentStatus', 'status']);
795
+ const usageLimitRaw = data.usageLimit;
796
+ return {
797
+ ...(paymentStatus ? { paymentStatus } : {}),
798
+ licenseKey: readString(data, ['licenseKey']) || undefined,
799
+ planTier: readString(data, ['planTier']) || undefined,
800
+ hasLicenseKey: readBoolean(data, ['hasLicenseKey']),
801
+ isLicenseRegistered: readBoolean(data, ['isLicenseRegistered']),
802
+ activationLimit: readNumber(data, ['activationLimit']),
803
+ activationUsage: readNumber(data, ['activationUsage']),
804
+ usageCount: readNumber(data, ['usageCount']),
805
+ remainingUsage: readNumber(data, ['remainingUsage']),
806
+ usageUsed: readNumber(data, ['usageUsed']),
807
+ usageLimit: usageLimitRaw === 'unlimited' ? 'unlimited' : readNumber(data, ['usageLimit']),
808
+ usageReset: readString(data, ['usageReset']) || undefined,
809
+ canUseFeature: readBoolean(data, ['canUseFeature']),
810
+ usageReason: readString(data, ['usageReason']) || undefined,
811
+ lastMessage: readString(data, ['message']) || undefined,
812
+ lastErrorCode: readString(data, ['errorCode']) || undefined,
813
+ };
814
+ }
815
+ function useFigmaLicenseBridge(options = {}) {
816
+ const messageTypes = useMemo(() => ({ ...DEFAULT_LICENSE_MESSAGE_TYPES, ...options.messageTypes }), [options.messageTypes]);
817
+ const [state, setState] = useState({
818
+ ...INITIAL_LICENSE_PAGE_STATE,
819
+ ...options.initialState,
820
+ });
821
+ const [isVerifying, setIsVerifying] = useState(false);
702
822
  const [isDeactivating, setIsDeactivating] = useState(false);
703
- const [licenseKey, setLicenseKey] = useState(initialLicenseKey);
704
- const [status, setStatus] = useState(initialStatus);
705
- const [usageCount, setLocalUsageCount] = useState(initialUsageCount);
706
- const [activationLimit, setActivationLimit] = useState(0);
707
- const [activationUsage, setActivationUsage] = useState(0);
708
- const [showDetails, setShowDetails] = useState(false);
823
+ const [isStartingAuth, setIsStartingAuth] = useState(false);
824
+ const optionsRef = useRef(options);
825
+ optionsRef.current = options;
826
+ const applyPatch = useCallback((patch) => {
827
+ setState((prev) => {
828
+ const next = {
829
+ ...prev,
830
+ ...Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined)),
831
+ };
832
+ optionsRef.current.onStateChange?.(next);
833
+ if (patch.paymentStatus) {
834
+ optionsRef.current.onPaymentStatusChange?.(patch.paymentStatus);
835
+ }
836
+ if (patch.usageCount !== undefined) {
837
+ optionsRef.current.onUsageCountChange?.(patch.usageCount);
838
+ }
839
+ return next;
840
+ });
841
+ }, []);
709
842
  useEffect(() => {
710
843
  const handleMessage = (event) => {
711
- const { type, paymentStatus, licenseKey, activationLimit, activationUsage, success, message, usageCount } = event.data.pluginMessage || {};
712
- if (type === 'update-plugin-status' || type === 'license-verification-result') {
713
- if (paymentStatus)
714
- setStatus(paymentStatus);
715
- if (licenseKey)
716
- setLicenseKey(licenseKey);
717
- if (activationLimit !== undefined)
718
- setActivationLimit(activationLimit);
719
- if (activationUsage !== undefined)
720
- setActivationUsage(activationUsage);
721
- if (paymentStatus)
722
- setPaymentStatus(paymentStatus);
723
- if (usageCount !== undefined) {
724
- setLocalUsageCount(usageCount);
725
- setUsageCount(usageCount);
726
- }
844
+ const data = event.data?.pluginMessage;
845
+ if (!data || typeof data !== 'object')
846
+ return;
847
+ const payload = data;
848
+ const type = typeof payload.type === 'string' ? payload.type : '';
849
+ if (type === messageTypes.updateStatus) {
850
+ applyPatch(parseLicenseMessage(payload));
727
851
  }
728
- if (type === 'license-verification-result') {
729
- setIsSubmitting(false);
730
- if (message)
731
- console.log('License verification:', message);
732
- if (success) {
733
- setStatus('PAID');
734
- setPaymentStatus('PAID');
852
+ if (type === messageTypes.verifyResult) {
853
+ setIsVerifying(false);
854
+ setIsStartingAuth(false);
855
+ applyPatch(parseLicenseMessage(payload));
856
+ if (payload.success === true) {
857
+ applyPatch({ paymentStatus: 'PAID', isLicenseRegistered: true });
735
858
  }
736
859
  }
737
- if (type === 'license-deactivation-result') {
860
+ if (type === messageTypes.deactivateResult) {
738
861
  setIsDeactivating(false);
739
- if (message)
740
- console.log('License deactivation:', message);
741
- if (success) {
742
- setStatus('UNPAID');
743
- setPaymentStatus('UNPAID');
744
- setLicenseKey('');
745
- setLocalUsageCount(20);
746
- setActivationLimit(0);
747
- setActivationUsage(0);
862
+ applyPatch(parseLicenseMessage(payload));
863
+ if (payload.success === true) {
864
+ applyPatch({
865
+ paymentStatus: 'UNPAID',
866
+ licenseKey: '',
867
+ hasLicenseKey: false,
868
+ isLicenseRegistered: false,
869
+ activationLimit: 0,
870
+ activationUsage: 0,
871
+ });
872
+ }
873
+ }
874
+ if (type === 'designbase-auth-result') {
875
+ setIsStartingAuth(false);
876
+ if (payload.success === true) {
877
+ applyPatch(parseLicenseMessage(payload));
878
+ }
879
+ else if (typeof payload.message === 'string') {
880
+ applyPatch({ lastMessage: payload.message });
748
881
  }
749
882
  }
750
883
  };
751
884
  window.addEventListener('message', handleMessage);
752
- if (typeof parent !== 'undefined') {
753
- parent.postMessage({ pluginMessage: { type: 'initialize' } }, '*');
754
- }
885
+ postPluginMessage({ type: messageTypes.initialize });
755
886
  return () => window.removeEventListener('message', handleMessage);
756
- }, [setPaymentStatus, setUsageCount]);
887
+ }, [applyPatch, messageTypes]);
888
+ const verifyLicense = useCallback((licenseKey) => {
889
+ setIsVerifying(true);
890
+ postPluginMessage({
891
+ type: messageTypes.verifyLicense,
892
+ licenseKey,
893
+ });
894
+ }, [messageTypes.verifyLicense]);
895
+ const deactivateLicense = useCallback(() => {
896
+ setIsDeactivating(true);
897
+ postPluginMessage({ type: messageTypes.deactivateLicense });
898
+ }, [messageTypes.deactivateLicense]);
899
+ const startDesignbaseAuth = useCallback(() => {
900
+ setIsStartingAuth(true);
901
+ postPluginMessage({ type: START_DESIGNBASE_AUTH_MESSAGE });
902
+ }, []);
903
+ return {
904
+ state,
905
+ isVerifying,
906
+ isDeactivating,
907
+ isStartingAuth,
908
+ verifyLicense,
909
+ deactivateLicense,
910
+ startDesignbaseAuth,
911
+ setLicenseKeyInput: (licenseKey) => applyPatch({ licenseKey }),
912
+ };
913
+ }
914
+
915
+ function formatUsageLabel(state, t) {
916
+ const { remainingUsage, usageUsed, usageLimit, usageReset } = state;
917
+ if (usageLimit === 'unlimited') {
918
+ return resolveText(t, { key: 'license.usage_unlimited' }, '사용 횟수 제한 없음');
919
+ }
920
+ const limit = typeof usageLimit === 'number' ? usageLimit : remainingUsage + usageUsed;
921
+ const remaining = remainingUsage ?? Math.max(0, limit - usageUsed);
922
+ if (usageReset === 'daily') {
923
+ const fallback = `오늘 남은 횟수 ${remaining}/${limit}`;
924
+ const translated = resolveText(t, { key: 'license.usage_daily' }, fallback);
925
+ return translated.includes(String(remaining)) ? translated : fallback;
926
+ }
927
+ if (usageReset === 'monthly') {
928
+ const fallback = `이번 달 남은 횟수 ${remaining}/${limit}`;
929
+ const translated = resolveText(t, { key: 'license.usage_monthly' }, fallback);
930
+ return translated.includes(String(remaining)) ? translated : fallback;
931
+ }
932
+ return `남은 횟수 ${remaining}/${limit}`;
933
+ }
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, }) => {
935
+ const links = useMemo(() => ({
936
+ purchaseUrl: wordpressLinks?.purchaseUrl ?? paymentPageUrl,
937
+ accountUrl: wordpressLinks?.accountUrl,
938
+ helpUrl: wordpressLinks?.helpUrl,
939
+ }), [wordpressLinks, paymentPageUrl]);
940
+ const { state, isVerifying, isDeactivating, isStartingAuth, verifyLicense, deactivateLicense, startDesignbaseAuth, setLicenseKeyInput, } = useFigmaLicenseBridge({
941
+ messageTypes,
942
+ initialState: {
943
+ paymentStatus: initialStatus,
944
+ licenseKey: initialLicenseKey,
945
+ usageCount: initialUsageCount,
946
+ },
947
+ onStateChange,
948
+ onPaymentStatusChange: setPaymentStatus,
949
+ onUsageCountChange: setUsageCount,
950
+ });
951
+ const [showDetails, setShowDetails] = useState(false);
952
+ const [licenseKeyDraft, setLicenseKeyDraft] = useState(initialLicenseKey);
953
+ const isPaid = state.isLicenseRegistered || state.paymentStatus === 'PAID';
954
+ const usageLabel = formatUsageLabel(state, t);
757
955
  const handleLicenseSubmit = async (submittedLicenseKey) => {
758
- setIsSubmitting(true);
759
- if (typeof parent !== 'undefined') {
760
- parent.postMessage({
761
- pluginMessage: {
762
- type: 'verify-license',
763
- licenseKey: submittedLicenseKey
764
- }
765
- }, '*');
956
+ if (onLicenseSubmit) {
957
+ await onLicenseSubmit(submittedLicenseKey);
958
+ return;
766
959
  }
960
+ verifyLicense(submittedLicenseKey);
767
961
  };
768
- const handleDeactivateLicense = () => {
769
- setIsDeactivating(true);
770
- if (typeof parent !== 'undefined') {
771
- parent.postMessage({
772
- pluginMessage: { type: 'deactivate-license' }
773
- }, '*');
774
- }
962
+ const handleLicenseKeyChange = (value) => {
963
+ setLicenseKeyDraft(value);
964
+ setLicenseKeyInput(value);
775
965
  };
776
- const isPaid = status === 'PAID';
777
966
  return (React.createElement("div", { className: `designbase-figma-page-license ${className || ''}` },
778
967
  React.createElement("div", { className: "designbase-figma-page-license__content" },
779
968
  React.createElement("div", { className: "designbase-figma-page-license__header" },
780
969
  React.createElement("div", { className: "designbase-figma-page-license__title" },
781
- React.createElement("h2", null, isPaid ? resolveText(t, { key: 'license.pro_account' }, '프로 계정') : resolveText(t, { key: 'license.upgrade_to_pro' }, '프로로 업그레이드'))),
970
+ React.createElement("h2", null, isPaid
971
+ ? resolveText(t, { key: 'license.pro_account' }, '프로 계정')
972
+ : resolveText(t, { key: 'license.upgrade_to_pro' }, '프로로 업그레이드')),
973
+ state.planTier && state.planTier !== 'FREE' && (React.createElement("span", { className: "designbase-figma-page-license__tier" }, state.planTier))),
782
974
  React.createElement("p", { className: "designbase-figma-page-license__description" }, isPaid
783
975
  ? resolveText(t, { key: 'license.all_features_available' }, '모든 기능을 사용할 수 있습니다.')
784
- : resolveText(t, { key: 'license.purchase_for_unlimited' }, '라이선스 구매 무제한 접근이 가능합니다.'))),
976
+ : resolveText(t, { key: 'license.purchase_for_unlimited' }, '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.')),
977
+ !isPaid && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
785
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)),
786
980
  isPaid && (React.createElement("div", { className: "designbase-figma-page-license__section" },
787
981
  React.createElement("div", { className: "designbase-figma-page-license__section-header" },
788
982
  React.createElement("h3", null, resolveText(t, { key: 'license.activated' }, '라이선스 활성화됨')),
789
983
  React.createElement(Button, { onClick: () => setShowDetails(!showDetails), variant: "tertiary", size: "s", iconOnly: true },
790
984
  React.createElement(MoreHorizontalIcon, { size: 16 }))),
791
- React.createElement("div", { className: "designbase-figma-page-license__license-key" },
985
+ state.licenseKey && (React.createElement("div", { className: "designbase-figma-page-license__license-key" },
792
986
  resolveText(t, { key: 'license.key_label' }, '라이선스 키'),
793
987
  ": ",
794
- React.createElement("span", null, licenseKey)),
988
+ React.createElement("span", null, state.licenseKey))),
989
+ React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel),
795
990
  showDetails && (React.createElement("div", { className: "designbase-figma-page-license__details" },
796
991
  React.createElement("div", { className: "designbase-figma-page-license__activation-info" },
797
992
  React.createElement("div", { className: "designbase-figma-page-license__remaining" },
798
- activationLimit - activationUsage,
799
- " ",
993
+ Math.max(0, state.activationLimit - state.activationUsage),
994
+ ' ',
800
995
  resolveText(t, { key: 'license.activations_remaining' }, '활성'),
801
- " ",
996
+ ' ',
802
997
  resolveText(t, { key: 'license.spots_remaining' }, '자리 남음')),
803
- React.createElement("div", { className: "designbase-figma-page-license__usage" },
804
- activationUsage,
998
+ React.createElement("div", { className: "designbase-figma-page-license__usage-count" },
999
+ state.activationUsage,
805
1000
  "/",
806
- activationLimit)),
807
- React.createElement(Button, { onClick: handleDeactivateLicense, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
808
- ? resolveText(t, { key: 'license.deactivating' }, '비활성화중...')
1001
+ state.activationLimit)),
1002
+ React.createElement(Button, { onClick: deactivateLicense, variant: "tertiary", size: "s", disabled: isDeactivating }, isDeactivating
1003
+ ? resolveText(t, { key: 'license.deactivating' }, '비활성화 중...')
809
1004
  : resolveText(t, { key: 'license.deactivate' }, '라이선스 비활성화')))))),
810
- !isPaid && (React.createElement("div", { className: "designbase-figma-page-license__section" },
811
- React.createElement("div", { className: "designbase-figma-page-license__section-header" },
812
- React.createElement("h3", null, resolveText(t, { key: 'license.enter_key' }, '라이선스 키 입력')),
813
- paymentPageUrl && (React.createElement(Button, { onClick: () => window.open(paymentPageUrl, '_blank'), variant: "primary", size: "s" },
814
- React.createElement("span", null, resolveText(t, { key: 'license.purchase' }, '라이선스 구매')),
815
- React.createElement(ExternalLinkIcon, { size: 16 })))),
816
- React.createElement(FormWithSubmit, { onLicenseSubmit: handleLicenseSubmit, disabled: false, isSubmitting: isSubmitting, value: licenseKey, onValueChange: setLicenseKey, label: { key: 'license.key' }, submitText: { key: 'license.submit' }, submittingText: { key: 'license.verifying' }, helperText: { key: 'license.enter_from_email' }, t: t })))),
1005
+ !isPaid && (React.createElement(React.Fragment, null,
1006
+ (enableAccountAuth || links.purchaseUrl || links.accountUrl) && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1007
+ React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1008
+ React.createElement("h3", null, resolveText(t, { key: 'license.designbase_account' }, '디자인베이스 계정'))),
1009
+ React.createElement("p", { className: "designbase-figma-page-license__hint" }, resolveText(t, { key: 'license.designbase_account_hint' }, 'WordPress 계정으로 로그인하거나 멤버십을 구매하면 플러그인이 자동으로 활성화됩니다.')),
1010
+ 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
1012
+ ? resolveText(t, { key: 'license.authenticating' }, '인증 중...')
1013
+ : 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' }, '멤버십 구매')),
1016
+ React.createElement(ExternalLinkIcon, { size: 16 }))),
1017
+ links.accountUrl && (React.createElement(Button, { onClick: () => openExternalUrl(links.accountUrl), variant: "tertiary", size: "s", fullWidth: true },
1018
+ React.createElement("span", null, resolveText(t, { key: 'license.manage_account' }, '계정·멤버십 관리')),
1019
+ React.createElement(ExternalLinkIcon, { size: 16 })))))),
1020
+ showLicenseKeyInput && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1021
+ React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1022
+ 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" },
1025
+ React.createElement("button", { type: "button", className: "designbase-figma-page-license__link", onClick: () => openExternalUrl(links.helpUrl) }, resolveText(t, { key: 'license.help_link' }, '라이선스·결제 도움말'))))),
817
1026
  React.createElement(Button, { className: "designbase-figma-page-license__close", onPress: onClose, variant: "tertiary", size: "s", "aria-label": "Close" },
818
1027
  React.createElement(CloseIcon, { size: 20 }))));
819
1028
  };
@@ -1087,11 +1296,11 @@ const UpgradeBanner = ({ onClick, variant = 'button', isLoading = false, title =
1087
1296
  React.createElement("div", { className: "designbase-figma-upgrade-banner__text-wrap" },
1088
1297
  React.createElement("h3", { className: "designbase-figma-upgrade-banner__title" }, displayTitle),
1089
1298
  React.createElement("p", { className: "designbase-figma-upgrade-banner__text" }, displayDescription)),
1090
- variant === 'button' ? (React.createElement(Button, { onClick: onClick, variant: "primary", size: "m", disabled: isLoading }, displayButtonText)) : (React.createElement("div", { className: "designbase-figma-upgrade-banner__icon" },
1299
+ variant === 'button' ? (React.createElement(Button, { onPress: onClick, variant: "primary", size: "m", disabled: isLoading }, displayButtonText)) : (React.createElement("div", { className: "designbase-figma-upgrade-banner__icon" },
1091
1300
  React.createElement(ChevronRightIcon, { size: 20 })))));
1092
1301
  return (React.createElement("div", { className: `designbase-figma-upgrade-banner designbase-figma-upgrade-banner--${variant} ${className || ''}` }, useGradient ? (React.createElement(RandomGradient, { scheme: gradientScheme, tone: gradientTone, animated: gradientAnimated, height: "100%", width: "100%", className: "designbase-figma-upgrade-banner__gradient-bg" }, bannerContent)) : (bannerContent)));
1093
1302
  };
1094
1303
  UpgradeBanner.displayName = 'UpgradeBanner';
1095
1304
 
1096
- export { DonationBadge, FigmaContainer, FigmaFooter, FigmaHeader, FigmaPageHead, FigmaSection, FigmaSidebar, FigmaToast, FigmaToastProvider, FormWithSubmit, InteractionFeedback, LanguageSelector, LogoDropdown, PageLicense, PaymentBadge, PaymentStatusSection, PricingComparison, ProgressModal, ResizablePlugin, SettingsModal, TitleDescription, UpgradeBanner, getActiveSection, resolveText, scrollToSection, useFigmaToast, useToast };
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 };
1097
1306
  //# sourceMappingURL=index.esm.js.map