@intlayer/design-system 7.5.4 → 7.5.6
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/esm/components/Browser/Browser.content.mjs +36 -17
- package/dist/esm/components/Browser/Browser.content.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs +191 -131
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/types/components/Badge/index.d.ts +2 -2
- package/dist/types/components/Breadcrumb/breadcrumb.content.d.ts +3 -3
- package/dist/types/components/Breadcrumb/index.d.ts +2 -2
- package/dist/types/components/Breadcrumb/index.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.content.d.ts +52 -11
- package/dist/types/components/Browser/Browser.content.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.d.ts +18 -60
- package/dist/types/components/Browser/Browser.d.ts.map +1 -1
- package/dist/types/components/Button/Button.d.ts +7 -7
- package/dist/types/components/Button/Button.d.ts.map +1 -1
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts +4 -4
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts.map +1 -1
- package/dist/types/components/Command/index.d.ts +17 -17
- package/dist/types/components/Command/index.d.ts.map +1 -1
- package/dist/types/components/Container/index.d.ts +6 -6
- package/dist/types/components/Container/index.d.ts.map +1 -1
- package/dist/types/components/CopyButton/CopyButton.content.d.ts +3 -3
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.d.ts +25 -25
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.content.d.ts +9 -9
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.content.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/dictionaryDetails.content.d.ts +33 -33
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.content.d.ts +25 -25
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.content.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/NavigationView/navigationViewNode.content.d.ts +25 -25
- package/dist/types/components/DictionaryFieldEditor/SaveForm/saveForm.content.d.ts +33 -33
- package/dist/types/components/DictionaryFieldEditor/StructureView/structureView.content.d.ts +9 -9
- package/dist/types/components/DictionaryFieldEditor/VersionSwitcherDropDown/versionSwitcherDropDown.content.d.ts +7 -7
- package/dist/types/components/DictionaryFieldEditor/dictionaryFieldEditor.content.d.ts +5 -5
- package/dist/types/components/DictionaryFieldEditor/nodeTypeSelector.content.d.ts +31 -31
- package/dist/types/components/ExpandCollapse/expandCollapse.content.d.ts +3 -3
- package/dist/types/components/Form/FormBase.d.ts +2 -2
- package/dist/types/components/Form/FormBase.d.ts.map +1 -1
- package/dist/types/components/Form/FormField.d.ts +2 -2
- package/dist/types/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/components/Form/FormItem.d.ts +2 -2
- package/dist/types/components/Form/FormItem.d.ts.map +1 -1
- package/dist/types/components/Form/elements/EditableFieldTextAreaElement.d.ts +2 -2
- package/dist/types/components/Form/elements/EditableFieldTextAreaElement.d.ts.map +1 -1
- package/dist/types/components/Form/elements/MultiselectElement.d.ts +2 -2
- package/dist/types/components/Form/elements/OTPElement.d.ts +2 -2
- package/dist/types/components/Form/elements/SelectElement.d.ts +2 -2
- package/dist/types/components/Form/elements/SwitchSelectorElement.d.ts +2 -2
- package/dist/types/components/IDE/CodeContext.d.ts +2 -2
- package/dist/types/components/IDE/CodeContext.d.ts.map +1 -1
- package/dist/types/components/IDE/code.content.d.ts +5 -5
- package/dist/types/components/IDE/copyCode.content.d.ts +5 -5
- package/dist/types/components/IDE/selectors.content.d.ts +7 -7
- package/dist/types/components/Input/Checkbox.d.ts +4 -4
- package/dist/types/components/Input/Checkbox.d.ts.map +1 -1
- package/dist/types/components/Input/Input.d.ts +3 -3
- package/dist/types/components/Input/Input.d.ts.map +1 -1
- package/dist/types/components/Input/OTPInput.d.ts +6 -6
- package/dist/types/components/Input/SearchInput.d.ts +2 -2
- package/dist/types/components/Link/Link.d.ts +6 -6
- package/dist/types/components/Link/Link.d.ts.map +1 -1
- package/dist/types/components/Loader/index.content.d.ts +3 -3
- package/dist/types/components/Loader/index.d.ts.map +1 -1
- package/dist/types/components/Loader/spinner.d.ts +2 -2
- package/dist/types/components/LocaleSwitcherContentDropDown/localeSwitcher.content.d.ts +17 -17
- package/dist/types/components/LocaleSwitcherContentDropDown/localeSwitcher.content.d.ts.map +1 -1
- package/dist/types/components/LocaleSwitcherDropDown/localeSwitcher.content.d.ts +13 -13
- package/dist/types/components/MarkDownRender/processor.d.ts.map +1 -1
- package/dist/types/components/MaxWidthSmoother/index.d.ts +2 -2
- package/dist/types/components/MaxWidthSmoother/index.d.ts.map +1 -1
- package/dist/types/components/Navbar/Burger.d.ts +2 -2
- package/dist/types/components/Navbar/Burger.d.ts.map +1 -1
- package/dist/types/components/Navbar/DesktopNavbar.d.ts +2 -2
- package/dist/types/components/Navbar/DesktopNavbar.d.ts.map +1 -1
- package/dist/types/components/Navbar/MobileNavbar.d.ts +2 -2
- package/dist/types/components/Navbar/MobileNavbar.d.ts.map +1 -1
- package/dist/types/components/Navbar/index.d.ts +2 -2
- package/dist/types/components/Navbar/index.d.ts.map +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts +3 -3
- package/dist/types/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/Pagination/pagination.content.d.ts +11 -11
- package/dist/types/components/Select/Select.d.ts +3 -3
- package/dist/types/components/Select/Select.d.ts.map +1 -1
- package/dist/types/components/SocialNetworks/index.d.ts +2 -2
- package/dist/types/components/SwitchSelector/index.d.ts +6 -6
- package/dist/types/components/Tab/Tab.d.ts +6 -6
- package/dist/types/components/Tab/TabContext.d.ts +2 -2
- package/dist/types/components/Tab/TabContext.d.ts.map +1 -1
- package/dist/types/components/TabSelector/TabSelector.d.ts +5 -5
- package/dist/types/components/TabSelector/TabSelector.d.ts.map +1 -1
- package/dist/types/components/Table/table.content.d.ts +3 -3
- package/dist/types/components/Tag/index.d.ts +5 -5
- package/dist/types/components/Terminal/terminal.content.d.ts +5 -5
- package/dist/types/components/Toaster/Toast.d.ts +3 -3
- package/dist/types/components/Toaster/Toast.d.ts.map +1 -1
- package/dist/types/components/Toaster/Toaster.d.ts +2 -2
- package/package.json +14 -14
|
@@ -44,23 +44,42 @@ const browserContent = {
|
|
|
44
44
|
}),
|
|
45
45
|
urlPlaceholder: "https://example.com",
|
|
46
46
|
errorMessage: t({
|
|
47
|
-
en: "Invalid URL. Try
|
|
48
|
-
"en-GB": "Invalid URL. Try
|
|
49
|
-
fr: "URL invalide. Essayez
|
|
50
|
-
es: "URL no válida. Intente
|
|
51
|
-
de: "Ungültige URL. Versuchen Sie
|
|
52
|
-
ja: "無効なURL。\"
|
|
53
|
-
ko: "잘못된 URL입니다. \"
|
|
54
|
-
zh: "无效的URL
|
|
55
|
-
it: "URL non valido. Prova
|
|
56
|
-
pt: "URL inválido. Tente
|
|
57
|
-
hi: "अमान्य URL। \"
|
|
58
|
-
ar: "رابط غير صالح. جرب
|
|
59
|
-
ru: "Неверный URL. Попробуйте
|
|
60
|
-
tr: "Geçersiz URL. \"
|
|
61
|
-
pl: "Nieprawidłowy adres URL. Wypróbuj
|
|
62
|
-
id: "URL tidak valid. Coba
|
|
63
|
-
vi: "URL không hợp lệ. Thử
|
|
47
|
+
en: "Invalid URL. Try a path like \"/page\" or a full URL.",
|
|
48
|
+
"en-GB": "Invalid URL. Try a path like \"/page\" or a full URL.",
|
|
49
|
+
fr: "URL invalide. Essayez un chemin comme \"/page\" ou une URL complète.",
|
|
50
|
+
es: "URL no válida. Intente una ruta como \"/page\" o una URL completa.",
|
|
51
|
+
de: "Ungültige URL. Versuchen Sie einen Pfad wie \"/page\" oder eine vollständige URL.",
|
|
52
|
+
ja: "無効なURL。\"/page\"のようなパスまたは完全なURLを試してください。",
|
|
53
|
+
ko: "잘못된 URL입니다. \"/page\"와 같은 경로 또는 전체 URL을 시도하십시오.",
|
|
54
|
+
zh: "无效的URL。尝试像\"/page\"这样的路径或完整的URL。",
|
|
55
|
+
it: "URL non valido. Prova un percorso come \"/page\" o un URL completo.",
|
|
56
|
+
pt: "URL inválido. Tente um caminho como \"/page\" ou uma URL completa.",
|
|
57
|
+
hi: "अमान्य URL। \"/page\" जैसा पथ या पूर्ण URL प्रयास करें।",
|
|
58
|
+
ar: "رابط غير صالح. جرب مسارًا مثل \"/page\" أو رابطًا كاملاً.",
|
|
59
|
+
ru: "Неверный URL. Попробуйте путь вроде \"/page\" или полный URL.",
|
|
60
|
+
tr: "Geçersiz URL. \"/page\" gibi bir yol veya tam URL deneyin.",
|
|
61
|
+
pl: "Nieprawidłowy adres URL. Wypróbuj ścieżkę jak \"/page\" lub pełny URL.",
|
|
62
|
+
id: "URL tidak valid. Coba jalur seperti \"/page\" atau URL lengkap.",
|
|
63
|
+
vi: "URL không hợp lệ. Thử đường dẫn như \"/page\" hoặc URL đầy đủ."
|
|
64
|
+
}),
|
|
65
|
+
domainRestrictionError: t({
|
|
66
|
+
en: "URL must match the application domain.",
|
|
67
|
+
"en-GB": "URL must match the application domain.",
|
|
68
|
+
fr: "L'URL doit correspondre au domaine de l'application.",
|
|
69
|
+
es: "La URL debe coincidir con el dominio de la aplicación.",
|
|
70
|
+
de: "Die URL muss mit der Anwendungsdomäne übereinstimmen.",
|
|
71
|
+
ja: "URLはアプリケーションドメインと一致する必要があります。",
|
|
72
|
+
ko: "URL은 애플리케이션 도메인과 일치해야 합니다.",
|
|
73
|
+
zh: "URL必须与应用程序域名匹配。",
|
|
74
|
+
it: "L'URL deve corrispondere al dominio dell'applicazione.",
|
|
75
|
+
pt: "A URL deve corresponder ao domínio da aplicação.",
|
|
76
|
+
hi: "URL एप्लिकेशन डोमेन से मेल खाना चाहिए।",
|
|
77
|
+
ar: "يجب أن يتطابق الرابط مع نطاق التطبيق.",
|
|
78
|
+
ru: "URL должен соответствовать домену приложения.",
|
|
79
|
+
tr: "URL, uygulama etki alanıyla eşleşmelidir.",
|
|
80
|
+
pl: "Adres URL musi pasować do domeny aplikacji.",
|
|
81
|
+
id: "URL harus cocok dengan domain aplikasi.",
|
|
82
|
+
vi: "URL phải khớp với tên miền ứng dụng."
|
|
64
83
|
}),
|
|
65
84
|
reloadButtonTitle: t({
|
|
66
85
|
en: "Reload page",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Browser.content.mjs","names":[],"sources":["../../../../src/components/Browser/Browser.content.ts"],"sourcesContent":["import { type Dictionary, t } from 'intlayer';\n\nexport const browserContent = {\n key: 'browser',\n content: {\n ariaLabel: t({\n en: 'Embedded browser',\n 'en-GB': 'Embedded browser',\n fr: 'Navigateur intégré',\n es: 'Navegador integrado',\n de: 'Eingebetteter Browser',\n ja: '埋め込みブラウザ',\n ko: '임베디드 브라우저',\n zh: '嵌入式浏览器',\n it: 'Browser incorporato',\n pt: 'Navegador incorporado',\n hi: 'एम्बेडेड ब्राउज़र',\n ar: 'متصفح مدمج',\n ru: 'Встроенный браузер',\n tr: 'Gömülü tarayıcı',\n pl: 'Osadzona przeglądarka',\n id: 'Peramban tertanam',\n vi: 'Trình duyệt nhúng',\n }),\n urlLabel: t({\n en: 'URL address bar',\n 'en-GB': 'URL address bar',\n fr: \"Barre d'adresse URL\",\n es: 'Barra de direcciones URL',\n de: 'URL-Adressleiste',\n ja: 'URLアドレスバー',\n ko: 'URL 주소 표시줄',\n zh: 'URL地址栏',\n it: 'Barra degli indirizzi URL',\n pt: 'Barra de endereços URL',\n hi: 'URL पता बार',\n ar: 'شريط عنوان URL',\n ru: 'Адресная строка URL',\n tr: 'URL adres çubuğu',\n pl: 'Pasek adresu URL',\n id: 'Bilah alamat URL',\n vi: 'Thanh địa chỉ URL',\n }),\n urlPlaceholder: 'https://example.com',\n errorMessage: t({\n en: 'Invalid URL. Try
|
|
1
|
+
{"version":3,"file":"Browser.content.mjs","names":[],"sources":["../../../../src/components/Browser/Browser.content.ts"],"sourcesContent":["import { type Dictionary, t } from 'intlayer';\n\nexport const browserContent = {\n key: 'browser',\n content: {\n ariaLabel: t({\n en: 'Embedded browser',\n 'en-GB': 'Embedded browser',\n fr: 'Navigateur intégré',\n es: 'Navegador integrado',\n de: 'Eingebetteter Browser',\n ja: '埋め込みブラウザ',\n ko: '임베디드 브라우저',\n zh: '嵌入式浏览器',\n it: 'Browser incorporato',\n pt: 'Navegador incorporado',\n hi: 'एम्बेडेड ब्राउज़र',\n ar: 'متصفح مدمج',\n ru: 'Встроенный браузер',\n tr: 'Gömülü tarayıcı',\n pl: 'Osadzona przeglądarka',\n id: 'Peramban tertanam',\n vi: 'Trình duyệt nhúng',\n }),\n urlLabel: t({\n en: 'URL address bar',\n 'en-GB': 'URL address bar',\n fr: \"Barre d'adresse URL\",\n es: 'Barra de direcciones URL',\n de: 'URL-Adressleiste',\n ja: 'URLアドレスバー',\n ko: 'URL 주소 표시줄',\n zh: 'URL地址栏',\n it: 'Barra degli indirizzi URL',\n pt: 'Barra de endereços URL',\n hi: 'URL पता बार',\n ar: 'شريط عنوان URL',\n ru: 'Адресная строка URL',\n tr: 'URL adres çubuğu',\n pl: 'Pasek adresu URL',\n id: 'Bilah alamat URL',\n vi: 'Thanh địa chỉ URL',\n }),\n urlPlaceholder: 'https://example.com',\n errorMessage: t({\n en: 'Invalid URL. Try a path like \"/page\" or a full URL.',\n 'en-GB': 'Invalid URL. Try a path like \"/page\" or a full URL.',\n fr: 'URL invalide. Essayez un chemin comme \"/page\" ou une URL complète.',\n es: 'URL no válida. Intente una ruta como \"/page\" o una URL completa.',\n de: 'Ungültige URL. Versuchen Sie einen Pfad wie \"/page\" oder eine vollständige URL.',\n ja: '無効なURL。\"/page\"のようなパスまたは完全なURLを試してください。',\n ko: '잘못된 URL입니다. \"/page\"와 같은 경로 또는 전체 URL을 시도하십시오.',\n zh: '无效的URL。尝试像\"/page\"这样的路径或完整的URL。',\n it: 'URL non valido. Prova un percorso come \"/page\" o un URL completo.',\n pt: 'URL inválido. Tente um caminho como \"/page\" ou uma URL completa.',\n hi: 'अमान्य URL। \"/page\" जैसा पथ या पूर्ण URL प्रयास करें।',\n ar: 'رابط غير صالح. جرب مسارًا مثل \"/page\" أو رابطًا كاملاً.',\n ru: 'Неверный URL. Попробуйте путь вроде \"/page\" или полный URL.',\n tr: 'Geçersiz URL. \"/page\" gibi bir yol veya tam URL deneyin.',\n pl: 'Nieprawidłowy adres URL. Wypróbuj ścieżkę jak \"/page\" lub pełny URL.',\n id: 'URL tidak valid. Coba jalur seperti \"/page\" atau URL lengkap.',\n vi: 'URL không hợp lệ. Thử đường dẫn như \"/page\" hoặc URL đầy đủ.',\n }),\n domainRestrictionError: t({\n en: 'URL must match the application domain.',\n 'en-GB': 'URL must match the application domain.',\n fr: \"L'URL doit correspondre au domaine de l'application.\",\n es: 'La URL debe coincidir con el dominio de la aplicación.',\n de: 'Die URL muss mit der Anwendungsdomäne übereinstimmen.',\n ja: 'URLはアプリケーションドメインと一致する必要があります。',\n ko: 'URL은 애플리케이션 도메인과 일치해야 합니다.',\n zh: 'URL必须与应用程序域名匹配。',\n it: \"L'URL deve corrispondere al dominio dell'applicazione.\",\n pt: 'A URL deve corresponder ao domínio da aplicação.',\n hi: 'URL एप्लिकेशन डोमेन से मेल खाना चाहिए।',\n ar: 'يجب أن يتطابق الرابط مع نطاق التطبيق.',\n ru: 'URL должен соответствовать домену приложения.',\n tr: 'URL, uygulama etki alanıyla eşleşmelidir.',\n pl: 'Adres URL musi pasować do domeny aplikacji.',\n id: 'URL harus cocok dengan domain aplikasi.',\n vi: 'URL phải khớp với tên miền ứng dụng.',\n }),\n reloadButtonTitle: t({\n en: 'Reload page',\n 'en-GB': 'Reload page',\n fr: 'Recharger la page',\n es: 'Recargar página',\n de: 'Seite neu laden',\n ja: 'ページを再読み込み',\n ko: '페이지 새로고침',\n zh: '重新加载页面',\n it: 'Ricarica pagina',\n pt: 'Recarregar página',\n hi: 'पृष्ठ को फिर से लोड करें',\n ar: 'إعادة تحميل الصفحة',\n ru: 'Перезагрузить страницу',\n tr: 'Sayfayı yenile',\n pl: 'Przeładuj stronę',\n id: 'Muat ulang halaman',\n vi: 'Tải lại trang',\n }),\n iframeTitle: t({\n en: 'Embedded web page',\n 'en-GB': 'Embedded web page',\n fr: 'Page web intégrée',\n es: 'Página web integrada',\n de: 'Eingebettete Webseite',\n ja: '埋め込みウェブページ',\n ko: '임베디드 웹페이지',\n zh: '嵌入式网页',\n it: 'Pagina web incorporata',\n pt: 'Página web incorporada',\n hi: 'एम्बेडेड वेब पेज',\n ar: 'صفحة ويب مدمجة',\n ru: 'Встроенная веб-страница',\n tr: 'Gömülü web sayfası',\n pl: 'Osadzona strona internetowa',\n id: 'Halaman web tertanam',\n vi: 'Trang web được nhúng',\n }),\n },\n title: 'Embedded browser component',\n description:\n 'Content declaration for the embedded browser UI component, including ARIA labels, URL placeholder, and error messages for displaying web pages within an iframe.',\n tags: ['UI component', 'embedded browser', 'iframe'],\n} satisfies Dictionary;\n\nexport default browserContent;\n"],"mappings":";;;AAEA,MAAa,iBAAiB;CAC5B,KAAK;CACL,SAAS;EACP,WAAW,EAAE;GACX,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACF,UAAU,EAAE;GACV,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACF,gBAAgB;EAChB,cAAc,EAAE;GACd,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACF,wBAAwB,EAAE;GACxB,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACF,mBAAmB,EAAE;GACnB,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACF,aAAa,EAAE;GACb,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL,CAAC;EACH;CACD,OAAO;CACP,aACE;CACF,MAAM;EAAC;EAAgB;EAAoB;EAAS;CACrD;AAED,8BAAe"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../utils/cn.mjs";
|
|
4
|
-
import { RotateCw } from "lucide-react";
|
|
5
|
-
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { ArrowLeft, ArrowRight, RotateCw } from "lucide-react";
|
|
5
|
+
import { useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
6
6
|
import { cva } from "class-variance-authority";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
import { useIntlayer } from "react-intlayer";
|
|
@@ -18,179 +18,239 @@ const browserVariants = cva("flex w-full flex-col overflow-hidden rounded-xl bg-
|
|
|
18
18
|
} },
|
|
19
19
|
defaultVariants: { size: "md" }
|
|
20
20
|
});
|
|
21
|
-
|
|
22
|
-
* Browser component that renders an iframe with a visible, editable URL bar.
|
|
23
|
-
* Allows users to view, edit, and navigate to different URLs within an embedded browser interface.
|
|
24
|
-
*
|
|
25
|
-
* Features:
|
|
26
|
-
* - Editable URL bar with strict validation (before navigation)
|
|
27
|
-
* - Automatic protocol addition (adds https:// if missing)
|
|
28
|
-
* - Integrated reload button inside the URL input
|
|
29
|
-
* - Error handling with visual feedback for invalid URLs
|
|
30
|
-
* - Responsive iframe with standardized sizes
|
|
31
|
-
* - Full accessibility support with ARIA attributes
|
|
32
|
-
* - Sandbox security for iframe content
|
|
33
|
-
* - Dark-themed UI matching modern browser aesthetics
|
|
34
|
-
* - Cross-browser compatibility (Chrome, Firefox, Safari)
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Basic usage
|
|
38
|
-
* <Browser initialUrl="https://example.com" size="md" />
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // With custom size and styling
|
|
42
|
-
* <Browser
|
|
43
|
-
* initialUrl="https://example.com"
|
|
44
|
-
* size="lg"
|
|
45
|
-
* className="shadow-2xl"
|
|
46
|
-
* aria-label="Documentation viewer"
|
|
47
|
-
* />
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // For content preview
|
|
51
|
-
* <Browser
|
|
52
|
-
* initialUrl="https://youtube.com/embed/VIDEO_ID"
|
|
53
|
-
* size="xl"
|
|
54
|
-
* aria-label="Video content browser"
|
|
55
|
-
* />
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // With custom sandbox restrictions
|
|
59
|
-
* <Browser
|
|
60
|
-
* initialUrl="https://example.com"
|
|
61
|
-
* sandbox="allow-scripts allow-same-origin"
|
|
62
|
-
* aria-label="Restricted content browser"
|
|
63
|
-
* />
|
|
64
|
-
*
|
|
65
|
-
* @param initialUrl - The initial URL to load in the iframe (default: 'https://example.com')
|
|
66
|
-
* @param className - Additional CSS classes for the main container element
|
|
67
|
-
* @param style - Inline CSS styles for the main container element
|
|
68
|
-
* @param size - Size of the browser window: 'xs' (400px), 'sm' (500px), 'md' (600px), 'lg' (800px), 'xl' (1000px). Defaults to 'md'
|
|
69
|
-
* @param aria-label - Accessible label for screen readers describing the browser's purpose (default: 'Embedded browser')
|
|
70
|
-
* @param sandbox - Sandbox attribute for the iframe to control security restrictions (default: 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads')
|
|
71
|
-
*/
|
|
72
|
-
const Browser = ({ initialUrl = "https://example.com", className, style, size = "md", "aria-label": ariaLabel, sandbox = "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads" }) => {
|
|
21
|
+
const Browser = ({ initialUrl = "https://example.com", path, className, style, size = "md", "aria-label": ariaLabel, sandbox = "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads", ref, domainRestriction, ...props }) => {
|
|
73
22
|
const [inputUrl, setInputUrl] = useState(initialUrl);
|
|
74
23
|
const [currentUrl, setCurrentUrl] = useState(initialUrl);
|
|
24
|
+
const [history, setHistory] = useState([initialUrl]);
|
|
25
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
75
26
|
const [error, setError] = useState(null);
|
|
76
27
|
const [submitted, setSubmitted] = useState(false);
|
|
77
|
-
const
|
|
28
|
+
const internalIframeRef = useRef(null);
|
|
29
|
+
useImperativeHandle(ref, () => internalIframeRef.current, []);
|
|
78
30
|
const content = useIntlayer("browser");
|
|
79
31
|
useEffect(() => {
|
|
80
32
|
setInputUrl(initialUrl);
|
|
81
33
|
setCurrentUrl(initialUrl);
|
|
34
|
+
setHistory([initialUrl]);
|
|
35
|
+
setCurrentIndex(0);
|
|
82
36
|
setError(null);
|
|
83
37
|
setSubmitted(false);
|
|
84
38
|
}, [initialUrl]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!path) return;
|
|
41
|
+
try {
|
|
42
|
+
const baseOrigin = domainRestriction ?? initialUrl;
|
|
43
|
+
const fullUrl = `${new URL(baseOrigin).origin}${path}`;
|
|
44
|
+
setInputUrl(fullUrl);
|
|
45
|
+
let isAlreadyAtUrl = false;
|
|
46
|
+
if (internalIframeRef.current?.contentWindow) try {
|
|
47
|
+
const currentIframeHref = internalIframeRef.current.contentWindow.location.href;
|
|
48
|
+
if (new URL(currentIframeHref).href === new URL(fullUrl).href) isAlreadyAtUrl = true;
|
|
49
|
+
} catch {}
|
|
50
|
+
if (!isAlreadyAtUrl) {
|
|
51
|
+
setCurrentUrl(fullUrl);
|
|
52
|
+
setHistory((prev) => {
|
|
53
|
+
const newHistory = prev.slice(0, currentIndex + 1);
|
|
54
|
+
newHistory.push(fullUrl);
|
|
55
|
+
return newHistory;
|
|
56
|
+
});
|
|
57
|
+
setCurrentIndex((prev) => prev + 1);
|
|
58
|
+
}
|
|
59
|
+
setError(null);
|
|
60
|
+
} catch {}
|
|
61
|
+
}, [
|
|
62
|
+
path,
|
|
63
|
+
domainRestriction,
|
|
64
|
+
initialUrl
|
|
65
|
+
]);
|
|
66
|
+
const handleNavigateTo = (url) => {
|
|
67
|
+
try {
|
|
68
|
+
const validated = normalizeUrl(url);
|
|
69
|
+
if (validated === currentUrl) {
|
|
70
|
+
handleReload();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setCurrentUrl(validated);
|
|
74
|
+
setInputUrl(validated);
|
|
75
|
+
setError(null);
|
|
76
|
+
const newHistory = history.slice(0, currentIndex + 1);
|
|
77
|
+
newHistory.push(validated);
|
|
78
|
+
setHistory(newHistory);
|
|
79
|
+
setCurrentIndex(newHistory.length - 1);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (e instanceof Error && e.message === "URL does not match allowed domain" && domainRestriction) setError(content.domainRestrictionError?.value ?? `Only URLs from ${domainRestriction} are allowed.`);
|
|
82
|
+
else setError(content.errorMessage.value);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const handleBack = () => {
|
|
86
|
+
if (currentIndex > 0) {
|
|
87
|
+
const newIndex = currentIndex - 1;
|
|
88
|
+
const prevUrl = history[newIndex];
|
|
89
|
+
setCurrentIndex(newIndex);
|
|
90
|
+
setCurrentUrl(prevUrl);
|
|
91
|
+
setInputUrl(prevUrl);
|
|
92
|
+
setError(null);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const handleForward = () => {
|
|
96
|
+
if (currentIndex < history.length - 1) {
|
|
97
|
+
const newIndex = currentIndex + 1;
|
|
98
|
+
const nextUrl = history[newIndex];
|
|
99
|
+
setCurrentIndex(newIndex);
|
|
100
|
+
setCurrentUrl(nextUrl);
|
|
101
|
+
setInputUrl(nextUrl);
|
|
102
|
+
setError(null);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const handleSubmit = (e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
setSubmitted(true);
|
|
108
|
+
handleNavigateTo(inputUrl);
|
|
109
|
+
};
|
|
110
|
+
const handleReload = () => {
|
|
111
|
+
if (internalIframeRef.current) {
|
|
112
|
+
const src = internalIframeRef.current.src;
|
|
113
|
+
internalIframeRef.current.src = "";
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
if (internalIframeRef.current) internalIframeRef.current.src = src;
|
|
116
|
+
}, 50);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
85
119
|
const isValidHostname = (host) => {
|
|
120
|
+
if (host === "localhost") return true;
|
|
121
|
+
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(host)) return true;
|
|
122
|
+
if (/^[a-f0-9:]+$/i.test(host)) return true;
|
|
86
123
|
if (!/^[a-z0-9.-]+$/i.test(host)) return false;
|
|
87
124
|
if (/^[-.]/.test(host) || /[-.]$/.test(host)) return false;
|
|
88
125
|
if (host.includes("..")) return false;
|
|
89
126
|
if (!host.includes(".")) return false;
|
|
90
127
|
return true;
|
|
91
128
|
};
|
|
129
|
+
const getRestrictionOrigin = () => {
|
|
130
|
+
if (!domainRestriction) return null;
|
|
131
|
+
try {
|
|
132
|
+
return new URL(domainRestriction);
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
92
137
|
const normalizeUrl = (raw) => {
|
|
93
138
|
const trimmed = raw.trim();
|
|
94
139
|
if (!trimmed || /\s/.test(trimmed)) throw new Error("Invalid");
|
|
140
|
+
const restrictionOrigin = getRestrictionOrigin();
|
|
141
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
142
|
+
if (restrictionOrigin) return new URL(`${restrictionOrigin.origin}${trimmed}`).toString();
|
|
143
|
+
return new URL(`${new URL(currentUrl).origin}${trimmed}`).toString();
|
|
144
|
+
}
|
|
95
145
|
const candidate = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
96
146
|
const url = new URL(candidate);
|
|
97
147
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("Only http(s) is allowed");
|
|
98
148
|
if (!isValidHostname(url.hostname)) throw new Error("Invalid host");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const validateAndNavigate = (url) => {
|
|
102
|
-
try {
|
|
103
|
-
const validated = normalizeUrl(url);
|
|
104
|
-
setCurrentUrl(validated);
|
|
105
|
-
setInputUrl(validated);
|
|
106
|
-
setError(null);
|
|
107
|
-
} catch {
|
|
108
|
-
setError(content.errorMessage.value);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
const handleSubmit = (e) => {
|
|
112
|
-
e.preventDefault();
|
|
113
|
-
setSubmitted(true);
|
|
114
|
-
validateAndNavigate(inputUrl);
|
|
115
|
-
};
|
|
116
|
-
const handleReload = () => {
|
|
117
|
-
if (iframeRef.current) {
|
|
118
|
-
const url = iframeRef.current.src;
|
|
119
|
-
iframeRef.current.src = "";
|
|
120
|
-
iframeRef.current.src = url;
|
|
149
|
+
if (restrictionOrigin) {
|
|
150
|
+
if (!(url.hostname === restrictionOrigin.hostname && url.protocol === restrictionOrigin.protocol && (restrictionOrigin.port === "" || url.port === restrictionOrigin.port || url.host === restrictionOrigin.host))) throw new Error("URL does not match allowed domain");
|
|
121
151
|
}
|
|
152
|
+
return url.toString();
|
|
122
153
|
};
|
|
123
154
|
const showError = submitted && !!error;
|
|
155
|
+
const canGoBack = currentIndex > 0;
|
|
156
|
+
const canGoForward = currentIndex < history.length - 1;
|
|
124
157
|
return /* @__PURE__ */ jsxs("section", {
|
|
125
158
|
className: cn(browserVariants({ size }), className),
|
|
126
159
|
style,
|
|
127
160
|
"aria-label": ariaLabel ?? content.ariaLabel.value,
|
|
128
161
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
129
|
-
className: "relative z-10 flex shrink-0
|
|
130
|
-
children: [
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
className: "
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
type: "text",
|
|
143
|
-
inputMode: "url",
|
|
144
|
-
spellCheck: false,
|
|
145
|
-
autoCapitalize: "off",
|
|
146
|
-
autoCorrect: "off",
|
|
147
|
-
value: inputUrl,
|
|
148
|
-
onChange: (e) => {
|
|
149
|
-
setInputUrl(e.target.value);
|
|
150
|
-
if (showError) setError(null);
|
|
151
|
-
},
|
|
152
|
-
placeholder: content.urlPlaceholder.value,
|
|
153
|
-
className: cn("w-full rounded-lg px-4 py-2 pr-11 text-sm leading-[1.4]", "bg-neutral-950 text-neutral-300", "placeholder:text-neutral-400", "transition-all focus:outline-none focus:ring-1 focus:ring-neutral-400/30", showError ? "border border-red-500" : "border border-transparent"),
|
|
154
|
-
"aria-label": content.urlLabel.value,
|
|
155
|
-
"aria-invalid": showError,
|
|
156
|
-
"aria-describedby": showError ? "browser-url-error" : void 0
|
|
157
|
-
}),
|
|
158
|
-
/* @__PURE__ */ jsx("button", {
|
|
162
|
+
className: "relative z-10 flex shrink-0 items-center gap-3 rounded-t-xl bg-neutral-900 px-4 py-2.5",
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: "flex items-center gap-1",
|
|
166
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
167
|
+
type: "button",
|
|
168
|
+
onClick: handleBack,
|
|
169
|
+
disabled: !canGoBack,
|
|
170
|
+
className: cn("flex h-8 w-8 items-center justify-center rounded-md text-neutral-400 transition-colors", canGoBack ? "hover:bg-neutral-800 hover:text-neutral-200" : "cursor-not-allowed opacity-40"),
|
|
171
|
+
"aria-label": "Go back",
|
|
172
|
+
title: "Go back",
|
|
173
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { size: 18 })
|
|
174
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
159
175
|
type: "button",
|
|
160
|
-
onClick:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
"aria-label":
|
|
164
|
-
|
|
165
|
-
children: /* @__PURE__ */ jsx(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
onClick: handleForward,
|
|
177
|
+
disabled: !canGoForward,
|
|
178
|
+
className: cn("flex h-8 w-8 items-center justify-center rounded-md text-neutral-400 transition-colors", canGoForward ? "hover:bg-neutral-800 hover:text-neutral-200" : "cursor-not-allowed opacity-40"),
|
|
179
|
+
"aria-label": "Go forward",
|
|
180
|
+
title: "Go forward",
|
|
181
|
+
children: /* @__PURE__ */ jsx(ArrowRight, { size: 18 })
|
|
182
|
+
})]
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ jsxs("form", {
|
|
185
|
+
onSubmit: handleSubmit,
|
|
186
|
+
className: "relative flex-1",
|
|
187
|
+
noValidate: true,
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsx("label", {
|
|
190
|
+
htmlFor: "browser-url",
|
|
191
|
+
className: "sr-only",
|
|
192
|
+
children: content.urlLabel.value
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ jsx("input", {
|
|
195
|
+
id: "browser-url",
|
|
196
|
+
type: "text",
|
|
197
|
+
inputMode: "url",
|
|
198
|
+
spellCheck: false,
|
|
199
|
+
autoCapitalize: "off",
|
|
200
|
+
autoCorrect: "off",
|
|
201
|
+
value: inputUrl,
|
|
202
|
+
onChange: (e) => {
|
|
203
|
+
setInputUrl(e.target.value);
|
|
204
|
+
if (showError) setError(null);
|
|
205
|
+
},
|
|
206
|
+
placeholder: content.urlPlaceholder.value,
|
|
207
|
+
className: cn("w-full rounded-lg px-4 py-2 pr-11 text-sm leading-[1.4]", "bg-neutral-950 text-neutral-300", "placeholder:text-neutral-400", "transition-all focus:outline-none focus:ring-1 focus:ring-neutral-400/30", showError ? "border border-red-500" : "border border-transparent"),
|
|
208
|
+
"aria-label": content.urlLabel.value,
|
|
209
|
+
"aria-invalid": showError,
|
|
210
|
+
"aria-describedby": showError ? "browser-url-error" : void 0
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ jsx("button", {
|
|
213
|
+
type: "button",
|
|
214
|
+
onClick: handleReload,
|
|
215
|
+
className: cn("-translate-y-1/2 absolute top-1/2 right-2", "flex h-8 w-8 items-center justify-center rounded-md", "transition-colors hover:bg-neutral-800", "focus:outline-none focus:ring-1 focus:ring-neutral-400/30"),
|
|
216
|
+
title: content.reloadButtonTitle.value,
|
|
217
|
+
"aria-label": content.reloadButtonTitle.value,
|
|
218
|
+
tabIndex: 0,
|
|
219
|
+
children: /* @__PURE__ */ jsx(RotateCw, {
|
|
220
|
+
size: 18,
|
|
221
|
+
className: "text-neutral-400",
|
|
222
|
+
strokeWidth: 2
|
|
223
|
+
})
|
|
224
|
+
}),
|
|
225
|
+
/* @__PURE__ */ jsx("button", {
|
|
226
|
+
type: "submit",
|
|
227
|
+
className: "sr-only absolute",
|
|
228
|
+
tabIndex: -1
|
|
169
229
|
})
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
230
|
+
]
|
|
231
|
+
}),
|
|
232
|
+
showError && /* @__PURE__ */ jsx("div", {
|
|
233
|
+
className: "absolute top-full left-4 z-20 mt-1",
|
|
234
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
235
|
+
id: "browser-url-error",
|
|
236
|
+
role: "alert",
|
|
237
|
+
"aria-live": "assertive",
|
|
238
|
+
className: "rounded-md bg-red-900/90 px-3 py-1.5 text-red-100 text-xs shadow-md backdrop-blur-sm",
|
|
239
|
+
children: error
|
|
175
240
|
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
id: "browser-url-error",
|
|
179
|
-
role: "alert",
|
|
180
|
-
"aria-live": "assertive",
|
|
181
|
-
className: "px-1 text-red-400 text-xs",
|
|
182
|
-
children: error
|
|
183
|
-
})]
|
|
241
|
+
})
|
|
242
|
+
]
|
|
184
243
|
}), /* @__PURE__ */ jsx("div", {
|
|
185
244
|
className: "relative z-0 min-h-0 w-full flex-1 overflow-hidden rounded-b-xl bg-background",
|
|
186
245
|
children: /* @__PURE__ */ jsx("iframe", {
|
|
187
|
-
ref:
|
|
246
|
+
ref: internalIframeRef,
|
|
188
247
|
src: currentUrl,
|
|
189
248
|
title: content.iframeTitle.value,
|
|
190
249
|
className: "h-full w-full rounded-b-xl border-0",
|
|
191
250
|
sandbox,
|
|
192
251
|
loading: "lazy",
|
|
193
|
-
"aria-live": "polite"
|
|
252
|
+
"aria-live": "polite",
|
|
253
|
+
...props
|
|
194
254
|
})
|
|
195
255
|
})]
|
|
196
256
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Browser.mjs","names":["Browser: FC<BrowserProps>"],"sources":["../../../../src/components/Browser/Browser.tsx"],"sourcesContent":["'use client';\n\nimport { cva } from 'class-variance-authority';\nimport { RotateCw } from 'lucide-react';\nimport {\n type CSSProperties,\n type FC,\n type FormEvent,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { cn } from '../../utils/cn';\n\nconst browserVariants = cva(\n 'flex w-full flex-col overflow-hidden rounded-xl bg-background shadow-[0_4px_12px_rgba(0,0,0,0.4),0_0_1px_rgba(0,0,0,0.2)]',\n {\n variants: {\n size: {\n xs: 'h-[400px]',\n sm: 'h-[500px]',\n md: 'h-[600px]',\n lg: 'h-[800px]',\n xl: 'h-[1000px]',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\nexport type BrowserProps = {\n /** Initial URL to load in the iframe */\n initialUrl?: string;\n /** Additional CSS classes for the container */\n className?: string;\n /** Inline styles for the container */\n style?: CSSProperties;\n /** Size of the browser window */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Accessible label for screen readers describing the browser purpose */\n 'aria-label'?: string;\n /** Sandbox attribute for the iframe to control security restrictions */\n sandbox?: string;\n};\n\n/**\n * Browser component that renders an iframe with a visible, editable URL bar.\n * Allows users to view, edit, and navigate to different URLs within an embedded browser interface.\n *\n * Features:\n * - Editable URL bar with strict validation (before navigation)\n * - Automatic protocol addition (adds https:// if missing)\n * - Integrated reload button inside the URL input\n * - Error handling with visual feedback for invalid URLs\n * - Responsive iframe with standardized sizes\n * - Full accessibility support with ARIA attributes\n * - Sandbox security for iframe content\n * - Dark-themed UI matching modern browser aesthetics\n * - Cross-browser compatibility (Chrome, Firefox, Safari)\n *\n * @example\n * // Basic usage\n * <Browser initialUrl=\"https://example.com\" size=\"md\" />\n *\n * @example\n * // With custom size and styling\n * <Browser\n * initialUrl=\"https://example.com\"\n * size=\"lg\"\n * className=\"shadow-2xl\"\n * aria-label=\"Documentation viewer\"\n * />\n *\n * @example\n * // For content preview\n * <Browser\n * initialUrl=\"https://youtube.com/embed/VIDEO_ID\"\n * size=\"xl\"\n * aria-label=\"Video content browser\"\n * />\n *\n * @example\n * // With custom sandbox restrictions\n * <Browser\n * initialUrl=\"https://example.com\"\n * sandbox=\"allow-scripts allow-same-origin\"\n * aria-label=\"Restricted content browser\"\n * />\n *\n * @param initialUrl - The initial URL to load in the iframe (default: 'https://example.com')\n * @param className - Additional CSS classes for the main container element\n * @param style - Inline CSS styles for the main container element\n * @param size - Size of the browser window: 'xs' (400px), 'sm' (500px), 'md' (600px), 'lg' (800px), 'xl' (1000px). Defaults to 'md'\n * @param aria-label - Accessible label for screen readers describing the browser's purpose (default: 'Embedded browser')\n * @param sandbox - Sandbox attribute for the iframe to control security restrictions (default: 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads')\n */\nexport const Browser: FC<BrowserProps> = ({\n initialUrl = 'https://example.com',\n className,\n style,\n size = 'md',\n 'aria-label': ariaLabel,\n sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n}) => {\n const [inputUrl, setInputUrl] = useState(initialUrl);\n const [currentUrl, setCurrentUrl] = useState(initialUrl);\n const [error, setError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false); // show errors only after attempt\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n // Load internationalized content\n const content = useIntlayer('browser');\n\n useEffect(() => {\n setInputUrl(initialUrl);\n setCurrentUrl(initialUrl);\n setError(null);\n setSubmitted(false);\n }, [initialUrl]);\n\n // --- Validation helpers ----------------------------------------------------\n const isValidHostname = (host: string) => {\n // allowed chars\n if (!/^[a-z0-9.-]+$/i.test(host)) return false;\n // no leading/trailing dot or hyphen\n if (/^[-.]/.test(host) || /[-.]$/.test(host)) return false;\n // no double dots\n if (host.includes('..')) return false;\n // must have at least one dot\n if (!host.includes('.')) return false;\n\n return true;\n };\n\n const normalizeUrl = (raw: string) => {\n const trimmed = raw.trim();\n if (!trimmed || /\\s/.test(trimmed)) throw new Error('Invalid');\n\n // Add https:// if protocol is missing\n const candidate = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)\n ? trimmed\n : `https://${trimmed}`;\n\n const url = new URL(candidate);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Only http(s) is allowed');\n }\n\n if (!isValidHostname(url.hostname)) {\n throw new Error('Invalid host');\n }\n\n return url.toString();\n };\n\n const validateAndNavigate = (url: string) => {\n try {\n const validated = normalizeUrl(url);\n setCurrentUrl(validated);\n setInputUrl(validated);\n setError(null);\n } catch {\n setError(content.errorMessage.value);\n }\n };\n // --------------------------------------------------------------------------\n\n const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n setSubmitted(true);\n validateAndNavigate(inputUrl);\n };\n\n const handleReload = () => {\n if (iframeRef.current) {\n const url = iframeRef.current.src;\n iframeRef.current.src = '';\n iframeRef.current.src = url;\n }\n };\n\n const showError = submitted && !!error;\n\n return (\n <section\n className={cn(browserVariants({ size }), className)}\n style={style}\n aria-label={ariaLabel ?? content.ariaLabel.value}\n >\n {/* Top bar */}\n <div className=\"relative z-10 flex shrink-0 flex-col gap-1 rounded-t-xl bg-neutral-900 px-4 py-2.5 shadow-[0_3px_4px_0_rgba(0,0,0,0.25)]\">\n <form onSubmit={handleSubmit} className=\"relative flex-1\" noValidate>\n <label htmlFor=\"browser-url\" className=\"sr-only\">\n {content.urlLabel.value}\n </label>\n <input\n id=\"browser-url\"\n type=\"text\"\n inputMode=\"url\"\n spellCheck={false}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n value={inputUrl}\n onChange={(e) => {\n setInputUrl(e.target.value);\n if (showError) setError(null);\n }}\n placeholder={content.urlPlaceholder.value}\n className={cn(\n 'w-full rounded-lg px-4 py-2 pr-11 text-sm leading-[1.4]',\n 'bg-neutral-950 text-neutral-300',\n 'placeholder:text-neutral-400',\n 'transition-all focus:outline-none focus:ring-1 focus:ring-neutral-400/30',\n showError ? 'border border-red-500' : 'border border-transparent'\n )}\n aria-label={content.urlLabel.value}\n aria-invalid={showError}\n aria-describedby={showError ? 'browser-url-error' : undefined}\n />\n\n {/* Absolutely positioned button inside the input */}\n <button\n type=\"button\"\n onClick={handleReload}\n className={cn(\n '-translate-y-1/2 absolute top-1/2 right-2',\n 'flex h-8 w-8 items-center justify-center rounded-md',\n 'transition-colors hover:bg-neutral-800',\n 'focus:outline-none focus:ring-1 focus:ring-neutral-400/30'\n )}\n title={content.reloadButtonTitle.value}\n aria-label={content.reloadButtonTitle.value}\n tabIndex={0}\n >\n <RotateCw size={18} className=\"text-neutral-400\" strokeWidth={2} />\n </button>\n\n {/* invisible submit to allow Enter to work semantically */}\n <button type=\"submit\" className=\"sr-only absolute\" tabIndex={-1} />\n </form>\n\n {/* subtle inline error text */}\n {showError && (\n <p\n id=\"browser-url-error\"\n role=\"alert\"\n aria-live=\"assertive\"\n className=\"px-1 text-red-400 text-xs\"\n >\n {error}\n </p>\n )}\n </div>\n\n {/* Iframe */}\n <div className=\"relative z-0 min-h-0 w-full flex-1 overflow-hidden rounded-b-xl bg-background\">\n <iframe\n ref={iframeRef}\n src={currentUrl}\n title={content.iframeTitle.value}\n className=\"h-full w-full rounded-b-xl border-0\"\n sandbox={sandbox}\n loading=\"lazy\"\n aria-live=\"polite\"\n />\n </div>\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Browser.mjs","names":[],"sources":["../../../../src/components/Browser/Browser.tsx"],"sourcesContent":["'use client';\n\nimport { cva } from 'class-variance-authority';\nimport { ArrowLeft, ArrowRight, RotateCw } from 'lucide-react';\nimport {\n type CSSProperties,\n type FormEvent,\n type HTMLAttributes,\n type RefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { cn } from '../../utils/cn';\n\nconst browserVariants = cva(\n 'flex w-full flex-col overflow-hidden rounded-xl bg-background shadow-[0_4px_12px_rgba(0,0,0,0.4),0_0_1px_rgba(0,0,0,0.2)]',\n {\n variants: {\n size: {\n xs: 'h-[400px]',\n sm: 'h-[500px]',\n md: 'h-[600px]',\n lg: 'h-[800px]',\n xl: 'h-[1000px]',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n);\n\nexport type BrowserProps = {\n initialUrl?: string;\n path?: string;\n className?: string;\n style?: CSSProperties;\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n 'aria-label'?: string;\n sandbox?: string;\n ref?: RefObject<HTMLIFrameElement | null>;\n domainRestriction?: string;\n} & HTMLAttributes<HTMLIFrameElement>;\n\nexport const Browser = ({\n initialUrl = 'https://example.com',\n path,\n className,\n style,\n size = 'md',\n 'aria-label': ariaLabel,\n sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n ref,\n domainRestriction,\n ...props\n}: BrowserProps) => {\n // --- State -----------------------------------------------------------------\n const [inputUrl, setInputUrl] = useState(initialUrl);\n const [currentUrl, setCurrentUrl] = useState(initialUrl);\n\n // History Management\n const [history, setHistory] = useState<string[]>([initialUrl]);\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const [error, setError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const internalIframeRef = useRef<HTMLIFrameElement>(null);\n\n useImperativeHandle(ref, () => internalIframeRef.current!, []);\n const content = useIntlayer('browser');\n\n // --- Effects ---------------------------------------------------------------\n\n // Reset everything if initialUrl changes completely\n useEffect(() => {\n setInputUrl(initialUrl);\n setCurrentUrl(initialUrl);\n setHistory([initialUrl]);\n setCurrentIndex(0);\n setError(null);\n setSubmitted(false);\n }, [initialUrl]);\n\n // Sync external path changes with the URL bar and History\n useEffect(() => {\n if (!path) return;\n\n try {\n const baseOrigin = domainRestriction ?? initialUrl;\n const origin = new URL(baseOrigin).origin;\n const fullUrl = `${origin}${path}`;\n\n // 1. Update Input\n setInputUrl(fullUrl);\n\n // 2. Check internal iframe state to avoid reload if already there\n let isAlreadyAtUrl = false;\n if (internalIframeRef.current?.contentWindow) {\n try {\n const currentIframeHref =\n internalIframeRef.current.contentWindow.location.href;\n if (new URL(currentIframeHref).href === new URL(fullUrl).href) {\n isAlreadyAtUrl = true;\n }\n } catch {\n // Cross-origin access ignored\n }\n }\n\n // 3. Navigate if needed (Push to history)\n if (!isAlreadyAtUrl) {\n // If the path prop forces a change, we treat it as a new navigation\n setCurrentUrl(fullUrl);\n\n // Update History Stack\n setHistory((prev) => {\n const newHistory = prev.slice(0, currentIndex + 1);\n newHistory.push(fullUrl);\n return newHistory;\n });\n setCurrentIndex((prev) => prev + 1);\n }\n\n setError(null);\n } catch {\n // Ignore invalid paths\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [path, domainRestriction, initialUrl]); // Removed currentIndex dependency to prevent loops\n\n // --- Navigation Logic ------------------------------------------------------\n\n const handleNavigateTo = (url: string) => {\n try {\n const validated = normalizeUrl(url);\n\n // If we are navigating to the exact same URL, just reload\n if (validated === currentUrl) {\n handleReload();\n return;\n }\n\n setCurrentUrl(validated);\n setInputUrl(validated);\n setError(null);\n\n // Update History: Slice future if we went back, then push new\n const newHistory = history.slice(0, currentIndex + 1);\n newHistory.push(validated);\n setHistory(newHistory);\n setCurrentIndex(newHistory.length - 1);\n } catch (e) {\n if (\n e instanceof Error &&\n e.message === 'URL does not match allowed domain' &&\n domainRestriction\n ) {\n setError(\n content.domainRestrictionError?.value ??\n `Only URLs from ${domainRestriction} are allowed.`\n );\n } else {\n setError(content.errorMessage.value);\n }\n }\n };\n\n const handleBack = () => {\n if (currentIndex > 0) {\n const newIndex = currentIndex - 1;\n const prevUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(prevUrl);\n setInputUrl(prevUrl);\n setError(null);\n }\n };\n\n const handleForward = () => {\n if (currentIndex < history.length - 1) {\n const newIndex = currentIndex + 1;\n const nextUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(nextUrl);\n setInputUrl(nextUrl);\n setError(null);\n }\n };\n\n const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n setSubmitted(true);\n handleNavigateTo(inputUrl);\n };\n\n const handleReload = () => {\n if (internalIframeRef.current) {\n // Create a clean reload effect\n const src = internalIframeRef.current.src;\n internalIframeRef.current.src = '';\n setTimeout(() => {\n if (internalIframeRef.current) internalIframeRef.current.src = src;\n }, 50);\n }\n };\n\n // --- Validation Helpers ----------------------------------------------------\n const isValidHostname = (host: string) => {\n if (host === 'localhost') return true;\n if (/^(\\d{1,3}\\.){3}\\d{1,3}$/.test(host)) return true;\n if (/^[a-f0-9:]+$/i.test(host)) return true;\n if (!/^[a-z0-9.-]+$/i.test(host)) return false;\n if (/^[-.]/.test(host) || /[-.]$/.test(host)) return false;\n if (host.includes('..')) return false;\n if (!host.includes('.')) return false;\n return true;\n };\n\n const getRestrictionOrigin = (): URL | null => {\n if (!domainRestriction) return null;\n try {\n return new URL(domainRestriction);\n } catch {\n return null;\n }\n };\n\n const normalizeUrl = (raw: string) => {\n const trimmed = raw.trim();\n if (!trimmed || /\\s/.test(trimmed)) throw new Error('Invalid');\n\n const restrictionOrigin = getRestrictionOrigin();\n const isRelativePath = trimmed.startsWith('/') && !trimmed.startsWith('//');\n\n if (isRelativePath) {\n if (restrictionOrigin) {\n return new URL(`${restrictionOrigin.origin}${trimmed}`).toString();\n }\n return new URL(`${new URL(currentUrl).origin}${trimmed}`).toString();\n }\n\n const hasProtocol = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed);\n const candidate = hasProtocol ? trimmed : `https://${trimmed}`;\n const url = new URL(candidate);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Only http(s) is allowed');\n }\n\n if (!isValidHostname(url.hostname)) throw new Error('Invalid host');\n\n if (restrictionOrigin) {\n const urlMatches =\n url.hostname === restrictionOrigin.hostname &&\n url.protocol === restrictionOrigin.protocol &&\n (restrictionOrigin.port === '' ||\n url.port === restrictionOrigin.port ||\n url.host === restrictionOrigin.host);\n\n if (!urlMatches) throw new Error('URL does not match allowed domain');\n }\n\n return url.toString();\n };\n\n const showError = submitted && !!error;\n const canGoBack = currentIndex > 0;\n const canGoForward = currentIndex < history.length - 1;\n\n return (\n <section\n className={cn(browserVariants({ size }), className)}\n style={style}\n aria-label={ariaLabel ?? content.ariaLabel.value}\n >\n {/* Top bar */}\n <div className=\"relative z-10 flex shrink-0 items-center gap-3 rounded-t-xl bg-neutral-900 px-4 py-2.5\">\n {/* Navigation Controls */}\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={handleBack}\n disabled={!canGoBack}\n className={cn(\n 'flex h-8 w-8 items-center justify-center rounded-md text-neutral-400 transition-colors',\n canGoBack\n ? 'hover:bg-neutral-800 hover:text-neutral-200'\n : 'cursor-not-allowed opacity-40'\n )}\n aria-label=\"Go back\"\n title=\"Go back\"\n >\n <ArrowLeft size={18} />\n </button>\n\n <button\n type=\"button\"\n onClick={handleForward}\n disabled={!canGoForward}\n className={cn(\n 'flex h-8 w-8 items-center justify-center rounded-md text-neutral-400 transition-colors',\n canGoForward\n ? 'hover:bg-neutral-800 hover:text-neutral-200'\n : 'cursor-not-allowed opacity-40'\n )}\n aria-label=\"Go forward\"\n title=\"Go forward\"\n >\n <ArrowRight size={18} />\n </button>\n </div>\n\n {/* URL Bar */}\n <form onSubmit={handleSubmit} className=\"relative flex-1\" noValidate>\n <label htmlFor=\"browser-url\" className=\"sr-only\">\n {content.urlLabel.value}\n </label>\n <input\n id=\"browser-url\"\n type=\"text\"\n inputMode=\"url\"\n spellCheck={false}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n value={inputUrl}\n onChange={(e) => {\n setInputUrl(e.target.value);\n if (showError) setError(null);\n }}\n placeholder={content.urlPlaceholder.value}\n className={cn(\n 'w-full rounded-lg px-4 py-2 pr-11 text-sm leading-[1.4]',\n 'bg-neutral-950 text-neutral-300',\n 'placeholder:text-neutral-400',\n 'transition-all focus:outline-none focus:ring-1 focus:ring-neutral-400/30',\n showError ? 'border border-red-500' : 'border border-transparent'\n )}\n aria-label={content.urlLabel.value}\n aria-invalid={showError}\n aria-describedby={showError ? 'browser-url-error' : undefined}\n />\n\n {/* Reload Button */}\n <button\n type=\"button\"\n onClick={handleReload}\n className={cn(\n '-translate-y-1/2 absolute top-1/2 right-2',\n 'flex h-8 w-8 items-center justify-center rounded-md',\n 'transition-colors hover:bg-neutral-800',\n 'focus:outline-none focus:ring-1 focus:ring-neutral-400/30'\n )}\n title={content.reloadButtonTitle.value}\n aria-label={content.reloadButtonTitle.value}\n tabIndex={0}\n >\n <RotateCw size={18} className=\"text-neutral-400\" strokeWidth={2} />\n </button>\n\n {/* invisible submit */}\n <button type=\"submit\" className=\"sr-only absolute\" tabIndex={-1} />\n </form>\n\n {/* Error Message Tooltip */}\n {showError && (\n <div className=\"absolute top-full left-4 z-20 mt-1\">\n <p\n id=\"browser-url-error\"\n role=\"alert\"\n aria-live=\"assertive\"\n className=\"rounded-md bg-red-900/90 px-3 py-1.5 text-red-100 text-xs shadow-md backdrop-blur-sm\"\n >\n {error}\n </p>\n </div>\n )}\n </div>\n\n {/* Iframe */}\n <div className=\"relative z-0 min-h-0 w-full flex-1 overflow-hidden rounded-b-xl bg-background\">\n <iframe\n ref={internalIframeRef}\n src={currentUrl}\n title={content.iframeTitle.value}\n className=\"h-full w-full rounded-b-xl border-0\"\n sandbox={sandbox}\n loading=\"lazy\"\n aria-live=\"polite\"\n {...props}\n />\n </div>\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes } from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as class_variance_authority_types7 from "class-variance-authority/types";
|
|
3
3
|
import { VariantProps } from "class-variance-authority";
|
|
4
4
|
|
|
5
5
|
//#region src/components/Badge/index.d.ts
|
|
@@ -47,7 +47,7 @@ declare const badgeVariants: (props?: {
|
|
|
47
47
|
color?: BadgeColor;
|
|
48
48
|
variant?: BadgeVariant;
|
|
49
49
|
size?: BadgeSize;
|
|
50
|
-
} &
|
|
50
|
+
} & class_variance_authority_types7.ClassProp) => string;
|
|
51
51
|
/**
|
|
52
52
|
* Badge component props interface
|
|
53
53
|
* @description Comprehensive props for the Badge component with accessibility and interactive features
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _intlayer_types305 from "@intlayer/types";
|
|
2
2
|
|
|
3
3
|
//#region src/components/Breadcrumb/breadcrumb.content.d.ts
|
|
4
4
|
declare const breadCrumbContent: {
|
|
5
5
|
key: string;
|
|
6
6
|
content: {
|
|
7
|
-
linkLabel:
|
|
7
|
+
linkLabel: _intlayer_types305.TypedNodeModel<_intlayer_types305.NodeType.Translation, {
|
|
8
8
|
en: string;
|
|
9
9
|
fr: string;
|
|
10
10
|
es: string;
|
|
@@ -23,7 +23,7 @@ declare const breadCrumbContent: {
|
|
|
23
23
|
id: string;
|
|
24
24
|
vi: string;
|
|
25
25
|
}, {
|
|
26
|
-
nodeType:
|
|
26
|
+
nodeType: "translation" | _intlayer_types305.NodeType.Translation;
|
|
27
27
|
} & {
|
|
28
28
|
translation: {
|
|
29
29
|
en: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LinkColor } from "../Link/Link.js";
|
|
2
2
|
import "../Link/index.js";
|
|
3
3
|
import { FC, HTMLAttributes, ReactNode } from "react";
|
|
4
|
-
import * as
|
|
4
|
+
import * as class_variance_authority_types6 from "class-variance-authority/types";
|
|
5
5
|
import { VariantProps } from "class-variance-authority";
|
|
6
6
|
import { LocalesValues } from "@intlayer/types";
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ import { LocalesValues } from "@intlayer/types";
|
|
|
12
12
|
declare const breadcrumbVariants: (props?: {
|
|
13
13
|
size?: "small" | "medium" | "large";
|
|
14
14
|
spacing?: "compact" | "normal" | "loose";
|
|
15
|
-
} &
|
|
15
|
+
} & class_variance_authority_types6.ClassProp) => string;
|
|
16
16
|
/**
|
|
17
17
|
* Detailed breadcrumb link configuration with optional href or onClick
|
|
18
18
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/components/Breadcrumb/index.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAoDM,0BAgKgC;EAhKhC,IAAA,CAAA,EAAA,OAAA,GAAA,QAiBJ,GAAA,OAAA;EA2HG,OAAA,CAAA,EAAA,SAAA,GAAA,QAAsB,GAAA,OAAA;AAoB3B,CAAA,GA/IE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../../src/components/Breadcrumb/index.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAoDM,0BAgKgC;EAhKhC,IAAA,CAAA,EAAA,OAAA,GAAA,QAiBJ,GAAA,OAAA;EA2HG,OAAA,CAAA,EAAA,SAAA,GAAA,QAAsB,GAAA,OAAA;AAoB3B,CAAA,GA/IE,+BAAA,CAAA,SA+IoC,EAAA,GAAsB,MAAA;AAE5D;;;KAtBK,sBAAA,GA+BoB;EAId;;;EAyBP,IAAA,CAAA,EAAA,MAAA;EACa;;;EA4BJ,IAAA,EAAA,MAAA;;;;;;;;;;;KArED,cAAA,YAA0B;KAE1B,eAAA;;;;SAIH;;;;;UAKC,eAAe;;;;WAId;;;;;;;;;;cAUG;;;;;;;;;;;;;;;IAeV,oBAAoB,sBACtB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4BJ,YAAY,GAAG"}
|