├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── add-label.yaml │ ├── cd.yaml │ ├── ci.yaml │ ├── pr-closed.yaml │ ├── tag-with-comment.yaml │ └── update-changelog.yaml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .storybook ├── decorators.tsx ├── main.ts ├── preview.ts └── public │ └── mockServiceWorker.js ├── .stylelintrc.json ├── .swcrc ├── .vscode └── settings.json ├── CHANGELOG-archive-201911.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── chromatic.config.json ├── codecov.yml ├── examples ├── nextjs-app │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── favicon.ico │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── template.tsx │ ├── lib │ │ ├── registry.tsx │ │ └── theme.tsx │ ├── next.config.mjs │ ├── package.json │ └── tsconfig.json └── nextjs-pages │ ├── .gitignore │ ├── README.md │ ├── next.config.mjs │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx │ ├── public │ └── favicon.ico │ └── tsconfig.json ├── global.d.ts ├── jest-setup.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── ab-experiments │ ├── README.md │ ├── package.json │ ├── src │ │ ├── google-optimize-context │ │ │ ├── README.md │ │ │ ├── context.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ └── triple-ab-experiment-context │ │ │ ├── README.md │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ └── service.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── constants │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── regex.test.ts │ │ └── regex.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── fetcher │ ├── README.md │ ├── package.json │ ├── src │ │ ├── add-fetchers-to-gssp.test.ts │ │ ├── add-fetchers-to-gssp.ts │ │ ├── auth-guarded-methods.ts │ │ ├── factories.test.ts │ │ ├── factories.ts │ │ ├── fetcher.ts │ │ ├── index.ts │ │ ├── make-request-params.test.ts │ │ ├── make-request-params.ts │ │ ├── methods.ts │ │ ├── response-handler.ts │ │ ├── safe-parse-json.test.ts │ │ ├── safe-parse-json.ts │ │ ├── server-fetch.ts │ │ ├── session-refresh.ts │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── i18n │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── interpolate.ts │ │ ├── locales │ │ │ ├── en.ts │ │ │ ├── index.ts │ │ │ ├── ja.ts │ │ │ ├── ko.ts │ │ │ └── zh-TW.ts │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── intersection-observer │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lazy-loaded-intersection-observer.tsx │ │ ├── static-intersection-observer.tsx │ │ └── use-intersection.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── meta-tags │ ├── README.md │ ├── package.json │ ├── src │ │ ├── app-router │ │ │ ├── generate-apple-smart-banner-meta.ts │ │ │ ├── generate-common-meta.ts │ │ │ ├── generate-essential-content-meta.ts │ │ │ ├── generate-facebook-app-link-meta.ts │ │ │ ├── generate-facebook-open-graph-meta.ts │ │ │ ├── generate-triple-default-meta.ts │ │ │ └── index.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── pages-router │ │ │ ├── apple-smart-banner-meta.tsx │ │ │ ├── common-meta.tsx │ │ │ ├── essential-content-meta.test.tsx │ │ │ ├── essential-content-meta.tsx │ │ │ ├── facebook-app-link-meta.tsx │ │ │ ├── facebook-open-graph-meta.tsx │ │ │ ├── index.ts │ │ │ └── theme-color-meta.tsx │ │ ├── structured-data │ │ │ ├── article-script.tsx │ │ │ ├── breadcrumb-list-script.tsx │ │ │ ├── discussion-forum-posting-script.tsx │ │ │ ├── index.ts │ │ │ ├── local-business-script.tsx │ │ │ ├── product-script.tsx │ │ │ ├── qa-page-script.tsx │ │ │ └── review-script.tsx │ │ ├── types │ │ │ ├── index.ts │ │ │ ├── schema.ts │ │ │ └── structured-data-script-props.ts │ │ └── utils.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── middlewares │ ├── README.md │ ├── package.json │ ├── src │ │ ├── chain.ts │ │ ├── index.ts │ │ ├── refresh-session │ │ │ ├── index.ts │ │ │ ├── next-13.ts │ │ │ └── next-14.ts │ │ ├── set-web-device-id.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── apply-set-cookie.ts │ │ │ ├── get-domain.ts │ │ │ └── get-triple-app.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── react-hooks │ ├── README.md │ ├── package.json │ ├── src │ │ ├── hooks.stories.tsx │ │ ├── index.ts │ │ ├── mocks │ │ │ └── lottie.sample.json │ │ ├── use-body-scroll-lock.ts │ │ ├── use-debounce.test.ts │ │ ├── use-debounce.ts │ │ ├── use-error-handler.ts │ │ ├── use-fetch.ts │ │ ├── use-interval.test.ts │ │ ├── use-interval.ts │ │ ├── use-local-storage.test.ts │ │ ├── use-local-storage.ts │ │ ├── use-lottie.tsx │ │ ├── use-scroll-to-anchor.ts │ │ ├── use-scroll-to-element.ts │ │ ├── use-session-storage.test.ts │ │ ├── use-session-storage.ts │ │ └── use-visibility-change.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── router │ ├── README.md │ ├── package.json │ ├── src │ │ ├── common │ │ │ ├── add-web-url-base.test.tsx │ │ │ ├── add-web-url-base.ts │ │ │ ├── default-router.ts │ │ │ ├── disabled-link-notifier.test.tsx │ │ │ ├── disabled-link-notifier.ts │ │ │ ├── router-guarded-link.test.tsx │ │ │ ├── router-guarded-link.tsx │ │ │ ├── target.ts │ │ │ ├── types.ts │ │ │ ├── use-rel.test.ts │ │ │ └── use-rel.ts │ │ ├── external │ │ │ ├── hook.ts │ │ │ ├── href-handler.ts │ │ │ ├── index.ts │ │ │ ├── link.tsx │ │ │ └── utils.ts │ │ ├── href-to-props │ │ │ ├── index.ts │ │ │ ├── use-href-to-props.test.tsx │ │ │ └── use-href-to-props.ts │ │ ├── index.ts │ │ ├── links │ │ │ ├── index.ts │ │ │ ├── use-make-inlink.ts │ │ │ ├── use-make-outlink.ts │ │ │ ├── use-open-inlink.test.ts │ │ │ ├── use-open-inlink.ts │ │ │ ├── use-open-native-link.test.tsx │ │ │ ├── use-open-native-link.ts │ │ │ ├── use-open-outlink.test.ts │ │ │ └── use-open-outlink.ts │ │ ├── local │ │ │ ├── base-path.test.ts │ │ │ ├── base-path.ts │ │ │ ├── hook.ts │ │ │ ├── href-handler.ts │ │ │ ├── index.ts │ │ │ └── link.tsx │ │ └── navigate │ │ │ ├── canonization.spec.ts │ │ │ ├── canonization.ts │ │ │ ├── index.ts │ │ │ ├── use-isomorphic-navigate.ts │ │ │ ├── use-navigate.test.tsx │ │ │ └── use-navigate.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── scroll-to-element │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── scroll-position-calculators.test.ts │ │ ├── scroll-position-calculators.ts │ │ ├── scroll-to-element.ts │ │ ├── scroll.ts │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── standard-action-handler │ ├── README.md │ ├── package.json │ ├── src │ │ ├── converse.tsx │ │ ├── copy-to-clipboard.ts │ │ ├── fetch-api.ts │ │ ├── handler.ts │ │ ├── hook.ts │ │ ├── image-download.ts │ │ ├── index.ts │ │ ├── initialize.ts │ │ ├── invoke-cta.ts │ │ ├── new-window.test.ts │ │ ├── new-window.ts │ │ ├── require-triple-client.tsx │ │ ├── scroll-to-element.ts │ │ ├── serial.ts │ │ ├── services │ │ │ ├── copy.ts │ │ │ └── share.ts │ │ ├── share.ts │ │ ├── show-toast.ts │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── tds-theme │ ├── package.json │ ├── src │ │ ├── foundations │ │ │ └── colors.ts │ │ ├── global-style.ts │ │ ├── index.ts │ │ ├── styled.ts │ │ └── theme │ │ │ ├── default.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── tds-ui │ ├── package.json │ ├── src │ │ ├── commons.ts │ │ ├── components │ │ │ ├── accordion │ │ │ │ ├── accordion-content.tsx │ │ │ │ ├── accordion-context.tsx │ │ │ │ ├── accordion-folded.tsx │ │ │ │ ├── accordion-title.tsx │ │ │ │ ├── accordion.stories.tsx │ │ │ │ ├── accordion.tsx │ │ │ │ └── index.ts │ │ │ ├── action-sheet-select │ │ │ │ ├── action-sheet-select-button.tsx │ │ │ │ ├── action-sheet-select-context.tsx │ │ │ │ ├── action-sheet-select-option.tsx │ │ │ │ ├── action-sheet-select-options.tsx │ │ │ │ ├── action-sheet-select.stories.tsx │ │ │ │ ├── action-sheet-select.tsx │ │ │ │ └── index.ts │ │ │ ├── action-sheet │ │ │ │ ├── action-sheet-body.tsx │ │ │ │ ├── action-sheet-context.tsx │ │ │ │ ├── action-sheet-item.tsx │ │ │ │ ├── action-sheet-overlay.tsx │ │ │ │ ├── action-sheet-title.tsx │ │ │ │ ├── action-sheet.stories.tsx │ │ │ │ ├── action-sheet.test.tsx │ │ │ │ ├── action-sheet.tsx │ │ │ │ ├── constants.tsx │ │ │ │ └── index.ts │ │ │ ├── alert │ │ │ │ ├── alert.stories.tsx │ │ │ │ ├── alert.tsx │ │ │ │ └── index.ts │ │ │ ├── button │ │ │ │ ├── basic-button.tsx │ │ │ │ ├── button-base.test.tsx │ │ │ │ ├── button-base.tsx │ │ │ │ ├── button-container.ts │ │ │ │ ├── button-group.ts │ │ │ │ ├── button-icon.tsx │ │ │ │ ├── button.stories.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── icon-button.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── normal-button.tsx │ │ │ │ └── types.ts │ │ │ ├── carousel │ │ │ │ ├── carousel-item.tsx │ │ │ │ ├── carousel.stories.tsx │ │ │ │ ├── carousel.tsx │ │ │ │ ├── index.ts │ │ │ │ └── mocks │ │ │ │ │ └── carousel.sample.json │ │ │ ├── checkbox-group │ │ │ │ ├── checkbox-group-context.tsx │ │ │ │ ├── checkbox-group-error.tsx │ │ │ │ ├── checkbox-group-help.tsx │ │ │ │ ├── checkbox-group-label.tsx │ │ │ │ ├── checkbox-group.stories.tsx │ │ │ │ ├── checkbox-group.tsx │ │ │ │ ├── index.ts │ │ │ │ └── use-checkbox-group.tsx │ │ │ ├── checkbox │ │ │ │ ├── checkbox-base.tsx │ │ │ │ ├── checkbox.stories.tsx │ │ │ │ ├── checkbox.tsx │ │ │ │ └── index.ts │ │ │ ├── confirm-selector │ │ │ │ ├── confirm-selector-base.tsx │ │ │ │ ├── confirm-selector.stories.tsx │ │ │ │ ├── confirm-selector.tsx │ │ │ │ └── index.ts │ │ │ ├── confirm │ │ │ │ ├── confirm.stories.tsx │ │ │ │ ├── confirm.tsx │ │ │ │ └── index.ts │ │ │ ├── container │ │ │ │ ├── container.stories.tsx │ │ │ │ ├── container.test.tsx │ │ │ │ ├── container.tsx │ │ │ │ └── index.ts │ │ │ ├── content-elements │ │ │ │ ├── content-elements.tsx │ │ │ │ └── index.ts │ │ │ ├── drawer-button │ │ │ │ ├── drawer-button.stories.tsx │ │ │ │ ├── drawer-button.tsx │ │ │ │ └── index.ts │ │ │ ├── drawer │ │ │ │ ├── drawer.stories.tsx │ │ │ │ ├── drawer.tsx │ │ │ │ └── index.ts │ │ │ ├── fieldset │ │ │ │ ├── fieldset-context.tsx │ │ │ │ ├── fieldset-legend.tsx │ │ │ │ ├── fieldset.stories.tsx │ │ │ │ ├── fieldset.tsx │ │ │ │ ├── index.ts │ │ │ │ └── use-fieldset.tsx │ │ │ ├── flex-box │ │ │ │ ├── flex-box.stories.tsx │ │ │ │ ├── flex-box.test.tsx │ │ │ │ ├── flex-box.tsx │ │ │ │ └── index.ts │ │ │ ├── form-field │ │ │ │ ├── form-field-context.tsx │ │ │ │ ├── form-field-error.tsx │ │ │ │ ├── form-field-help.tsx │ │ │ │ ├── form-field-label.tsx │ │ │ │ ├── form-field.stories.tsx │ │ │ │ ├── form-field.tsx │ │ │ │ ├── index.ts │ │ │ │ └── use-form-field-state.ts │ │ │ ├── gender-selector │ │ │ │ ├── gender-selector-item.tsx │ │ │ │ ├── gender-selector.stories.tsx │ │ │ │ ├── gender-selector.tsx │ │ │ │ └── index.ts │ │ │ ├── hr │ │ │ │ ├── hr.ts │ │ │ │ └── index.ts │ │ │ ├── icon │ │ │ │ ├── icon.ts │ │ │ │ └── index.ts │ │ │ ├── image │ │ │ │ ├── README.md │ │ │ │ ├── circular.ts │ │ │ │ ├── context.tsx │ │ │ │ ├── fixed-dimensions-frame.tsx │ │ │ │ ├── fixed-ratio-frame.tsx │ │ │ │ ├── image.stories.tsx │ │ │ │ ├── image.tsx │ │ │ │ ├── img.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── link-indicator.tsx │ │ │ │ ├── optimized-img.tsx │ │ │ │ ├── overlay.tsx │ │ │ │ ├── placeholder.tsx │ │ │ │ └── source-url.tsx │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ ├── input.stories.tsx │ │ │ │ └── input.tsx │ │ │ ├── label │ │ │ │ ├── index.ts │ │ │ │ ├── label.stories.tsx │ │ │ │ └── label.tsx │ │ │ ├── list │ │ │ │ ├── index.ts │ │ │ │ ├── list.stories.tsx │ │ │ │ ├── list.test.tsx │ │ │ │ └── list.tsx │ │ │ ├── long-clickable │ │ │ │ ├── index.ts │ │ │ │ ├── long-clickable.stories.tsx │ │ │ │ └── long-clickable.tsx │ │ │ ├── modal │ │ │ │ ├── index.ts │ │ │ │ ├── modal-action.tsx │ │ │ │ ├── modal-actions.tsx │ │ │ │ ├── modal-body.tsx │ │ │ │ ├── modal-context.tsx │ │ │ │ ├── modal-description.tsx │ │ │ │ ├── modal-title.tsx │ │ │ │ ├── modal.stories.tsx │ │ │ │ ├── modal.test.tsx │ │ │ │ └── modal.tsx │ │ │ ├── navbar │ │ │ │ ├── index.ts │ │ │ │ ├── navbar.stories.tsx │ │ │ │ ├── navbar.tsx │ │ │ │ ├── search-navbar.stories.tsx │ │ │ │ └── search-navbar.tsx │ │ │ ├── numeric-spinner │ │ │ │ ├── index.ts │ │ │ │ ├── numeric-spinner-base.tsx │ │ │ │ ├── numeric-spinner.stories.tsx │ │ │ │ └── numeric-spinner.tsx │ │ │ ├── popup │ │ │ │ ├── index.ts │ │ │ │ ├── popup.stories.tsx │ │ │ │ ├── popup.test.tsx │ │ │ │ └── popup.tsx │ │ │ ├── radio-group │ │ │ │ ├── index.ts │ │ │ │ ├── radio-group-context.tsx │ │ │ │ ├── radio-group-error.tsx │ │ │ │ ├── radio-group-help.tsx │ │ │ │ ├── radio-group-label.tsx │ │ │ │ ├── radio-group.stories.tsx │ │ │ │ ├── radio-group.tsx │ │ │ │ └── use-radio-group.tsx │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ ├── radio-base.tsx │ │ │ │ ├── radio.stories.tsx │ │ │ │ └── radio.tsx │ │ │ ├── rating │ │ │ │ ├── index.ts │ │ │ │ ├── rating.stories.tsx │ │ │ │ └── rating.tsx │ │ │ ├── responsive │ │ │ │ ├── index.ts │ │ │ │ ├── responsive.test.tsx │ │ │ │ └── responsive.tsx │ │ │ ├── section │ │ │ │ ├── index.ts │ │ │ │ ├── section.stories.tsx │ │ │ │ ├── section.test.tsx │ │ │ │ └── section.tsx │ │ │ ├── segment │ │ │ │ ├── index.ts │ │ │ │ ├── segment.stories.tsx │ │ │ │ └── segment.tsx │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ ├── select.stories.tsx │ │ │ │ └── select.tsx │ │ │ ├── skeleton │ │ │ │ ├── index.ts │ │ │ │ ├── skeleton.stories.tsx │ │ │ │ └── skeleton.tsx │ │ │ ├── slider │ │ │ │ ├── handle.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── range-slider.stories.tsx │ │ │ │ ├── range-slider.tsx │ │ │ │ ├── single-slider.stories.tsx │ │ │ │ ├── single-slider.tsx │ │ │ │ ├── slider-base.tsx │ │ │ │ ├── track.tsx │ │ │ │ └── types.ts │ │ │ ├── spinner │ │ │ │ ├── index.ts │ │ │ │ ├── rolling-spinner.stories.tsx │ │ │ │ ├── rolling-spinner.tsx │ │ │ │ ├── spinner.stories.tsx │ │ │ │ └── spinner.tsx │ │ │ ├── stack │ │ │ │ ├── index.ts │ │ │ │ ├── stack.stories.tsx │ │ │ │ ├── stack.test.tsx │ │ │ │ └── stack.tsx │ │ │ ├── sticky-header │ │ │ │ ├── index.ts │ │ │ │ ├── sticky-header.stories.tsx │ │ │ │ ├── sticky-header.test.tsx │ │ │ │ └── sticky-header.tsx │ │ │ ├── table │ │ │ │ ├── index.ts │ │ │ │ ├── table.stories.tsx │ │ │ │ └── table.tsx │ │ │ ├── tabs │ │ │ │ ├── basic-tab-list.tsx │ │ │ │ ├── basic-tab.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── pointing-tab-context.tsx │ │ │ │ ├── pointing-tab-list.tsx │ │ │ │ ├── pointing-tab.tsx │ │ │ │ ├── rounded-tab-list.tsx │ │ │ │ ├── rounded-tab.tsx │ │ │ │ ├── tab-base.tsx │ │ │ │ ├── tab-list-base.tsx │ │ │ │ ├── tab-list.tsx │ │ │ │ ├── tab-panel.tsx │ │ │ │ ├── tab.tsx │ │ │ │ ├── tabs-context.tsx │ │ │ │ ├── tabs.stories.tsx │ │ │ │ ├── tabs.test.tsx │ │ │ │ ├── tabs.tsx │ │ │ │ └── types.ts │ │ │ ├── tag │ │ │ │ ├── index.ts │ │ │ │ ├── tag.stories.tsx │ │ │ │ └── tag.tsx │ │ │ ├── text │ │ │ │ ├── index.ts │ │ │ │ ├── text.stories.tsx │ │ │ │ ├── text.test.tsx │ │ │ │ ├── text.tsx │ │ │ │ └── typography.tsx │ │ │ ├── textarea │ │ │ │ ├── index.ts │ │ │ │ ├── textarea.stories.tsx │ │ │ │ └── textarea.tsx │ │ │ ├── tooltip │ │ │ │ ├── index.ts │ │ │ │ ├── tooltip.stories.tsx │ │ │ │ └── tooltip.tsx │ │ │ ├── video │ │ │ │ ├── context.tsx │ │ │ │ ├── controls.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── mute-unmute-button.tsx │ │ │ │ ├── play-pause-button.tsx │ │ │ │ ├── seeker.tsx │ │ │ │ ├── sources.tsx │ │ │ │ ├── use-video-control.ts │ │ │ │ ├── use-video-ref.ts │ │ │ │ ├── utils.tsx │ │ │ │ ├── video-element.tsx │ │ │ │ ├── video-frame.tsx │ │ │ │ ├── video.stories.tsx │ │ │ │ └── video.tsx │ │ │ └── visually-hidden │ │ │ │ ├── index.ts │ │ │ │ ├── visually-hidden.test.tsx │ │ │ │ └── visually-hidden.tsx │ │ ├── index.ts │ │ ├── mixins │ │ │ ├── border-radius.ts │ │ │ ├── box.ts │ │ │ ├── centered.ts │ │ │ ├── clearing.ts │ │ │ ├── ellipsis.ts │ │ │ ├── horizontal-scroll.ts │ │ │ ├── index.ts │ │ │ ├── layering.ts │ │ │ ├── margin-padding.ts │ │ │ ├── max-lines.ts │ │ │ ├── positioning.ts │ │ │ ├── safe-area.ts │ │ │ └── text-style.ts │ │ └── utils │ │ │ ├── merge-refs.ts │ │ │ ├── should-forward-prop.ts │ │ │ └── unit.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── tds-widget │ ├── .eslintignore │ ├── .prettierignore │ ├── .stylelintignore │ ├── package.json │ ├── src │ │ ├── ad-banners │ │ │ ├── api.ts │ │ │ ├── content-details-banner.tsx │ │ │ ├── horizontal-entity.tsx │ │ │ ├── horizontal-list-view.tsx │ │ │ ├── index.ts │ │ │ ├── list-section.ts │ │ │ ├── list-top-banners.tsx │ │ │ ├── typing.ts │ │ │ ├── vertical-entity.tsx │ │ │ └── vertical-list-view.tsx │ │ ├── app-banner │ │ │ ├── app-banner.stories.tsx │ │ │ ├── app-banner.tsx │ │ │ └── index.ts │ │ ├── app-installation-cta │ │ │ ├── article-card-cta.tsx │ │ │ ├── banner-cta.stories.tsx │ │ │ ├── banner-cta.tsx │ │ │ ├── chatbot-cta.tsx │ │ │ ├── constants.ts │ │ │ ├── elements.tsx │ │ │ ├── floating-button-cta.stories.tsx │ │ │ ├── floating-button-cta.tsx │ │ │ ├── image-banner.stories.tsx │ │ │ ├── image-banner.tsx │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── service.ts │ │ │ ├── text-banner.stories.tsx │ │ │ └── text-banner.tsx │ │ ├── author │ │ │ ├── author-intro.tsx │ │ │ ├── author.stories.tsx │ │ │ ├── author.tsx │ │ │ └── index.ts │ │ ├── booking-completion │ │ │ ├── booking-completion.stories.tsx │ │ │ └── index.tsx │ │ ├── chat │ │ │ ├── bubble-container │ │ │ │ ├── bubble-container.stories.tsx │ │ │ │ ├── bubble-container.tsx │ │ │ │ ├── bubble-info.tsx │ │ │ │ ├── elements.tsx │ │ │ │ ├── icons.tsx │ │ │ │ └── index.ts │ │ │ ├── bubble │ │ │ │ ├── altered.tsx │ │ │ │ ├── bubble-ui.tsx │ │ │ │ ├── bubble.stories.tsx │ │ │ │ ├── bubble.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── elements.tsx │ │ │ │ ├── full-text-message-view.tsx │ │ │ │ ├── image.tsx │ │ │ │ ├── images.stories.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── item │ │ │ │ │ ├── image.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── text.tsx │ │ │ │ ├── nol │ │ │ │ │ ├── full-text-view │ │ │ │ │ │ ├── elements.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── styles.ts │ │ │ │ ├── parent │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── parent-message.tsx │ │ │ │ │ ├── parent-ui.tsx │ │ │ │ │ └── parent.stories.tsx │ │ │ │ ├── product.tsx │ │ │ │ ├── rich.tsx │ │ │ │ ├── text.tsx │ │ │ │ └── type.ts │ │ │ ├── chat │ │ │ │ ├── chat-room-messages │ │ │ │ │ ├── chat-api-service.ts │ │ │ │ │ ├── chat-message-context.tsx │ │ │ │ │ ├── chat-room-messages-provider.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── messages.tsx │ │ │ │ │ ├── use-chat-room-messages.ts │ │ │ │ │ ├── use-scroll.tsx │ │ │ │ │ └── use-unread-messages.ts │ │ │ │ ├── chat-scroll-container.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── messages-reducer.ts │ │ │ │ ├── room-context.tsx │ │ │ │ └── scroll-context.tsx │ │ │ ├── expired │ │ │ │ ├── elements.tsx │ │ │ │ ├── expired.stories.tsx │ │ │ │ ├── expired.tsx │ │ │ │ └── index.ts │ │ │ ├── icons │ │ │ │ ├── ExclamationMarkIcon.tsx │ │ │ │ ├── arrow-bottom-16-icon.tsx │ │ │ │ ├── arrow-right-icon.tsx │ │ │ │ ├── arrow-top-icon.tsx │ │ │ │ ├── reply-meesage-icon.tsx │ │ │ │ ├── reply-message-icon.tsx │ │ │ │ ├── select-photo-icon.tsx │ │ │ │ ├── send-icon.tsx │ │ │ │ ├── talk-icon.tsx │ │ │ │ └── text-full-view-arrow-icon.tsx │ │ │ ├── index.ts │ │ │ ├── input-area │ │ │ │ ├── index.tsx │ │ │ │ ├── input-area-ui │ │ │ │ │ ├── elements.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── input-area.stories.tsx │ │ │ │ ├── nol-input-area-ui │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── types.ts │ │ │ │ │ └── use-input-resize-observer.ts │ │ │ │ └── utils.ts │ │ │ ├── list │ │ │ │ ├── hooks.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reducer.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── messages │ │ │ │ ├── date-divider.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── messages.stories.tsx │ │ │ │ ├── type.ts │ │ │ │ └── utils.ts │ │ │ ├── navbar │ │ │ │ ├── index.tsx │ │ │ │ └── navbar.stories.tsx │ │ │ ├── nol-theme-provider │ │ │ │ ├── constants.ts │ │ │ │ ├── converter.ts │ │ │ │ ├── index.ts │ │ │ │ └── nol-theme-provider.tsx │ │ │ ├── preview │ │ │ │ ├── elements.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── preview.stories.tsx │ │ │ │ ├── preview.tsx │ │ │ │ └── utils.ts │ │ │ ├── reservation-info │ │ │ │ ├── elements.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── reservation-info.stories.tsx │ │ │ │ └── reservation-info.tsx │ │ │ ├── scroll-buttons-area │ │ │ │ ├── elements.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── scroll-buttons.tsx │ │ │ ├── types │ │ │ │ ├── base.ts │ │ │ │ ├── image.ts │ │ │ │ ├── index.ts │ │ │ │ ├── message.ts │ │ │ │ ├── room.ts │ │ │ │ ├── ui.ts │ │ │ │ ├── unread.ts │ │ │ │ └── user.ts │ │ │ └── utils │ │ │ │ ├── a-tag-navigator.ts │ │ │ │ ├── image.ts │ │ │ │ ├── index.ts │ │ │ │ ├── profile.ts │ │ │ │ └── user.ts │ │ ├── content-sharing │ │ │ ├── content-sharing.stories.tsx │ │ │ ├── content-sharing.tsx │ │ │ └── index.ts │ │ ├── date-picker │ │ │ ├── constants.ts │ │ │ ├── date-styles.stories.tsx │ │ │ ├── day-picker.stories.tsx │ │ │ ├── day-picker.tsx │ │ │ ├── index.ts │ │ │ ├── mixins.ts │ │ │ ├── picker-frame.ts │ │ │ ├── range-picker-v2.stories.tsx │ │ │ ├── range-picker-v2 │ │ │ │ ├── index.tsx │ │ │ │ ├── picker-frame.ts │ │ │ │ └── range-picker.tsx │ │ │ ├── range-picker.stories.tsx │ │ │ ├── range-picker.test.tsx │ │ │ ├── range-picker.tsx │ │ │ ├── service.ts │ │ │ ├── use-disabled-days.ts │ │ │ ├── use-public-holidays.ts │ │ │ └── utils.ts │ │ ├── directions-finder │ │ │ ├── ask-to-the-local.tsx │ │ │ ├── constants.ts │ │ │ ├── direction-buttons.tsx │ │ │ ├── directions-finder.stories.tsx │ │ │ └── index.ts │ │ ├── flicking-carousel │ │ │ ├── arrow-icon.tsx │ │ │ ├── flicking-carousel.stories.tsx │ │ │ ├── flicking-carousel.tsx │ │ │ ├── index.ts │ │ │ └── mocks │ │ │ │ └── carousel.sample.json │ │ ├── footer │ │ │ ├── award-footer.stories.tsx │ │ │ ├── award-footer.tsx │ │ │ ├── button-area │ │ │ │ ├── button.tsx │ │ │ │ ├── dropdown.tsx │ │ │ │ └── index.tsx │ │ │ ├── company-info.tsx │ │ │ ├── constants.ts │ │ │ ├── default-footer.tsx │ │ │ ├── extra-link-group.tsx │ │ │ ├── footer.json │ │ │ ├── footer.stories.tsx │ │ │ ├── index.ts │ │ │ ├── link-group.tsx │ │ │ ├── logo-footer.stories.tsx │ │ │ ├── logo-footer.tsx │ │ │ ├── type.ts │ │ │ └── use-footer-info.tsx │ │ ├── hub-form │ │ │ ├── cell.tsx │ │ │ ├── cta.tsx │ │ │ ├── hub-form.stories.tsx │ │ │ ├── hub-form.tsx │ │ │ └── index.ts │ │ ├── image-carousel │ │ │ ├── carousel.tsx │ │ │ ├── content.tsx │ │ │ ├── image-carousel.stories.tsx │ │ │ ├── image-carousel.tsx │ │ │ ├── image-content.tsx │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ ├── image-carousel.sample.json │ │ │ │ └── video-carousel.sample.json │ │ │ ├── page-label.tsx │ │ │ ├── types.ts │ │ │ └── video-content.tsx │ │ ├── image-source │ │ │ ├── image-source.tsx │ │ │ └── index.ts │ │ ├── image-viewer │ │ │ ├── detail-viewer │ │ │ │ ├── image.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── video.tsx │ │ │ ├── image-viewer.stories.tsx │ │ │ ├── image-viewer.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── listing-filter │ │ │ ├── index.ts │ │ │ ├── listing-filter-expanding-filter-entry.stories.tsx │ │ │ ├── listing-filter-filter-entry.stories.tsx │ │ │ ├── listing-filter-primary-filter-entry.stories.tsx │ │ │ ├── listing-filter.stories.tsx │ │ │ └── listing-filter.tsx │ │ ├── location-properties │ │ │ ├── index.tsx │ │ │ ├── location-properties.stories.tsx │ │ │ ├── location-properties.tsx │ │ │ └── property-item.tsx │ │ ├── map │ │ │ ├── focus-tracker.tsx │ │ │ ├── index.test.tsx │ │ │ ├── index.ts │ │ │ ├── map-view.tsx │ │ │ ├── map.stories.tsx │ │ │ ├── mock.ts │ │ │ ├── mocks │ │ │ │ └── hotel-recommandations.json │ │ │ ├── overlay │ │ │ │ ├── index.ts │ │ │ │ ├── markers │ │ │ │ │ ├── flexible-marker.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── poi-dot-marker.tsx │ │ │ │ │ └── primary-marker │ │ │ │ │ │ ├── bubble-marker.tsx │ │ │ │ │ │ ├── circle-marker │ │ │ │ │ │ ├── circle-marker-base.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── dot-marker.tsx │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── pin-marker │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── pin-marker.tsx │ │ │ │ ├── polygon.tsx │ │ │ │ └── polyline.tsx │ │ │ ├── types.ts │ │ │ └── utilities.ts │ │ ├── media │ │ │ ├── index.ts │ │ │ └── media.tsx │ │ ├── nearby-pois │ │ │ ├── index.ts │ │ │ ├── nearby-pois.stories.tsx │ │ │ ├── nearby-pois.tsx │ │ │ ├── poi-entry.tsx │ │ │ ├── reducer.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ │ ├── poi-detail │ │ │ ├── actions │ │ │ │ ├── actions.stories.tsx │ │ │ │ ├── actions.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tooltip │ │ │ │ │ └── tooltip.tsx │ │ │ ├── area-names.tsx │ │ │ ├── constants.ts │ │ │ ├── copy-action-sheet-item.tsx │ │ │ ├── copy-action-sheet.tsx │ │ │ ├── detail-header-v2.stories.tsx │ │ │ ├── detail-header-v2 │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ ├── detail-header.stories.tsx │ │ │ ├── detail-header │ │ │ │ ├── business-hours-icons.tsx │ │ │ │ ├── business-hours-note.tsx │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ ├── image-carousel.stories.tsx │ │ │ ├── image-carousel │ │ │ │ ├── carousel-section.tsx │ │ │ │ ├── carousel.tsx │ │ │ │ ├── cta-overlay.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── note.tsx │ │ │ │ └── placeholder.tsx │ │ │ ├── images-provider.tsx │ │ │ ├── images-reducer.ts │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ ├── inventory-item.json │ │ │ │ └── recommended-articles.json │ │ │ ├── recommended-articles.stories.tsx │ │ │ ├── recommended-articles │ │ │ │ ├── api-client.ts │ │ │ │ ├── article-entry.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── more-button.tsx │ │ │ │ ├── recommended-articles.tsx │ │ │ │ └── types.ts │ │ │ ├── types.ts │ │ │ └── use-fetch-images.tsx │ │ ├── poi-list-elements │ │ │ ├── carousel-element.tsx │ │ │ ├── compact-poi-list-element.tsx │ │ │ ├── constants.ts │ │ │ ├── extended-poi-list-element.tsx │ │ │ ├── get-type-names.ts │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ ├── hotels.sample.json │ │ │ │ └── pois.sample.json │ │ │ ├── poi-card-element.stories.tsx │ │ │ ├── poi-card-element │ │ │ │ ├── direction-button.tsx │ │ │ │ ├── index.ts │ │ │ │ └── poi-card-element.tsx │ │ │ ├── poi-carousel-element.stories.tsx │ │ │ ├── poi-list-element.stories.tsx │ │ │ ├── poi-list-element.tsx │ │ │ └── types.ts │ │ ├── pricing │ │ │ ├── fixed-pricing-v2.stories.tsx │ │ │ ├── fixed-pricing-v2 │ │ │ │ ├── index.tsx │ │ │ │ ├── purchase-button-loading-indicator.tsx │ │ │ │ └── purchase-button.tsx │ │ │ ├── fixed-pricing.tsx │ │ │ ├── index.ts │ │ │ ├── pricing.stories.tsx │ │ │ └── pricing.tsx │ │ ├── public-header │ │ │ ├── categories.ts │ │ │ ├── constants.ts │ │ │ ├── extra-action-item.tsx │ │ │ ├── extra-action-separator.tsx │ │ │ ├── extra-actions-container.tsx │ │ │ ├── header-menu-button.tsx │ │ │ ├── index.ts │ │ │ ├── public-header-deeplink.tsx │ │ │ ├── public-header.spec.tsx │ │ │ ├── public-header.stories.tsx │ │ │ ├── public-header.tsx │ │ │ ├── side-menu │ │ │ │ ├── auth-button.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── menu-list.tsx │ │ │ │ ├── overlay.tsx │ │ │ │ ├── profile.tsx │ │ │ │ └── type.ts │ │ │ ├── types.ts │ │ │ ├── use-auto-hide.ts │ │ │ └── use-deeplink-href.ts │ │ ├── recommended-contents │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ └── recommended-contents.sample.json │ │ │ ├── recommended-contents.stories.tsx │ │ │ └── recommended-contents.tsx │ │ ├── replies │ │ │ ├── auto-resizing-textarea.tsx │ │ │ ├── context.tsx │ │ │ ├── guide-text.tsx │ │ │ ├── hook.tsx │ │ │ ├── index.ts │ │ │ ├── list │ │ │ │ ├── index.tsx │ │ │ │ ├── not-exist-replies.tsx │ │ │ │ └── reply.tsx │ │ │ ├── register.test.tsx │ │ │ ├── register.tsx │ │ │ ├── replies-api-client.test.ts │ │ │ ├── replies-api-client.ts │ │ │ ├── replies.stories.tsx │ │ │ ├── replies.tsx │ │ │ ├── reply-tree-manipulators.test.tsx │ │ │ ├── reply-tree-manipulators.ts │ │ │ ├── reply.test.tsx │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── resource-list-elements │ │ │ ├── extended-resource-list-element.stories.tsx │ │ │ ├── extended-resource-list-element.tsx │ │ │ ├── index.ts │ │ │ ├── resource-list-element-stats.stories.tsx │ │ │ ├── resource-list-element-stats.tsx │ │ │ ├── review-scrap-stat.stories.tsx │ │ │ └── review-scrap-stat.tsx │ │ ├── review │ │ │ ├── components │ │ │ │ ├── filter-context.tsx │ │ │ │ ├── filter.tsx │ │ │ │ ├── full-list-button.tsx │ │ │ │ ├── infinite-list │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── infinite-list.tsx │ │ │ │ │ ├── latest-reviews-infinite.tsx │ │ │ │ │ ├── popular-reviews-infinite.tsx │ │ │ │ │ ├── rating-infinite-list.tsx │ │ │ │ │ ├── services.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── mileage-button.tsx │ │ │ │ ├── my-review-action-sheet.tsx │ │ │ │ ├── others-review-action-sheet.tsx │ │ │ │ ├── review-element │ │ │ │ │ ├── badges.tsx │ │ │ │ │ ├── comment.tsx │ │ │ │ │ ├── foldable-comment.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── media │ │ │ │ │ │ ├── compare-media.ts │ │ │ │ │ │ ├── elements.ts │ │ │ │ │ │ ├── image.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── media-wrapper.tsx │ │ │ │ │ │ ├── medium.tsx │ │ │ │ │ │ └── video.tsx │ │ │ │ │ ├── pinned-message.tsx │ │ │ │ │ ├── purchaseInfo.tsx │ │ │ │ │ └── user.tsx │ │ │ │ ├── review-placeholder-with-rating.tsx │ │ │ │ ├── reviews-shorten.tsx │ │ │ │ ├── reviews.tsx │ │ │ │ ├── shorten-list │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── latest-reviews.tsx │ │ │ │ │ ├── popular-reviews.tsx │ │ │ │ │ ├── rating-reviews.tsx │ │ │ │ │ ├── reviews-list.tsx │ │ │ │ │ ├── services.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── sorting-context.tsx │ │ │ │ ├── sorting-options-action-sheet.tsx │ │ │ │ ├── sorting-options.tsx │ │ │ │ ├── types.tsx │ │ │ │ └── write-button.tsx │ │ │ ├── constants.ts │ │ │ ├── data │ │ │ │ └── graphql │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── generated.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── mutation.graphql │ │ │ │ │ └── query.graphql │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ ├── review-element.duo-images.json │ │ │ │ ├── review-element.duo-videos.json │ │ │ │ ├── review-element.mono-image.json │ │ │ │ ├── review-element.mono-video.json │ │ │ │ ├── review-element.more-images.json │ │ │ │ ├── review-element.more-vidoes.json │ │ │ │ ├── review-element.penta-images.json │ │ │ │ ├── review-element.quad-images.json │ │ │ │ ├── review-element.tri-images.json │ │ │ │ ├── review-element.tri-videos.json │ │ │ │ └── reviews.ts │ │ │ ├── review-element.stories.tsx │ │ │ ├── reviews-placeholder.stories.tsx │ │ │ ├── reviews-shorten.stories.tsx │ │ │ ├── reviews.stories.tsx │ │ │ ├── services │ │ │ │ ├── index.ts │ │ │ │ ├── use-client-actions.tsx │ │ │ │ └── use-reviews.ts │ │ │ └── utils.ts │ │ ├── scrap-button │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── outline-scrap-button.stories.tsx │ │ │ ├── outline-scrap-button.tsx │ │ │ ├── overlay-scrap-button.stories.tsx │ │ │ ├── overlay-scrap-button.tsx │ │ │ ├── scrap-button-mask.test.tsx │ │ │ ├── scrap-button-mask.tsx │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── scrap │ │ │ ├── constants.ts │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── provider.tsx │ │ │ ├── reducer.ts │ │ │ ├── services.ts │ │ │ ├── types.ts │ │ │ └── use-scrap.ts │ │ ├── search │ │ │ ├── index.ts │ │ │ ├── search.stories.tsx │ │ │ └── search.tsx │ │ ├── social-reviews │ │ │ ├── external-links.tsx │ │ │ ├── index.ts │ │ │ ├── social-review.tsx │ │ │ └── social-reviews.stories.tsx │ │ ├── static-map │ │ │ ├── index.ts │ │ │ ├── static-map.stories.tsx │ │ │ └── static-map.tsx │ │ └── user-verification │ │ │ ├── confirmation-services.test.ts │ │ │ ├── confirmation-services.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-user-verification.test.ts │ │ │ ├── use-user-verification.ts │ │ │ ├── verification-request.stories.tsx │ │ │ ├── verification-request.tsx │ │ │ ├── verified-message.spec.tsx │ │ │ └── verified-message.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-document │ ├── README.md │ ├── package.json │ ├── src │ │ ├── animation.stories.tsx │ │ ├── elements │ │ │ ├── anchor.tsx │ │ │ ├── animation.tsx │ │ │ ├── coupon │ │ │ │ ├── coupon-download-buttons.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── modals.tsx │ │ │ │ ├── utils.test.ts │ │ │ │ └── utils.ts │ │ │ ├── embedded.tsx │ │ │ ├── external-video.tsx │ │ │ ├── images.tsx │ │ │ ├── index.ts │ │ │ ├── itinerary.tsx │ │ │ ├── itinerary │ │ │ │ ├── badge.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── itinerary-map.tsx │ │ │ │ ├── poi-card.tsx │ │ │ │ ├── save-to-itinerary.tsx │ │ │ │ ├── tag-label.ts │ │ │ │ ├── types.ts │ │ │ │ ├── use-computed-itineraries.ts │ │ │ │ ├── use-computed-map.ts │ │ │ │ ├── use-handle-add-pois-to-trip.ts │ │ │ │ ├── use-safety-poi.ts │ │ │ │ └── with-type-circle-badge.tsx │ │ │ ├── links.tsx │ │ │ ├── list.tsx │ │ │ ├── note.tsx │ │ │ ├── pois.tsx │ │ │ ├── regions.tsx │ │ │ ├── shared │ │ │ │ ├── display-containers.tsx │ │ │ │ ├── document-carousel.tsx │ │ │ │ ├── generate-click-handler.ts │ │ │ │ └── resource-list.tsx │ │ │ ├── sticky-tabs.tsx │ │ │ ├── table.tsx │ │ │ ├── text │ │ │ │ ├── headings.tsx │ │ │ │ ├── index.ts │ │ │ │ └── plain.tsx │ │ │ └── tna │ │ │ │ ├── index.tsx │ │ │ │ ├── price-policy-coupon-info.tsx │ │ │ │ ├── product.tsx │ │ │ │ ├── slot.tsx │ │ │ │ ├── types.ts │ │ │ │ └── use-generate-coupon.ts │ │ ├── heading.stories.tsx │ │ ├── hr.stories.tsx │ │ ├── images.stories.tsx │ │ ├── index.ts │ │ ├── links.stories.tsx │ │ ├── list.stories.tsx │ │ ├── mocks │ │ │ ├── hotel.sample.json │ │ │ ├── images-frame.sample.json │ │ │ ├── images.sample.json │ │ │ ├── pois.sample.json │ │ │ ├── slots.sample.json │ │ │ ├── triple-document.embedded.json │ │ │ ├── triple-document.itinerary.json │ │ │ ├── triple-document.regions.json │ │ │ └── triple-document.sample.json │ │ ├── pois.stories.tsx │ │ ├── prop-context │ │ │ ├── deep-link.ts │ │ │ ├── guest-mode.ts │ │ │ ├── image-click-handler.ts │ │ │ ├── image-source.ts │ │ │ ├── link-click-handler.ts │ │ │ ├── media-config.tsx │ │ │ └── resource-click-handler.ts │ │ ├── tna-slot.stories.tsx │ │ ├── triple-document.stories.tsx │ │ ├── triple-document.tsx │ │ ├── types.ts │ │ └── use-resource-event-tracker.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-email-document │ ├── package.json │ ├── src │ │ ├── common │ │ │ ├── box.ts │ │ │ ├── fluid-table.tsx │ │ │ ├── handlebars-anchor.tsx │ │ │ ├── index.ts │ │ │ └── reset.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── preview.test.tsx │ │ │ └── preview.tsx │ │ ├── elements │ │ │ ├── dividers.test.tsx │ │ │ ├── dividers.tsx │ │ │ ├── embedded.test.tsx │ │ │ ├── embedded.tsx │ │ │ ├── heading.test.tsx │ │ │ ├── heading.tsx │ │ │ ├── images.test.tsx │ │ │ ├── images.tsx │ │ │ ├── index.ts │ │ │ ├── links.test.tsx │ │ │ ├── links.tsx │ │ │ ├── note.test.tsx │ │ │ ├── note.tsx │ │ │ ├── text.test.tsx │ │ │ └── text.tsx │ │ ├── embedded.stories.tsx │ │ ├── full-email-template.tsx │ │ ├── heading.stories.tsx │ │ ├── hr.stories.tsx │ │ ├── images.stories.tsx │ │ ├── index.ts │ │ ├── links.stories.tsx │ │ ├── note.stories.tsx │ │ ├── preview.stories.tsx │ │ ├── text.stories.tsx │ │ ├── triple-email-document.test.tsx │ │ └── triple-email-document.tsx │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-fallback-action │ ├── README.md │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── index.ts │ │ ├── triple-fallback-action-remover.tsx │ │ ├── triple-fallback-action.test.tsx │ │ ├── triple-fallback-action.tsx │ │ └── use-triple-fallback-action-remover.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-header │ ├── package.json │ ├── src │ │ ├── frame │ │ │ ├── common.ts │ │ │ ├── effects │ │ │ │ ├── common.ts │ │ │ │ ├── flying.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── rotate.tsx │ │ │ │ ├── types.ts │ │ │ │ └── zoom.tsx │ │ │ ├── frame.tsx │ │ │ ├── image.tsx │ │ │ ├── index.ts │ │ │ └── text.tsx │ │ ├── index.ts │ │ ├── layer │ │ │ ├── index.ts │ │ │ ├── layer.tsx │ │ │ └── transitions │ │ │ │ ├── fade-in-out.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── marquee.tsx │ │ │ │ ├── rolling.tsx │ │ │ │ └── slide.tsx │ │ ├── lottie │ │ │ ├── index.ts │ │ │ ├── lottie.tsx │ │ │ └── use-lottie.ts │ │ ├── mocks │ │ │ ├── framer-type.sample.json │ │ │ └── lottie-type.sample.json │ │ ├── motion-container.ts │ │ ├── service.ts │ │ ├── triple-header.stories.tsx │ │ ├── triple-header.tsx │ │ └── types.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-web-nextjs-pages │ ├── package.json │ ├── src │ │ ├── helpers │ │ │ ├── client-app.ts │ │ │ ├── session.ts │ │ │ └── user-agent.ts │ │ ├── index.ts │ │ ├── initializers │ │ │ ├── client-app.ts │ │ │ ├── index.ts │ │ │ ├── session.ts │ │ │ └── user-agent.ts │ │ ├── providers │ │ │ ├── app-install-cta-modal-provider.mdx │ │ │ ├── app-install-cta-modal-provider.tsx │ │ │ ├── event-metadata-provider.mdx │ │ │ ├── event-metadata-provider.tsx │ │ │ ├── event-tracking-provider.mdx │ │ │ ├── event-tracking-provider.tsx │ │ │ ├── index.ts │ │ │ ├── login-cta-modal-provider.mdx │ │ │ ├── login-cta-modal-provider.tsx │ │ │ ├── triple-web.mdx │ │ │ └── triple-web.tsx │ │ └── ssr-utils │ │ │ ├── auth-guard.test.ts │ │ │ ├── auth-guard.ts │ │ │ ├── get-client-app.ts │ │ │ ├── get-session-availability.ts │ │ │ ├── get-user-agent.ts │ │ │ ├── index.ts │ │ │ └── put-invalid-session-id-remover.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-web-nextjs │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── initializers │ │ │ ├── client-app.ts │ │ │ ├── index.ts │ │ │ ├── session.ts │ │ │ └── user-agent.ts │ │ └── providers │ │ │ ├── app-install-cta-modal-provider.mdx │ │ │ ├── app-install-cta-modal-provider.tsx │ │ │ ├── event-metadata-provider.mdx │ │ │ ├── event-metadata-provider.tsx │ │ │ ├── event-tracking-provider.mdx │ │ │ ├── event-tracking-provider.tsx │ │ │ ├── index.ts │ │ │ ├── login-cta-modal-provider.mdx │ │ │ ├── login-cta-modal-provider.tsx │ │ │ ├── triple-web.mdx │ │ │ └── triple-web.tsx │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-web-test-utils │ ├── package.json │ ├── src │ │ ├── create-test-wrapper.tsx │ │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-web-utils │ ├── package.json │ ├── src │ │ ├── check-client-app.test.ts │ │ ├── check-client-app.ts │ │ ├── index.ts │ │ ├── regex.ts │ │ ├── user-agent.ts │ │ └── user.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── triple-web │ ├── README.md │ ├── package.json │ ├── src │ │ ├── client-app │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-client-app-actions.mdx │ │ │ ├── use-client-app-actions.test.tsx │ │ │ ├── use-client-app-actions.ts │ │ │ ├── use-client-app-callback.mdx │ │ │ ├── use-client-app-callback.test.tsx │ │ │ ├── use-client-app-callback.ts │ │ │ ├── use-client-app.mdx │ │ │ ├── use-client-app.test.tsx │ │ │ ├── use-client-app.ts │ │ │ ├── use-feature-flag.mdx │ │ │ ├── use-feature-flag.test.tsx │ │ │ └── use-feature-flag.ts │ │ ├── env │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-env.mdx │ │ │ ├── use-env.test.tsx │ │ │ └── use-env.ts │ │ ├── event-tracking │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── libs │ │ │ │ └── firebase-analytics.ts │ │ │ ├── types.ts │ │ │ ├── use-set-firebase-user-id.mdx │ │ │ ├── use-set-firebase-user-id.ts │ │ │ ├── use-track-event-with-metadata.mdx │ │ │ ├── use-track-event-with-metadata.ts │ │ │ ├── use-track-event.mdx │ │ │ ├── use-track-event.ts │ │ │ ├── use-track-screen.mdx │ │ │ ├── use-track-screen.ts │ │ │ ├── use-triple-web-device-id.ts │ │ │ ├── use-utm.mdx │ │ │ ├── use-utm.ts │ │ │ └── utils │ │ │ │ ├── track-event.ts │ │ │ │ └── track-screen.ts │ │ ├── hash-router │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ ├── use-hash-router.mdx │ │ │ └── use-hash-router.ts │ │ ├── i18n │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-i18n.mdx │ │ │ ├── use-i18n.test.tsx │ │ │ ├── use-i18n.ts │ │ │ ├── use-translation.mdx │ │ │ ├── use-translation.test.tsx │ │ │ └── use-translation.ts │ │ ├── index.ts │ │ ├── modal │ │ │ ├── app-install-cta-modal-context.ts │ │ │ ├── components │ │ │ │ ├── app-install-cta-modal.tsx │ │ │ │ └── login-cta-modal.tsx │ │ │ ├── constants.ts │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ ├── login-cta-modal-context.ts │ │ │ ├── types.ts │ │ │ ├── use-app-install-cta-modal.mdx │ │ │ ├── use-app-install-cta-modal.ts │ │ │ ├── use-login-cta-modal.mdx │ │ │ └── use-login-cta-modal.ts │ │ ├── providers │ │ │ ├── app-install-cta-modal-provider.tsx │ │ │ ├── event-metadata-provider.tsx │ │ │ ├── event-tracking-provider.tsx │ │ │ ├── index.ts │ │ │ ├── login-cta-modal-provider.tsx │ │ │ └── triple-web.tsx │ │ ├── session │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ ├── types.tsx │ │ │ ├── use-login.mdx │ │ │ ├── use-login.ts │ │ │ ├── use-logout.mdx │ │ │ ├── use-logout.ts │ │ │ ├── use-session-availability.mdx │ │ │ ├── use-session-availability.test.tsx │ │ │ ├── use-session-availability.ts │ │ │ ├── use-session-callback.mdx │ │ │ ├── use-session-callback.test.tsx │ │ │ ├── use-session-callback.ts │ │ │ ├── use-session.mdx │ │ │ ├── use-session.test.tsx │ │ │ ├── use-session.ts │ │ │ └── utils │ │ │ │ ├── redirect.spec.ts │ │ │ │ └── redirect.ts │ │ └── user-agent │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-user-agent.mdx │ │ │ ├── use-user-agent.test.tsx │ │ │ └── use-user-agent.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── type-definitions │ ├── README.md │ ├── package.json │ ├── src │ │ ├── geojson.ts │ │ ├── image.ts │ │ ├── index.ts │ │ ├── inventory-item.ts │ │ ├── listing-poi.ts │ │ ├── translated-property.ts │ │ └── triple-document.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts └── view-utilities │ ├── README.md │ ├── package.json │ ├── src │ ├── debounce.ts │ ├── derive-current-state-and-count.ts │ ├── find-folded-position.ts │ ├── format-number.spec.ts │ ├── format-number.ts │ ├── generate-deep-link │ │ ├── README.md │ │ ├── index.ts │ │ ├── make-deep-link-generator.test.ts │ │ ├── make-deep-link-generator.ts │ │ ├── param-injectors.test.ts │ │ └── param-injectors.ts │ ├── generate-share-image-url.ts │ ├── index.ts │ ├── measure-distance.spec.ts │ ├── measure-distance.ts │ ├── normalize-query-keys │ │ ├── index.test.ts │ │ └── index.ts │ ├── routelist │ │ ├── index.ts │ │ ├── routelist.spec.ts │ │ └── routelist.ts │ ├── strict-query │ │ ├── index.test.ts │ │ └── index.ts │ ├── timestamp.ts │ ├── url.spec.ts │ └── url.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.mts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── scripts └── changelog.js ├── stories └── introduction.mdx ├── tsconfig.json ├── tsconfig.test.json └── vite.config.mts /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production] 2 | iOS >= 13.4, last 3 Safari versions, > 0.5% in KR, not dead 3 | 4 | [development] 5 | last 1 chrome version 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*.{js,ts,tsx}] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.{json,yml,yaml}] 14 | charset = utf-8 15 | end_of_line = lf 16 | indent_style = space 17 | indent_size = 2 18 | insert_final_newline = true 19 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type import('eslint').Linter.Config */ 2 | module.exports = { 3 | root: true, 4 | extends: [ 5 | '@titicaca/eslint-config-triple', 6 | '@titicaca/eslint-config-triple/frontend', 7 | '@titicaca/eslint-config-triple/prettier', 8 | 'plugin:storybook/recommended', 9 | ], 10 | overrides: [ 11 | { 12 | files: ['*.test.*', '*.spec.*'], 13 | extends: [ 14 | 'plugin:jest/style', 15 | 'plugin:jest/recommended', 16 | 'plugin:jest-dom/recommended', 17 | 'plugin:testing-library/react', 18 | ], 19 | }, 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @titicacadev/frontend 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm exec lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | "@titicaca/prettier-config-triple" 2 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@titicaca/stylelint-config-triple"] 3 | } 4 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/swcrc", 3 | "module": { 4 | "type": "commonjs" 5 | }, 6 | "jsc": { 7 | "parser": { 8 | "syntax": "typescript", 9 | "tsx": true 10 | }, 11 | "transform": { 12 | "react": { 13 | "runtime": "automatic" 14 | } 15 | }, 16 | "experimental": { 17 | "plugins": [["@swc/plugin-styled-components", {}]] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [{ "mode": "auto" }] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Triple Frontend 2 | 3 | [![codecov](https://codecov.io/gh/titicacadev/triple-frontend/branch/main/graph/badge.svg?token=B1ME2OJD68)](https://codecov.io/gh/titicacadev/triple-frontend) 4 | 5 | 트리플 프론트엔드 공용 컴포넌트 및 라이브러리입니다. 6 | 7 | ## 문서 8 | 9 | [Triple Frontend Storybook](https://storybook.triple-corp.com)에서 컴포넌트 문서와 예시를 볼 수 있습니다. 10 | 11 | ## 기여하기 12 | 13 | [CONTRIBUTING.md](./CONTRIBUTING.md)를 참고해주세요. 14 | 15 | ## 관련 서비스 16 | 17 | - [트리플](https://triple.guide) 18 | - [TRIPLE Korea](https://triple.global) 19 | 20 | ## 라이선스 21 | 22 | [MIT License](./LICENSE) 23 | -------------------------------------------------------------------------------- /chromatic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "Project:617b7c4431c922004a42c7cc", 3 | "onlyChanged": true, 4 | "zip": true 5 | } 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 10% 7 | patch: off 8 | -------------------------------------------------------------------------------- /examples/nextjs-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/nextjs-app/README.md: -------------------------------------------------------------------------------- 1 | # Next.js App Example 2 | 3 | `triple-frontend` 모노레포 패키지를 로컬 개발 환경에서 테스트하고 통합하기 위한 실험용 프로젝트입니다. `triple-frontend` 패키지가 Next.js App Router 환경에서 어떻게 작동하는지 탐구할 수 있도록 간단한 설정을 제공합니다. 4 | -------------------------------------------------------------------------------- /examples/nextjs-app/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/titicacadev/triple-frontend/1c8f67c41cdb2f57020480af1f22e0feb319a708/examples/nextjs-app/app/favicon.ico -------------------------------------------------------------------------------- /examples/nextjs-app/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs-app/lib/theme.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ThemeProvider as StyledThemeProvider } from 'styled-components' 4 | import { defaultTheme, GlobalStyle } from '@titicaca/tds-theme' 5 | import { PropsWithChildren } from 'react' 6 | 7 | export default function ThemeProvider({ children }: PropsWithChildren) { 8 | return ( 9 | 10 | 11 | {children} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /examples/nextjs-app/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | compiler: { 4 | styledComponents: true, 5 | }, 6 | } 7 | 8 | export default nextConfig 9 | -------------------------------------------------------------------------------- /examples/nextjs-pages/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/nextjs-pages/README.md: -------------------------------------------------------------------------------- 1 | # Next.js Pages Example 2 | 3 | `triple-frontend` 모노레포 패키지를 로컬 개발 환경에서 테스트하고 통합하기 위한 실험용 프로젝트입니다. `triple-frontend` 패키지가 Next.js Pages Router 환경에서 어떻게 작동하는지 탐구할 수 있도록 간단한 설정을 제공합니다. 4 | -------------------------------------------------------------------------------- /examples/nextjs-pages/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | compiler: { 5 | styledComponents: true, 6 | }, 7 | transpilePackages: [ 8 | '@titicaca/tds-theme', 9 | '@titicaca/tds-ui', 10 | '@titicaca/triple-web', 11 | '@titicaca/triple-web-nextjs-pages', 12 | ], 13 | } 14 | 15 | export default nextConfig 16 | -------------------------------------------------------------------------------- /examples/nextjs-pages/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/nextjs-pages/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs-pages/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/titicacadev/triple-frontend/1c8f67c41cdb2f57020480af1f22e0feb319a708/examples/nextjs-pages/public/favicon.ico -------------------------------------------------------------------------------- /examples/nextjs-pages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true 15 | }, 16 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import 'jest-styled-components' 3 | -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import 'jest-styled-components' 3 | 4 | import { TextDecoder, TextEncoder } from 'util' 5 | 6 | global.TextDecoder = TextDecoder 7 | global.TextEncoder = TextEncoder 8 | 9 | class ResizeObserver { 10 | observe() {} 11 | 12 | unobserve() {} 13 | 14 | disconnect() {} 15 | } 16 | 17 | global.ResizeObserver = ResizeObserver 18 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "npmClient": "pnpm", 4 | "version": "14.0.13" 5 | } 6 | -------------------------------------------------------------------------------- /packages/ab-experiments/README.md: -------------------------------------------------------------------------------- 1 | # ab-experiments 2 | 3 | A/B 테스트를 지원하는 패키지입니다. 4 | 5 | ## 인터페이스 종류 6 | 7 | ### 1. triple-ab-experiment-context 8 | 9 | **트리플의 사용자 ID(내부 API를 이용)**를 이용하여 A/B 테스트를 도와주는 context입니다. 10 | 11 | ### 2. google-optimize-context 12 | 13 | **구글 옵티마이즈**를 이용하여 세션을 통해 A/B 테스트를 도와주는 context입니다. 14 | -------------------------------------------------------------------------------- /packages/ab-experiments/src/google-optimize-context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context' 2 | -------------------------------------------------------------------------------- /packages/ab-experiments/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './google-optimize-context' 2 | export * from './triple-ab-experiment-context' 3 | -------------------------------------------------------------------------------- /packages/ab-experiments/src/triple-ab-experiment-context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context' 2 | export * from './service' 3 | -------------------------------------------------------------------------------- /packages/ab-experiments/src/triple-ab-experiment-context/service.ts: -------------------------------------------------------------------------------- 1 | import { authGuardedFetchers, RequestOptions } from '@titicaca/fetcher' 2 | 3 | export interface TripleABExperimentMeta { 4 | testId: number 5 | group: string 6 | } 7 | 8 | export async function getTripleABExperiment( 9 | slug: string, 10 | options?: RequestOptions, 11 | ) { 12 | return authGuardedFetchers.get( 13 | `/api/abtest/${slug}`, 14 | options, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/ab-experiments/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmitOnError": true, 7 | "outDir": "./lib" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": [ 11 | "node_modules", 12 | "lib", 13 | "src/**/*.test.*", 14 | "src/**/*.spec.*", 15 | "src/**/*.stories.*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/ab-experiments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/ab-experiments/vite.config.mts: -------------------------------------------------------------------------------- 1 | export { default } from '../../vite.config.mjs' 2 | -------------------------------------------------------------------------------- /packages/constants/README.md: -------------------------------------------------------------------------------- 1 | # `@titicaca/constants` 2 | 3 | 트리플 프론트엔드의 공통 상수를 모아놓는 패키지입니다. 4 | 5 | - 공통 상수 6 | - 공통 정규 표현식 7 | -------------------------------------------------------------------------------- /packages/constants/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './regex' 2 | 3 | /** API 호출 시 필요한 사용자 세션 HTTP 헤더 키 */ 4 | export const SESSION_KEY = 'x-soto-session' 5 | export const TP_TK = 'TP_TK' 6 | export const TP_SE = 'TP_SE' 7 | export const X_TRIPLE_WEB_DEVICE_ID = 'x-triple-web-device-id' 8 | -------------------------------------------------------------------------------- /packages/constants/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmitOnError": true, 7 | "outDir": "./lib" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": [ 11 | "node_modules", 12 | "lib", 13 | "src/**/*.test.*", 14 | "src/**/*.spec.*", 15 | "src/**/*.stories.*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/constants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/constants/vite.config.mts: -------------------------------------------------------------------------------- 1 | export { default } from '../../vite.config.mjs' 2 | -------------------------------------------------------------------------------- /packages/fetcher/src/safe-parse-json.test.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'node-fetch' 2 | 3 | import safeParseJson from './safe-parse-json' 4 | 5 | it('JSON 파싱 에러를 조용히 넘깁니다.', async () => { 6 | const response = new Response('', { 7 | headers: { 8 | 'content-type': 'application/json', 9 | }, 10 | }) 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | const json = await safeParseJson(response as any) 14 | expect(json).toBeUndefined() 15 | }) 16 | -------------------------------------------------------------------------------- /packages/fetcher/src/safe-parse-json.ts: -------------------------------------------------------------------------------- 1 | export default async function safeParseJson( 2 | response: Response, 3 | ): Promise { 4 | try { 5 | const json = await response.json() 6 | return json 7 | } catch { 8 | return undefined 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/fetcher/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmitOnError": true, 7 | "outDir": "./lib" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": [ 11 | "node_modules", 12 | "lib", 13 | "src/**/*.test.*", 14 | "src/**/*.spec.*", 15 | "src/**/*.stories.*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/fetcher/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/fetcher/vite.config.mts: -------------------------------------------------------------------------------- 1 | export { default } from '../../vite.config.mjs' 2 | -------------------------------------------------------------------------------- /packages/i18n/src/index.ts: -------------------------------------------------------------------------------- 1 | export { locales } from './locales' 2 | export { interpolate } from './interpolate' 3 | export * from './types' 4 | -------------------------------------------------------------------------------- /packages/i18n/src/interpolate.ts: -------------------------------------------------------------------------------- 1 | export const interpolate = ( 2 | text: string, 3 | values: Record = {}, 4 | ) => { 5 | return text.replace(/{{(\w+)}}/g, (match, key) => { 6 | return values[key] !== undefined ? values[key] : match 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /packages/i18n/src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en' 2 | import ja from './ja' 3 | import ko from './ko' 4 | import zhTw from './zh-TW' 5 | 6 | export const locales = { en, ja, ko, zhTw } 7 | -------------------------------------------------------------------------------- /packages/i18n/src/types.ts: -------------------------------------------------------------------------------- 1 | import ko from './locales/ko' 2 | 3 | export type I18nKeys = typeof ko 4 | -------------------------------------------------------------------------------- /packages/i18n/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmitOnError": true, 7 | "outDir": "./lib" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": [ 11 | "node_modules", 12 | "lib", 13 | "src/**/*.test.*", 14 | "src/**/*.spec.*", 15 | "src/**/*.stories.*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/i18n/vite.config.mts: -------------------------------------------------------------------------------- 1 | export { default } from '../../vite.config.mjs' 2 | -------------------------------------------------------------------------------- /packages/intersection-observer/README.md: -------------------------------------------------------------------------------- 1 | # intersection-observer 2 | 3 | intersection 관련 컴포넌트 및 Hook을 제공합니다. 4 | 5 | ## Usage 6 | 7 | ### use-intersection 8 | 9 | 기본 사용법은 아래와 같이 useIntersection의 사용하고자 하는 타입 ex) `HTMLDivElement`지정 그리고 `threshold, rootMargin` 값을 Optional 하게 사용하여 목적에 맞는 ref 를 생성 후 `isIntersecting`를 확인하고자 하는 태그에 해당 ref를 설정합니다. 10 | 11 | ```js 12 | import { useIntersection } from '@titicaca/intersection-observer'; 13 | 14 | const { ref, isIntersecting } = useIntersection({ threshold, rootMargin } 15 | 16 | return ( 17 |
18 | ) 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/intersection-observer/src/index.ts: -------------------------------------------------------------------------------- 1 | import IntersectionObserver from './lazy-loaded-intersection-observer' 2 | 3 | export { default as StaticIntersectionObserver } from './static-intersection-observer' 4 | export { default as useIntersection } from './use-intersection' 5 | export default IntersectionObserver 6 | -------------------------------------------------------------------------------- /packages/intersection-observer/src/static-intersection-observer.tsx: -------------------------------------------------------------------------------- 1 | import 'intersection-observer' 2 | import ReactIntersectionObserver from '@titicaca/react-intersection-observer' 3 | 4 | export default ReactIntersectionObserver 5 | -------------------------------------------------------------------------------- /packages/intersection-observer/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmitOnError": true, 7 | "outDir": "./lib" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": [ 11 | "node_modules", 12 | "lib", 13 | "src/**/*.test.*", 14 | "src/**/*.spec.*", 15 | "src/**/*.stories.*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/intersection-observer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["node_modules", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/intersection-observer/vite.config.mts: -------------------------------------------------------------------------------- 1 | export { default } from '../../vite.config.mjs' 2 | -------------------------------------------------------------------------------- /packages/meta-tags/src/app-router/generate-apple-smart-banner-meta.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | 3 | import { DEFAULT_APP_ID } from '../constants' 4 | 5 | export function generateAppleSmartBannerMeta({ 6 | appId = DEFAULT_APP_ID, 7 | appPath = '/', 8 | }: { 9 | appId?: string 10 | appPath?: string 11 | } = {}): Metadata { 12 | const appUrlScheme = process.env.NEXT_PUBLIC_APP_URL_SCHEME || '' 13 | 14 | return { 15 | itunes: { 16 | appId, 17 | appArgument: `${appUrlScheme}://${appPath}`, 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/meta-tags/src/app-router/generate-common-meta.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | 3 | export function generateCommonMeta(): Metadata { 4 | return { 5 | icons: { 6 | apple: 'https://triple.guide/icons/favicon-152x152.png', 7 | }, 8 | manifest: '/manifest.json', 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/meta-tags/src/app-router/generate-essential-content-meta.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next' 2 | 3 | export function generateEssentialContentMeta({ 4 | title, 5 | description, 6 | canonicalUrl, 7 | }: { 8 | title?: string 9 | description?: string 10 | canonicalUrl?: string 11 | } = {}): Metadata { 12 | return { 13 | title: title || process.env.NEXT_PUBLIC_DEFAULT_PAGE_TITLE || '', 14 | description: 15 | description || process.env.NEXT_PUBLIC_DEFAULT_PAGE_DESCRIPTION || '', 16 | alternates: { 17 | canonical: canonicalUrl, 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/meta-tags/src/app-router/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generate-common-meta' 2 | export * from './generate-apple-smart-banner-meta' 3 | export * from './generate-essential-content-meta' 4 | export * from './generate-facebook-app-link-meta' 5 | export * from './generate-facebook-open-graph-meta' 6 | export * from './generate-triple-default-meta' 7 | -------------------------------------------------------------------------------- /packages/meta-tags/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_APP_ID = '1225499481' 2 | export const DEFAULT_APP_PACKAGE_NAME = 'com.titicacacorp.triple' 3 | export const DEFAULT_THEME_COLOR = '#1FC1B6' 4 | export const DEFAULT_OG_IMAGE = { 5 | url: 'https://assets.triple.guide/images/og-tag-app_download@4x.png', 6 | width: 260, 7 | height: 260, 8 | } 9 | -------------------------------------------------------------------------------- /packages/meta-tags/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './app-router' 3 | export * from './pages-router' 4 | export * from './structured-data' 5 | -------------------------------------------------------------------------------- /packages/meta-tags/src/pages-router/apple-smart-banner-meta.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { useEnv } from '@titicaca/triple-web' 3 | 4 | import { DEFAULT_APP_ID } from '../constants' 5 | 6 | export function AppleSmartBannerMeta({ 7 | appId = DEFAULT_APP_ID, 8 | appPath = '/', 9 | }: { 10 | appId?: string 11 | appPath?: string 12 | }) { 13 | const { appUrlScheme } = useEnv() 14 | 15 | return ( 16 | 17 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/meta-tags/src/pages-router/index.ts: -------------------------------------------------------------------------------- 1 | export { EssentialContentMeta } from './essential-content-meta' 2 | export { CommonMeta } from './common-meta' 3 | export { AppleSmartBannerMeta } from './apple-smart-banner-meta' 4 | export { FacebookAppLinkMeta } from './facebook-app-link-meta' 5 | export { FacebookOpenGraphMeta } from './facebook-open-graph-meta' 6 | export { ThemeColorMeta } from './theme-color-meta' 7 | -------------------------------------------------------------------------------- /packages/meta-tags/src/pages-router/theme-color-meta.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | 3 | import { ThemeColor } from '../types' 4 | import { DEFAULT_THEME_COLOR } from '../constants' 5 | 6 | export function ThemeColorMeta({ 7 | content = DEFAULT_THEME_COLOR, 8 | }: { 9 | content?: ThemeColor | string 10 | }) { 11 | return ( 12 | 13 | 14 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/meta-tags/src/structured-data/article-script.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script' 2 | 3 | import { createScript } from '../utils' 4 | import { ArticleScriptProps } from '../types' 5 | 6 | export function ArticleScript(props: ArticleScriptProps) { 7 | const articleScript = createScript(props, 'Article') 8 | 9 | return ( 10 |