@asafarim/shared-i18n 0.8.1 → 0.9.0

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.
Files changed (94) hide show
  1. package/README.md +266 -350
  2. package/demo/README.md +119 -148
  3. package/demo/index.html +12 -1
  4. package/demo/node_modules/.bin/kill-port +17 -0
  5. package/demo/node_modules/.bin/tsc +5 -9
  6. package/demo/node_modules/.bin/tsserver +5 -9
  7. package/demo/node_modules/.bin/vite +5 -9
  8. package/demo/package.json +7 -4
  9. package/demo/public/404.html +24 -0
  10. package/demo/public/favicon.svg +4 -4
  11. package/demo/public/logo.svg +24 -24
  12. package/demo/src/App.tsx +178 -129
  13. package/demo/src/components/CountryLanguageSelectorsPage.tsx +240 -0
  14. package/demo/src/components/GetStartedSection.tsx +56 -56
  15. package/demo/src/components/KeyTable.tsx +29 -29
  16. package/demo/src/components/LanguageBar.tsx +145 -103
  17. package/demo/src/components/LanguageSwitcherDemo.module.css +114 -114
  18. package/demo/src/components/LanguageSwitchersPage.tsx +245 -0
  19. package/demo/src/components/Logo.tsx +6 -6
  20. package/demo/src/components/OverviewSection.tsx +58 -43
  21. package/demo/src/components/Panel.tsx +15 -15
  22. package/demo/src/components/RoutingLabPage.tsx +147 -0
  23. package/demo/src/components/StatusCard.tsx +109 -109
  24. package/demo/src/data/countries.ts +48 -0
  25. package/demo/src/i18n/localeAdapter.ts +91 -0
  26. package/demo/src/i18n/localeRouting.ts +77 -0
  27. package/demo/src/index.css +1075 -644
  28. package/demo/src/locales/de/demo.json +202 -84
  29. package/demo/src/locales/en/demo.json +201 -85
  30. package/demo/src/locales/fr/demo.json +203 -85
  31. package/demo/src/locales/it/demo.json +202 -84
  32. package/demo/src/locales/lb/demo.json +201 -0
  33. package/demo/src/locales/nl/demo.json +203 -85
  34. package/demo/src/main.tsx +32 -29
  35. package/demo/tsconfig.json +18 -18
  36. package/demo/tsconfig.node.json +10 -10
  37. package/demo/tsconfig.tsbuildinfo +1 -1
  38. package/demo/vite-env.d.ts +7 -7
  39. package/demo/vite.config.d.ts +2 -2
  40. package/demo/vite.config.js +10 -10
  41. package/dist/components/LanguageSwitcher.module.css +303 -303
  42. package/dist/country-language-selector.css +431 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +2 -0
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +8 -5
  48. package/demo/dist/Icon Dropdown_Limited Languages.png +0 -0
  49. package/demo/dist/Select Dropdown_Text Only.png +0 -0
  50. package/demo/dist/assets/favicon-BZYZvBLo.svg +0 -4
  51. package/demo/dist/assets/index-BdjqKw_N.css +0 -1
  52. package/demo/dist/assets/index-C1Tq1uEr.js +0 -191
  53. package/demo/dist/favicon.svg +0 -4
  54. package/demo/dist/index.html +0 -27
  55. package/demo/dist/logo.svg +0 -24
  56. package/demo/node_modules/.bin/browserslist +0 -21
  57. package/demo/node_modules/.bin/browserslist.CMD +0 -12
  58. package/demo/node_modules/.bin/browserslist.ps1 +0 -41
  59. package/demo/node_modules/.bin/tsc.CMD +0 -12
  60. package/demo/node_modules/.bin/tsc.ps1 +0 -41
  61. package/demo/node_modules/.bin/tsserver.CMD +0 -12
  62. package/demo/node_modules/.bin/tsserver.ps1 +0 -41
  63. package/demo/node_modules/.bin/vite.CMD +0 -12
  64. package/demo/node_modules/.bin/vite.ps1 +0 -41
  65. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js +0 -848
  66. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js.map +0 -7
  67. package/demo/node_modules/.vite/deps/_metadata.json +0 -76
  68. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
  69. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
  70. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js +0 -1004
  71. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js.map +0 -7
  72. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js +0 -292
  73. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js.map +0 -7
  74. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js +0 -280
  75. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js.map +0 -7
  76. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js +0 -400
  77. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js.map +0 -7
  78. package/demo/node_modules/.vite/deps/i18next.js +0 -2392
  79. package/demo/node_modules/.vite/deps/i18next.js.map +0 -7
  80. package/demo/node_modules/.vite/deps/package.json +0 -3
  81. package/demo/node_modules/.vite/deps/react-dom.js +0 -6
  82. package/demo/node_modules/.vite/deps/react-dom.js.map +0 -7
  83. package/demo/node_modules/.vite/deps/react-dom_client.js +0 -20217
  84. package/demo/node_modules/.vite/deps/react-dom_client.js.map +0 -7
  85. package/demo/node_modules/.vite/deps/react-i18next.js +0 -869
  86. package/demo/node_modules/.vite/deps/react-i18next.js.map +0 -7
  87. package/demo/node_modules/.vite/deps/react.js +0 -5
  88. package/demo/node_modules/.vite/deps/react.js.map +0 -7
  89. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js +0 -278
  90. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +0 -7
  91. package/demo/node_modules/.vite/deps/react_jsx-runtime.js +0 -6
  92. package/demo/node_modules/.vite/deps/react_jsx-runtime.js.map +0 -7
  93. package/demo/src/components/CountryLanguageDemo.tsx +0 -140
  94. package/demo/src/components/LanguageSwitcherDemo.tsx +0 -256
