diff --git a/frontend/src/ui/controls.css b/frontend/src/ui/controls.css index 9673486..8673830 100644 --- a/frontend/src/ui/controls.css +++ b/frontend/src/ui/controls.css @@ -1660,6 +1660,27 @@ label.sky-list-item__row { transition-duration: 0ms; } +.sky-glass.sky-segmented--navigation.sky-segmented--compact { + height: 48px; + min-height: 48px; + padding-block: 2px; +} + +.sky-segmented--navigation.sky-segmented--compact .sky-segmented-button { + height: var(--sky-touch-target, 44px); + min-height: var(--sky-touch-target, 44px); + flex-direction: row; + gap: 0; + font-size: 15px; + font-weight: 500; + line-height: 20px; +} + +.sky-segmented--compact .sky-segmented__highlight { + top: 2px; + bottom: 2px; +} + .sky-segmented__highlight { position: absolute; z-index: 0; diff --git a/frontend/src/ui/controls/SkySegmented.test.ts b/frontend/src/ui/controls/SkySegmented.test.ts index 49dc633..f4e0c6f 100644 --- a/frontend/src/ui/controls/SkySegmented.test.ts +++ b/frontend/src/ui/controls/SkySegmented.test.ts @@ -102,6 +102,42 @@ describe('SkySegmented navigation', () => { ) }) + it('keeps text-only Glass navigation compact with 44px targets', async () => { + const html = await renderToString( + createSSRApp({ + render: () => + h( + SkySegmented, + { + activeIndex: 1, + ariaLabel: 'Availability', + compact: true, + itemCount: 3, + navigation: true, + }, + { + default: () => + ['Available', 'Busy', 'Closed'].map((label, index) => + h(SkySegmentedButton, { active: index === 1 }, () => label), + ), + }, + ), + }), + ) + + expect(html).toContain('sky-segmented--compact') + expect(html).toContain('width:calc(33.3333% - 5.3333px)') + expect(controls).toMatch( + /\.sky-glass\.sky-segmented--navigation\.sky-segmented--compact\s*\{[^}]*height:\s*48px[^}]*min-height:\s*48px[^}]*padding-block:\s*2px/s, + ) + expect(controls).toMatch( + /\.sky-segmented--navigation\.sky-segmented--compact \.sky-segmented-button\s*\{[^}]*height:\s*var\(--sky-touch-target, 44px\)[^}]*font-size:\s*15px[^}]*font-weight:\s*500/s, + ) + expect(controls).toMatch( + /\.sky-segmented--compact \.sky-segmented__highlight\s*\{[^}]*top:\s*2px[^}]*bottom:\s*2px/s, + ) + }) + it('lets subnavbar search controls fill the available Konsta row', () => { expect(controls).toMatch( /\.sky-searchbar\s*\{[^}]*width:\s*100%[^}]*flex:\s*1 1 auto/s, diff --git a/frontend/src/ui/controls/SkySegmented.vue b/frontend/src/ui/controls/SkySegmented.vue index e15eebd..1e2f965 100644 --- a/frontend/src/ui/controls/SkySegmented.vue +++ b/frontend/src/ui/controls/SkySegmented.vue @@ -10,6 +10,7 @@ const props = withDefaults( defineProps<{ activeIndex?: number ariaLabel?: string + compact?: boolean glassHighlight?: boolean itemCount?: number navigation?: boolean @@ -21,6 +22,7 @@ const props = withDefaults( { activeIndex: 0, ariaLabel: '', + compact: false, glassHighlight: true, itemCount: 0, navigation: undefined, @@ -65,6 +67,7 @@ const indicatorStyle = computed(() => { class="sky-segmented" :class="{ 'sky-segmented--navigation': isNavigation, + 'sky-segmented--compact': compact, 'sky-segmented--outline': outline, 'sky-segmented--raised': raised, 'sky-segmented--rounded': rounded, diff --git a/frontend/src/views/apps/AppStoreApp.contract.test.ts b/frontend/src/views/apps/AppStoreApp.contract.test.ts index f1c4ac4..9925e14 100644 --- a/frontend/src/views/apps/AppStoreApp.contract.test.ts +++ b/frontend/src/views/apps/AppStoreApp.contract.test.ts @@ -6,6 +6,10 @@ const source = readFileSync( new URL('./AppStoreApp.vue', import.meta.url), 'utf8', ) +const navigationSource = source.slice( + source.indexOf('') + ''.length, +) describe('AppStoreApp Sky navigation contract', () => { it('uses only the first-party Sky UI surface', () => { @@ -32,6 +36,8 @@ describe('AppStoreApp Sky navigation contract', () => { expect(source).toContain(':item-count="tabs.length"') expect(source).toContain(' { diff --git a/frontend/src/views/apps/CompaniesApp.navigation.contract.test.ts b/frontend/src/views/apps/CompaniesApp.navigation.contract.test.ts index 989ccbf..54b7489 100644 --- a/frontend/src/views/apps/CompaniesApp.navigation.contract.test.ts +++ b/frontend/src/views/apps/CompaniesApp.navigation.contract.test.ts @@ -31,6 +31,7 @@ describe('CompaniesApp Sky pill navigation contract', () => { expect(source).toContain(':data-active-tab="activeTab"') expect(source).toContain(':item-count="3"') expect(navigationSource.match(/ { @@ -50,6 +51,7 @@ describe('CompaniesApp Sky pill navigation contract', () => { it('uses the shared sliding Glass system for the request-state filter', () => { expect(requestFilterSource).toContain(' { ) expect(requestFilterSource.match(/ { + expect(source.match(/class="availability-segmented"/g)).toHaveLength(2) + expect( + source.match( + /:active-index="availabilityValues\.indexOf\(workCompany\.availability\)"/g, + ), + ).toHaveLength(2) + expect( + source.match(/:item-count="availabilityValues\.length"/g), + ).toHaveLength(2) + expect(source.match(/\s+compact\s+navigation/g)).toHaveLength(3) + expect(source).toContain( + ':aria-label="phone.t(\'Apps.companies.work.publicAvailability\')"', + ) + expect(source).toContain( + ':aria-label="phone.t(\'Apps.companies.manager.availability\')"', + ) + }) }) diff --git a/frontend/src/views/apps/CompaniesApp.vue b/frontend/src/views/apps/CompaniesApp.vue index 8f37dbd..5993feb 100644 --- a/frontend/src/views/apps/CompaniesApp.vue +++ b/frontend/src/views/apps/CompaniesApp.vue @@ -1201,7 +1201,9 @@ onBeforeUnmount(() => { :active-index="requestList === 'open' ? 0 : 1" :aria-label="phone.t('Apps.companies.tabs.requests')" :item-count="2" + compact navigation + rounded > { {{ phone.t('Apps.companies.work.publicAvailability') }} - + { {{ phone.t('Apps.companies.manager.availability') }} - +