Skip to content

Commit 6e7cd04

Browse files
feat(tabs): DLT-3162 add spread prop for horizontal distribution (#1135)
1 parent ba6b599 commit 6e7cd04

16 files changed

Lines changed: 234 additions & 37 deletions

File tree

apps/dialtone-documentation/docs/.vuepress/exampleComponents/ExampleTabs.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:inverted="inverted"
77
:borderless="borderless"
88
:disabled="disabled"
9+
:spread="spread"
910
:orientation="orientation"
1011
:tab-list-class="orientation === 'vertical' ? 'd-w264' : undefined"
1112
:activation-mode="activationMode"
@@ -112,6 +113,11 @@ export default {
112113
default: 'manual',
113114
},
114115
116+
spread: {
117+
type: String,
118+
default: 'none',
119+
},
120+
115121
orientation: {
116122
type: String,
117123
default: 'horizontal',

apps/dialtone-documentation/docs/_data/tabs.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@
2020
"applies": "N/A",
2121
"description": "Inverts the tablist to work on dark backgrounds."
2222
},
23+
{
24+
"class": "d-tablist--spread-grow",
25+
"applies": ".d-tablist",
26+
"description": "Tabs grow proportionally to fill available space. Longer labels get more room."
27+
},
28+
{
29+
"class": "d-tablist--spread-equal",
30+
"applies": ".d-tablist",
31+
"description": "All tabs are the same width regardless of content length."
32+
},
2333
{
2434
"class": "d-tab",
2535
"applies": "N/A",

apps/dialtone-documentation/docs/components/tabs.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,66 @@ vueCode='
204204
'
205205
showHtmlWarning />
206206

207+
## Spread
208+
209+
Control how tabs distribute available horizontal space within the tab list. It only applies to horizontal tabs, and has no effect with `orientation="vertical"`.
210+
211+
### Grow
212+
213+
Tabs expand proportionally to fill the container. Longer labels receive more space.
214+
215+
<code-well-header>
216+
<div class="d-w100p">
217+
<dt-tab-group spread="grow">
218+
<template #tabs>
219+
<dt-tab id="sg1" panel-id="sg2" selected>Tab 1</dt-tab>
220+
<dt-tab id="sg3" panel-id="sg4">Tab the second</dt-tab>
221+
<dt-tab id="sg5" panel-id="sg6">Tab the third</dt-tab>
222+
</template>
223+
</dt-tab-group>
224+
</div>
225+
</code-well-header>
226+
227+
<code-example-tabs
228+
vueCode='
229+
<dt-tab-group spread="grow">
230+
<template #tabs>
231+
<dt-tab id="1" panel-id="2" selected>Tab 1</dt-tab>
232+
<dt-tab id="3" panel-id="4">Tab the second</dt-tab>
233+
<dt-tab id="5" panel-id="6">Tab the third</dt-tab>
234+
</template>
235+
</dt-tab-group>
236+
'
237+
showHtmlWarning />
238+
239+
### Equal
240+
241+
All tabs share the same width, regardless of label length.
242+
243+
<code-well-header>
244+
<div class="d-w100p">
245+
<dt-tab-group spread="equal">
246+
<template #tabs>
247+
<dt-tab id="se1" panel-id="se2" selected>Tab 1</dt-tab>
248+
<dt-tab id="se3" panel-id="se4">Tab the second</dt-tab>
249+
<dt-tab id="se5" panel-id="se6">Tab the third</dt-tab>
250+
</template>
251+
</dt-tab-group>
252+
</div>
253+
</code-well-header>
254+
255+
<code-example-tabs
256+
vueCode='
257+
<dt-tab-group spread="equal">
258+
<template #tabs>
259+
<dt-tab id="1" panel-id="2" selected>Tab 1</dt-tab>
260+
<dt-tab id="3" panel-id="4">Tab the second</dt-tab>
261+
<dt-tab id="5" panel-id="6">Tab the third</dt-tab>
262+
</template>
263+
</dt-tab-group>
264+
'
265+
showHtmlWarning />
266+
207267
## Sizes
208268

209269
<code-well-header>

packages/combinator/src/variants/variants_tab_group.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,46 @@ export default {
1717
},
1818
},
1919

20+
'spread tabs: equal': {
21+
props: {
22+
selected: { initialValue: 'panel-1' },
23+
tabListClass: {
24+
initialValue: 'd-w384',
25+
},
26+
spread: {
27+
initialValue: 'equal',
28+
},
29+
},
30+
slots: {
31+
tabs: {
32+
initialValue: '<dt-tab id="tab-1" panel-id="panel-1" selected>Tab 1</dt-tab><dt-tab id="tab-2" panel-id="panel-2">Tab 2</dt-tab><dt-tab id="tab-3" panel-id="panel-3">Tab the third</dt-tab>',
33+
},
34+
default: {
35+
initialValue: '<div class="d-ba d-baw2 d-bas-dashed d-bc-subtle d-w100p d-py48"><dt-tab-panel id="panel-1" tab-id="tab-1"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>First</strong> tab content panel </dt-text></dt-tab-panel><dt-tab-panel id="panel-2" tab-id="tab-2"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>Second</strong> tab content panel </dt-text></dt-tab-panel><dt-tab-panel id="panel-3" tab-id="tab-3"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>Third</strong> tab content panel </dt-text></dt-tab-panel></div>',
36+
},
37+
},
38+
},
39+
40+
'spread tabs: grow': {
41+
props: {
42+
selected: { initialValue: 'panel-1' },
43+
tabListClass: {
44+
initialValue: 'd-w384',
45+
},
46+
spread: {
47+
initialValue: 'grow',
48+
},
49+
},
50+
slots: {
51+
tabs: {
52+
initialValue: '<dt-tab id="tab-1" panel-id="panel-1" selected>Tab 1</dt-tab><dt-tab id="tab-2" panel-id="panel-2">Tab the second</dt-tab><dt-tab id="tab-3" panel-id="panel-3">Tab the third</dt-tab>',
53+
},
54+
default: {
55+
initialValue: '<div class="d-ba d-baw2 d-bas-dashed d-bc-subtle d-w100p d-py48"><dt-tab-panel id="panel-1" tab-id="tab-1"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>First</strong> tab content panel </dt-text></dt-tab-panel><dt-tab-panel id="panel-2" tab-id="tab-2"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>Second</strong> tab content panel </dt-text></dt-tab-panel><dt-tab-panel id="panel-3" tab-id="tab-3"><dt-text as="p" kind="code" size="xs" tone="muted" align="center" class="d-p16"> <strong>Third</strong> tab content panel </dt-text></dt-tab-panel></div>',
56+
},
57+
},
58+
},
59+
2060
'muted, small': {
2161
props: {
2262
kind: { initialValue: 'muted' },

packages/dialtone-css/lib/build/less/components/emoji-picker.less

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,14 @@
6060

6161
&__tabset-list {
6262
gap: var(--dt-size-0);
63-
justify-content: space-between;
6463

6564
&::after {
6665
background-color: var(--dt-color-surface-moderate) !important;
6766
}
6867

69-
.d-tab {
70-
--tab-padding-y: var(--dt-size-400);
71-
--tab-padding-x: var(--dt-size-300);
72-
73-
flex-grow: 1;
68+
.d-tablist__item {
69+
--button-padding-y: var(--dt-size-350);
70+
--button-padding-x: var(--dt-size-100);
7471

7572
&.d-tab--selected {
7673
&::after {

packages/dialtone-css/lib/build/less/components/tabs.less

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,27 @@
154154
}
155155
}
156156

157+
// Spread — shared
158+
&--spread-grow,
159+
&--spread-equal {
160+
flex-wrap: nowrap;
161+
}
162+
163+
// Spread grow — tabs grow proportionally to fill space
164+
&--spread-grow {
165+
> :where(.d-tablist__item) {
166+
flex: 1 1 auto;
167+
}
168+
}
169+
170+
// Spread equal — all tabs are the same width
171+
&--spread-equal {
172+
> :where(.d-tablist__item) {
173+
flex: 1 1 0;
174+
min-inline-size: 0;
175+
}
176+
}
177+
157178
// Inverted
158179
&--inverted {
159180
--tab-color-text: var(--dt-action-color-foreground-inverted-default);

packages/dialtone-vue/components/emoji_picker/modules/emoji_tabset.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<dt-tab-group
44
:selected="selectedTab"
55
size="sm"
6+
spread="equal"
67
tab-list-class="d-emoji-picker__tabset-list"
78
>
89
<template #tabs>

packages/dialtone-vue/components/tab/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export {
88
TAB_LIST_SIZE_MODIFIERS,
99
TAB_ACTIVATION_MODES,
1010
TAB_GROUP_KINDS,
11+
TAB_SPREADS,
1112
} from './tabs_constants';

packages/dialtone-vue/components/tab/tab.test.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ describe('DtTab Tests', () => {
6161
expect(tab.text()).toBe(MOCK_DEFAULT_SLOT);
6262
});
6363

64-
it('should set data-content to match the slot text', () => {
65-
const label = tab.find('[data-qa="dt-tab-label"]');
66-
67-
expect(label.attributes('data-content')).toBe(MOCK_DEFAULT_SLOT);
68-
});
69-
7064
describe('Selected Tab by default', () => {
7165
it('Group context should have set selected tab', () => {
7266
mockProps = { selected: true };

packages/dialtone-vue/components/tab/tab.vue

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,7 @@
6868
<slot name="trailing" />
6969
</template>
7070
<!-- @slot default slot, defaults contains dt-button -->
71-
<span
72-
ref="tabLabel"
73-
class="d-tablist__item-label"
74-
data-qa="dt-tab-label"
75-
>
76-
<slot />
77-
</span>
71+
<slot />
7872
</dt-button>
7973
</template>
8074

@@ -246,28 +240,12 @@ export default {
246240
},
247241
248242
mounted () {
249-
this.syncDataContent();
250243
if (this.selected) {
251244
this.groupContext.selected = this.panelId;
252245
}
253246
},
254247
255-
updated () {
256-
this.syncDataContent();
257-
},
258-
259248
methods: {
260-
// Sets data-content to match the rendered label text so CSS can use
261-
// `content: attr(data-content)` on a hidden ::after pseudo-element to
262-
// hold the bold-width and prevent layout shift on selection.
263-
syncDataContent () {
264-
const el = this.$refs.tabLabel;
265-
if (!el) return;
266-
const text = el.textContent?.trim() || '';
267-
if (el.getAttribute('data-content') !== text) {
268-
el.setAttribute('data-content', text);
269-
}
270-
},
271249
},
272250
};
273251
</script>

0 commit comments

Comments
 (0)