package/demo/src/App.tsx CHANGED
@@ -1,129 +1,178 @@
1
- import { useState } from 'react'
2
- import { useTranslation } from '@asafarim/shared-i18n'
3
- import LanguageBar from './components/LanguageBar'
4
- import Panel from './components/Panel'
5
- import KeyTable from './components/KeyTable'
6
- import StatusCard from './components/StatusCard'
7
- import OverviewSection from './components/OverviewSection'
8
- import GetStartedSection from './components/GetStartedSection'
9
- import LanguageSwitcherDemo from './components/LanguageSwitcherDemo'
10
- import CountryLanguageDemo from './components/CountryLanguageDemo'
11
- import Logo from './components/Logo'
12
-
13
- type TabType = 'overview' | 'getStarted' | 'demo' | 'countryDemo'
14
-
15
-
16
-
17
- export default function App() {
18
- const { t } = useTranslation('demo')
19
- const [activeTab, setActiveTab] = useState<TabType>('overview')
20
-
21
- return (
22
- <div className="app-container">
23
- <header className="app-header">
24
- <Logo />
25
- <div className="header-content">
26
- <h1>{t('title')}</h1>
27
- <p className="subtitle">{t('subtitle')}</p>
28
- </div>
29
- <LanguageBar />
30
- </header>
31
-
32
- <main className="app-main">
33
- <div className="nav-tabs">
34
- <button
35
- className={`nav-tab ${activeTab === 'overview' ? 'active' : ''}`}
36
- onClick={() => setActiveTab('overview')}
37
- >
38
- Overview
39
- </button>
40
- <button
41
- className={`nav-tab ${activeTab === 'getStarted' ? 'active' : ''}`}
42
- onClick={() => setActiveTab('getStarted')}
43
- >
44
- Get Started
45
- </button>
46
- <button
47
- className={`nav-tab ${activeTab === 'demo' ? 'active' : ''}`}
48
- onClick={() => setActiveTab('demo')}
49
- >
50
- Demo
51
- </button>
52
- <button
53
- className={`nav-tab ${activeTab === 'countryDemo' ? 'active' : ''}`}
54
- onClick={() => setActiveTab('countryDemo')}
55
- >
56
- Country Selector
57
- </button>
58
- </div>
59
-
60
- {activeTab === 'overview' && <OverviewSection />}
61
-
62
- {activeTab === 'getStarted' && <GetStartedSection />}
63
-
64
- {activeTab === 'demo' && (
65
- <>
66
- <Panel title="LanguageSwitcher Component">
67
- <LanguageSwitcherDemo />
68
- </Panel>
69
-
70
- <Panel title="Translations">
71
- <div className="panel-grid">
72
- <div>
73
- <h3>Common Namespace</h3>
74
- <KeyTable
75
- namespace="common"
76
- keys={['welcome', 'language', 'apps.appName.identity', 'apps.description.identity']}
77
- />
78
- </div>
79
- <div>
80
- <h3>Identity Portal Namespace</h3>
81
- <KeyTable
82
- namespace="identityPortal"
83
- keys={[
84
- 'navbar.admin-area',
85
- 'navbar.auth.signIn',
86
- 'navbar.auth.signOut',
87
- 'dashboard.title',
88
- 'dashboard.user-management.title'
89
- ]}
90
- />
91
- </div>
92
- </div>
93
- </Panel>
94
-
95
- <Panel title="Interpolation & Trans Component">
96
- <InterpolationDemo />
97
- </Panel>
98
-
99
- <StatusCard />
100
- </>
101
- )}
102
-
103
- {activeTab === 'countryDemo' && (
104
- <Panel title="Country / Language Selector Examples">
105
- <CountryLanguageDemo />
106
- </Panel>
107
- )}
108
- </main>
109
- </div>
110
- )
111
- }
112
-
113
- function InterpolationDemo() {
114
- const { t, i18n } = useTranslation('identityPortal')
115
-
116
- return (
117
- <div className="interpolation-demo">
118
- <div className="demo-item">
119
- <h4>navbar.auth.welcome with interpolation</h4>
120
- <p className="demo-output">
121
- {t('navbar.auth.welcome', { userName: 'Ali' })}
122
- </p>
123
- </div>
124
- <div className="demo-item">
125
- <h4>Current language: {i18n.language}</h4>
126
- </div>
127
- </div>
128
- )
129
- }
1
+ import { useCallback, useEffect, useMemo, useState } from 'react'
2
+ import { useTranslation } from '@asafarim/shared-i18n'
3
+ import type { Locale } from '@asafarim/shared-i18n'
4
+ import LanguageBar from './components/LanguageBar'
5
+ import OverviewSection from './components/OverviewSection'
6
+ import GetStartedSection from './components/GetStartedSection'
7
+ import LanguageSwitchersPage from './components/LanguageSwitchersPage'
8
+ import CountryLanguageSelectorsPage from './components/CountryLanguageSelectorsPage'
9
+ import RoutingLabPage from './components/RoutingLabPage'
10
+ import {
11
+ buildLocalizedPath,
12
+ parsePathname,
13
+ localeToSlug,
14
+ } from './i18n/localeRouting'
15
+ import type { DemoPage } from './i18n/localeRouting'
16
+
17
+ interface RouteState {
18
+ locale: Locale
19
+ page: DemoPage
20
+ }
21
+
22
+ function readRouteFromLocation(): RouteState & { needsRedirect: boolean } {
23
+ return parsePathname(window.location.pathname)
24
+ }
25
+
26
+ export default function App() {
27
+ const { i18n, t } = useTranslation('demo')
28
+
29
+ const [route, setRoute] = useState<RouteState>(() => {
30
+ const parsed = readRouteFromLocation()
31
+ return { locale: parsed.locale, page: parsed.page }
32
+ })
33
+
34
+ // Normalize URL on first paint and sync i18n language.
35
+ useEffect(() => {
36
+ const parsed = readRouteFromLocation()
37
+ const target = buildLocalizedPath(parsed.locale, parsed.page)
38
+ if (parsed.needsRedirect || window.location.pathname !== target) {
39
+ window.history.replaceState({}, '', target)
40
+ }
41
+ if (i18n?.changeLanguage && i18n.language !== parsed.locale.language) {
42
+ i18n.changeLanguage(parsed.locale.language)
43
+ }
44
+ // eslint-disable-next-line react-hooks/exhaustive-deps
45
+ }, [])
46
+
47
+ // Keep i18n language in sync with route locale.
48
+ useEffect(() => {
49
+ if (i18n?.changeLanguage && i18n.language !== route.locale.language) {
50
+ i18n.changeLanguage(route.locale.language)
51
+ }
52
+ }, [route.locale.language, i18n])
53
+
54
+ // Browser back/forward.
55
+ useEffect(() => {
56
+ const onPop = () => {
57
+ const parsed = readRouteFromLocation()
58
+ setRoute({ locale: parsed.locale, page: parsed.page })
59
+ }
60
+ window.addEventListener('popstate', onPop)
61
+ return () => window.removeEventListener('popstate', onPop)
62
+ }, [])
63
+
64
+ const navigate = useCallback((next: RouteState, replace = false) => {
65
+ const target = buildLocalizedPath(next.locale, next.page)
66
+ if (window.location.pathname !== target) {
67
+ if (replace) window.history.replaceState({}, '', target)
68
+ else window.history.pushState({}, '', target)
69
+ }
70
+ setRoute(next)
71
+ }, [])
72
+
73
+ const handleLocaleChange = useCallback((locale: Locale) => {
74
+ navigate({ locale, page: route.page })
75
+ }, [navigate, route.page])
76
+
77
+ const handlePageChange = useCallback((page: DemoPage) => {
78
+ navigate({ locale: route.locale, page })
79
+ }, [navigate, route.locale])
80
+
81
+ const slug = useMemo(() => localeToSlug(route.locale), [route.locale])
82
+ const path = useMemo(() => buildLocalizedPath(route.locale, route.page), [route.locale, route.page])
83
+
84
+ return (
85
+ <div className="app-shell">
86
+ <LanguageBar
87
+ locale={route.locale}
88
+ onLocaleChange={handleLocaleChange}
89
+ currentPage={route.page}
90
+ onPageChange={handlePageChange}
91
+ />
92
+
93
+ <main className="app-main" id="main">
94
+ {route.page === 'overview' && (
95
+ <>
96
+ <section className="hero">
97
+ <div className="hero__content">
98
+ <span className="hero__eyebrow">@asafarim/shared-i18n</span>
99
+ <h1 className="hero__title">{t('title')}</h1>
100
+ <p className="hero__subtitle">{t('subtitle')}</p>
101
+ <div className="hero__cta">
102
+ <button className="btn btn--primary" onClick={() => handlePageChange('get-started')}>
103
+ {t('hero.getStarted')}
104
+ </button>
105
+ <button className="btn btn--ghost" onClick={() => handlePageChange('country-language-selectors')}>
106
+ {t('hero.trySelector')}
107
+ </button>
108
+ </div>
109
+ </div>
110
+
111
+ <aside className="url-card" aria-label={t('hero.liveRoutePreview')}>
112
+ <div className="url-card__label">{t('hero.liveRoutePreview')}</div>
113
+ <code className="url-card__url">
114
+ alisafari-it.github.io<span className="url-card__path">{path}</span>
115
+ </code>
116
+ <dl className="url-card__grid">
117
+ <div><dt>Country</dt><dd>{route.locale.country}</dd></div>
118
+ <div><dt>Language</dt><dd>{route.locale.language.toUpperCase()}</dd></div>
119
+ <div><dt>Slug</dt><dd>{slug}</dd></div>
120
+ <div><dt>i18n</dt><dd>{i18n.language}</dd></div>
121
+ </dl>
122
+ </aside>
123
+ </section>
124
+
125
+ <OverviewSection />
126
+
127
+ <section className="contract">
128
+ <header className="section-head">
129
+ <h2>{t('routingContract.heading')}</h2>
130
+ <p>{t('routingContract.description')}</p>
131
+ </header>
132
+ <ul className="contract__list">
133
+ <li><code>/shared-i18n/</code> {t('routingContract.items.0', { base: '/shared-i18n/', target: '/shared-i18n/be-en/overview' })}</li>
134
+ <li><code>/shared-i18n/&lt;country&gt;-&lt;lang&gt;/&lt;page&gt;</code> {t('routingContract.items.1', { pattern: '/shared-i18n/<country>-<lang>/<page>' })}</li>
135
+ <li><strong>CountryLanguageSelector</strong> {t('routingContract.items.2')}</li>
136
+ <li><strong>LanguageSwitcher</strong> {t('routingContract.items.3')}</li>
137
+ <li>{t('routingContract.items.4', { defaultSlug: 'be-en', defaultPage: 'overview' })}</li>
138
+ </ul>
139
+ </section>
140
+ </>
141
+ )}
142
+
143
+ {route.page === 'get-started' && (
144
+ <section className="page-section">
145
+ <GetStartedSection />
146
+ </section>
147
+ )}
148
+
149
+ {route.page === 'language-switchers' && (
150
+ <section className="page-section">
151
+ <LanguageSwitchersPage locale={route.locale} onLocaleChange={handleLocaleChange} />
152
+ </section>
153
+ )}
154
+
155
+ {route.page === 'country-language-selectors' && (
156
+ <section className="page-section">
157
+ <CountryLanguageSelectorsPage locale={route.locale} onLocaleChange={handleLocaleChange} />
158
+ </section>
159
+ )}
160
+
161
+ {route.page === 'routing-lab' && (
162
+ <section className="page-section">
163
+ <RoutingLabPage locale={route.locale} onLocaleChange={handleLocaleChange} />
164
+ </section>
165
+ )}
166
+ </main>
167
+
168
+ <footer className="app-footer">
169
+ <span>
170
+ {t('footer.activeRoute')}: <code>{path}</code>
171
+ </span>
172
+ <span>
173
+ {t('footer.builtWith')} <a href="https://github.com/AliSafari-IT/shared-i18n" target="_blank" rel="noopener noreferrer">@asafarim/shared-i18n</a>
174
+ </span>
175
+ </footer>
176
+ </div>
177
+ )
178
+ }
@@ -0,0 +1,240 @@
1
+ import { CountryLanguageSelector, useTranslation } from '@asafarim/shared-i18n'
2
+ import type { Locale } from '@asafarim/shared-i18n'
3
+ import { countries } from '../data/countries'
4
+ import { localeToSlug, buildLocalizedPath } from '../i18n/localeRouting'
5
+
6
+ interface Props {
7
+ locale: Locale
8
+ onLocaleChange: (locale: Locale) => void
9
+ }
10
+
11
+ interface DemoItemProps {
12
+ title: string
13
+ description: string
14
+ code: string
15
+ children: React.ReactNode
16
+ previewLabel: string
17
+ codeLabel: string
18
+ }
19
+
20
+ function DemoItem({ title, description, code, children, previewLabel, codeLabel }: DemoItemProps) {
21
+ return (
22
+ <article className="lab-item">
23
+ <header className="lab-item__head">
24
+ <h3 className="lab-item__title">{title}</h3>
25
+ <p className="lab-item__desc">{description}</p>
26
+ </header>
27
+ <div className="lab-item__body">
28
+ <div className="lab-item__preview">
29
+ <span className="lab-item__caption">{previewLabel}</span>
30
+ {children}
31
+ </div>
32
+ <div className="lab-item__code">
33
+ <span className="lab-item__caption">{codeLabel}</span>
34
+ <pre><code>{code}</code></pre>
35
+ </div>
36
+ </div>
37
+ </article>
38
+ )
39
+ }
40
+
41
+ export default function CountryLanguageSelectorsPage({ locale, onLocaleChange }: Props) {
42
+ const { t } = useTranslation('demo')
43
+ const slug = localeToSlug(locale)
44
+ const url = buildLocalizedPath(locale, 'country-language-selectors')
45
+
46
+ return (
47
+ <div className="lab">
48
+ <header className="section-head">
49
+ <h2>{t('countryLanguageSelectors.heading')}</h2>
50
+ <p>{t('countryLanguageSelectors.intro')}</p>
51
+ </header>
52
+
53
+ <div className="lab__status surface">
54
+ <div>
55
+ <span className="lab__caption">{t('countryLanguageSelectors.country')}</span>
56
+ <strong className="lab__value">{locale.country}</strong>
57
+ </div>
58
+ <div>
59
+ <span className="lab__caption">{t('countryLanguageSelectors.language')}</span>
60
+ <strong className="lab__value">{locale.language.toUpperCase()}</strong>
61
+ </div>
62
+ <div>
63
+ <span className="lab__caption">{t('countryLanguageSelectors.slug')}</span>
64
+ <code className="lab__value">{slug}</code>
65
+ </div>
66
+ <div>
67
+ <span className="lab__caption">{t('countryLanguageSelectors.url')}</span>
68
+ <code className="lab__value">{url}</code>
69
+ </div>
70
+ </div>
71
+
72
+ <div className="lab__grid">
73
+ <DemoItem
74
+ title={t('countryLanguageSelectors.variants.0.title')}
75
+ description={t('countryLanguageSelectors.variants.0.desc')}
76
+ previewLabel={t('countryLanguageSelectors.preview')}
77
+ codeLabel={t('countryLanguageSelectors.code')}
78
+ code={`import { CountryLanguageSelector } from '@asafarim/shared-i18n'
79
+
80
+ <CountryLanguageSelector
81
+ countries={countries}
82
+ value={locale}
83
+ onChange={onLocaleChange}
84
+ triggerVariant="compact"
85
+ flagMode="image"
86
+ ariaLabel="Select locale (image flags)"
87
+ />`}
88
+ >
89
+ <CountryLanguageSelector
90
+ countries={countries}
91
+ value={locale}
92
+ onChange={onLocaleChange}
93
+ triggerVariant="compact"
94
+ flagMode="image"
95
+ ariaLabel="Lab — compact, image flags"
96
+ />
97
+ </DemoItem>
98
+
99
+ <DemoItem
100
+ title={t('countryLanguageSelectors.variants.1.title')}
101
+ description={t('countryLanguageSelectors.variants.1.desc')}
102
+ previewLabel={t('countryLanguageSelectors.preview')}
103
+ codeLabel={t('countryLanguageSelectors.code')}
104
+ code={`<CountryLanguageSelector
105
+ countries={countries}
106
+ value={locale}
107
+ onChange={onLocaleChange}
108
+ triggerVariant="compact"
109
+ flagMode="image"
110
+ disabled={true}
111
+ />`}
112
+ >
113
+ <CountryLanguageSelector
114
+ countries={countries}
115
+ value={locale}
116
+ onChange={onLocaleChange}
117
+ triggerVariant="compact"
118
+ flagMode="image"
119
+ disabled={true}
120
+ ariaLabel="Lab — disabled"
121
+ />
122
+ </DemoItem>
123
+
124
+ <DemoItem
125
+ title={t('countryLanguageSelectors.variants.2.title')}
126
+ description={t('countryLanguageSelectors.variants.2.desc')}
127
+ previewLabel={t('countryLanguageSelectors.preview')}
128
+ codeLabel={t('countryLanguageSelectors.code')}
129
+ code={`<CountryLanguageSelector
130
+ countries={countries}
131
+ value={locale}
132
+ onChange={onLocaleChange}
133
+ triggerVariant="full"
134
+ flagMode="image"
135
+ />`}
136
+ >
137
+ <CountryLanguageSelector
138
+ countries={countries}
139
+ value={locale}
140
+ onChange={onLocaleChange}
141
+ triggerVariant="full"
142
+ flagMode="image"
143
+ ariaLabel="Lab — full names, image flags"
144
+ />
145
+ </DemoItem>
146
+
147
+ <DemoItem
148
+ title={t('countryLanguageSelectors.variants.3.title')}
149
+ description={t('countryLanguageSelectors.variants.3.desc')}
150
+ previewLabel={t('countryLanguageSelectors.preview')}
151
+ codeLabel={t('countryLanguageSelectors.code')}
152
+ code={`<CountryLanguageSelector
153
+ countries={countries}
154
+ value={locale}
155
+ onChange={onLocaleChange}
156
+ triggerVariant="flag"
157
+ flagMode="image"
158
+ />`}
159
+ >
160
+ <CountryLanguageSelector
161
+ countries={countries}
162
+ value={locale}
163
+ onChange={onLocaleChange}
164
+ triggerVariant="flag"
165
+ flagMode="image"
166
+ ariaLabel="Lab — flag only"
167
+ />
168
+ </DemoItem>
169
+
170
+ <DemoItem
171
+ title={t('countryLanguageSelectors.variants.4.title')}
172
+ description={t('countryLanguageSelectors.variants.4.desc')}
173
+ previewLabel={t('countryLanguageSelectors.preview')}
174
+ codeLabel={t('countryLanguageSelectors.code')}
175
+ code={`<CountryLanguageSelector
176
+ countries={countries}
177
+ value={locale}
178
+ onChange={onLocaleChange}
179
+ renderTrigger={({ country, language, open }) => (
180
+ <button type="button" className="custom-trigger" data-open={open}>
181
+ {open ? 'Close' : 'Open'}{' '}
182
+ <img
183
+ src={\`https://flagcdn.com/w20/\${country.code.toLowerCase()}.png\`}
184
+ alt=""
185
+ width="20"
186
+ style={{ borderRadius: 2, verticalAlign: 'middle' }}
187
+ />{' '}
188
+ {country.name} · {language.code.toUpperCase()}
189
+ </button>
190
+ )}
191
+ />`}
192
+ >
193
+ <CountryLanguageSelector
194
+ countries={countries}
195
+ value={locale}
196
+ onChange={onLocaleChange}
197
+ renderTrigger={({ country, language, open }) => (
198
+ <button type="button" className="custom-trigger" data-open={open}>
199
+ {open ? t('countryLanguageSelectors.close') : t('countryLanguageSelectors.open')}{' '}
200
+ <img
201
+ src={`https://flagcdn.com/w20/${country.code.toLowerCase()}.png`}
202
+ alt=""
203
+ width="20"
204
+ style={{ borderRadius: 2, verticalAlign: 'middle' }}
205
+ />{' '}
206
+ {country.name} · {language.code.toUpperCase()}
207
+ </button>
208
+ )}
209
+ ariaLabel="Lab — custom trigger"
210
+ />
211
+ </DemoItem>
212
+
213
+ <DemoItem
214
+ title={t('countryLanguageSelectors.variants.5.title')}
215
+ description={t('countryLanguageSelectors.variants.5.desc')}
216
+ previewLabel={t('countryLanguageSelectors.preview')}
217
+ codeLabel={t('countryLanguageSelectors.code')}
218
+ code={`<CountryLanguageSelector
219
+ countries={countries}
220
+ value={locale}
221
+ onChange={onLocaleChange}
222
+ triggerVariant="compact"
223
+ flagMode="image"
224
+ align="start"
225
+ />`}
226
+ >
227
+ <CountryLanguageSelector
228
+ countries={countries}
229
+ value={locale}
230
+ onChange={onLocaleChange}
231
+ triggerVariant="compact"
232
+ flagMode="image"
233
+ align="start"
234
+ ariaLabel="Lab — align start"
235
+ />
236
+ </DemoItem>
237
+ </div>
238
+ </div>
239
+ )
240
+ }
@@ -1,56 +1,56 @@
1
- import { useState } from 'react'
2
- import { useTranslation } from '@asafarim/shared-i18n'
3
-
4
- export default function GetStartedSection() {
5
- const { t } = useTranslation('demo')
6
- const [expandedStep, setExpandedStep] = useState<number>(0)
7
- const getStarted = t('getStarted', { returnObjects: true }) as any
8
-
9
- return (
10
- <div className="get-started-section">
11
- <div className="gs-header">
12
- <h2 className="gs-title">{getStarted.heading}</h2>
13
- <p className="gs-intro">{getStarted.intro}</p>
14
- </div>
15
-
16
- <div className="steps-container">
17
- {getStarted.steps.map((step: any, idx: number) => (
18
- <div key={idx} className="step-item">
19
- <button
20
- className={`step-header ${expandedStep === idx ? 'expanded' : ''}`}
21
- onClick={() => setExpandedStep(expandedStep === idx ? -1 : idx)}
22
- >
23
- <div className="step-number">{idx + 1}</div>
24
- <div className="step-info">
25
- <h3>{step.title}</h3>
26
- <p>{step.description}</p>
27
- </div>
28
- <div className="step-toggle">
29
- {expandedStep === idx ? '−' : '+'}
30
- </div>
31
- </button>
32
- {expandedStep === idx && (
33
- <div className="step-content">
34
- <pre className="code-block">
35
- <code>{step.code}</code>
36
- </pre>
37
- </div>
38
- )}
39
- </div>
40
- ))}
41
- </div>
42
-
43
- <div className="tips-section">
44
- <div className="tips-icon">💡</div>
45
- <div className="tips-content">
46
- <h3>{getStarted.tips.title}</h3>
47
- <ul className="tips-list">
48
- {getStarted.tips.items.map((tip: string, idx: number) => (
49
- <li key={idx}>{tip}</li>
50
- ))}
51
- </ul>
52
- </div>
53
- </div>
54
- </div>
55
- )
56
- }
1
+ import { useState } from 'react'
2
+ import { useTranslation } from '@asafarim/shared-i18n'
3
+
4
+ export default function GetStartedSection() {
5
+ const { t } = useTranslation('demo')
6
+ const [expandedStep, setExpandedStep] = useState<number>(0)
7
+ const getStarted = t('getStarted', { returnObjects: true }) as any
8
+
9
+ return (
10
+ <div className="get-started-section">
11
+ <div className="gs-header">
12
+ <h2 className="gs-title">{getStarted.heading}</h2>
13
+ <p className="gs-intro">{getStarted.intro}</p>
14
+ </div>
15
+
16
+ <div className="steps-container">
17
+ {getStarted.steps.map((step: any, idx: number) => (
18
+ <div key={idx} className="step-item">
19
+ <button
20
+ className={`step-header ${expandedStep === idx ? 'expanded' : ''}`}
21
+ onClick={() => setExpandedStep(expandedStep === idx ? -1 : idx)}
22
+ >
23
+ <div className="step-number">{idx + 1}</div>
24
+ <div className="step-info">
25
+ <h3>{step.title}</h3>
26
+ <p>{step.description}</p>
27
+ </div>
28
+ <div className="step-toggle">
29
+ {expandedStep === idx ? '−' : '+'}
30
+ </div>
31
+ </button>
32
+ {expandedStep === idx && (
33
+ <div className="step-content">
34
+ <pre className="code-block">
35
+ <code>{step.code}</code>
36
+ </pre>
37
+ </div>
38
+ )}
39
+ </div>
40
+ ))}
41
+ </div>
42
+
43
+ <div className="tips-section">
44
+ <div className="tips-icon">💡</div>
45
+ <div className="tips-content">
46
+ <h3>{getStarted.tips.title}</h3>
47
+ <ul className="tips-list">
48
+ {getStarted.tips.items.map((tip: string, idx: number) => (
49
+ <li key={idx}>{tip}</li>
50
+ ))}
51
+ </ul>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ )
56
+ }