Skip to content

Commit 233f2de

Browse files
author
Ryan A. Johnson
committed
fix(HXTabsetElement): correct issues with dynamic tabs
* add update() method to synchronize visual state with logical config * fix 'hxtabclick' listener to update() if configuration is already correct * tweak styles for `hr.hxDivider` to support usage on various backgrounds * beta styles for button set spacing (`.beta-hxButtonSet > .hxBtn + .hxBtn`)
1 parent 6875755 commit 233f2de

File tree

8 files changed

+391
-23
lines changed

8 files changed

+391
-23
lines changed

docs/components/tabset/index.html

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,156 @@ <h3>Third Panel</h3>
121121
</footer>
122122
</div>
123123
</section>
124+
125+
<section>
126+
<header>
127+
<h2 id="dynamic-tabset">Dynamic Tabset</h2>
128+
{# TODO: add section description #}
129+
</header>
130+
131+
<div class="example" id="vue-dynamicTabsetDemo" v-cloak>
132+
<header>
133+
<form class="beta-hxForm" @submit.prevent>
134+
<div>
135+
<label for="numCurrentTab" class="beta-hxFieldName">
136+
Current Tab
137+
</label>
138+
<br />
139+
<input
140+
class="hxTextCtrl"
141+
id="numCurrentTab"
142+
min="0"
143+
step="1"
144+
type="number"
145+
v-model.number="currentTab"
146+
:max="tabs.length - 1"
147+
/>
148+
</div>
149+
150+
<section>
151+
<p class="beta-hxFieldName">
152+
Increase Tab Count
153+
</p>
154+
<p class="hxBtnGroup">
155+
<button
156+
class="hxBtn"
157+
type="button"
158+
@click="addTab('start')"
159+
>
160+
<hx-icon type="plus"></hx-icon>
161+
<span>Prepend New</span>
162+
</button>
163+
164+
<button
165+
class="hxBtn"
166+
type="button"
167+
@click="addTab('end')"
168+
>
169+
<hx-icon type="plus"></hx-icon>
170+
<span>Append New</span>
171+
</button>
172+
</p>
173+
</section>
174+
175+
<section>
176+
<p class="beta-hxFieldName">
177+
Decrease Tab Count
178+
</p>
179+
<p class="hxBtnGroup">
180+
<button
181+
class="hxBtn"
182+
type="button"
183+
@click="removeTab('start')"
184+
>
185+
<hx-icon type="minus"></hx-icon>
186+
<span>Remove First</span>
187+
</button>
188+
189+
<button
190+
class="hxBtn"
191+
type="button"
192+
@click="removeTab('end')"
193+
>
194+
<hx-icon type="minus"></hx-icon>
195+
<span>Remove Last</span>
196+
</button>
197+
</p>
198+
</section>
199+
200+
<fieldset>
201+
<legend>Options</legend>
202+
<hx-checkbox-control>
203+
<input
204+
id="chkAutoUpdate"
205+
type="checkbox"
206+
v-model="autoUpdate"
207+
/>
208+
<label for="chkAutoUpdate">
209+
<hx-checkbox></hx-checkbox>
210+
Update on change
211+
</label>
212+
</hx-checkbox-control>
213+
</fieldset>
214+
215+
<hr class="hxDivider" />
216+
217+
<footer>
218+
<p class="beta-hxButtonSet">
219+
<button
220+
class="hxBtn hxPrimary"
221+
type="button"
222+
:disabled="autoUpdate"
223+
@click="update"
224+
>
225+
Update Tabset
226+
</button>
227+
228+
<button
229+
class="hxBtn hxTertiary"
230+
type="button"
231+
@click="reset"
232+
>
233+
<span>Reset Tabset</span>
234+
<hx-icon type="undo"></hx-icon>
235+
</button>
236+
</p>
237+
</footer>
238+
</form>
239+
</header>
240+
241+
{% raw %}
242+
<div>
243+
<hx-tabset
244+
ref="tabset"
245+
:current-tab="currentTab"
246+
@tabchange="onTabchange"
247+
>
248+
<hx-tablist>
249+
<hx-tab v-for="tab in tabs" :key="tab.id">
250+
Tab #{{tab.id}}
251+
</hx-tab>
252+
</hx-tablist>
253+
254+
<hx-tabcontent>
255+
<hx-tabpanel v-for="tab in tabs" :key="tab.id">
256+
Tab Panel #{{tab.id}}
257+
</hx-tabpanel>
258+
</hx-tabcontent>
259+
</hx-tabset>
260+
</div>
261+
{% endraw %}
262+
263+
<footer>
264+
<pre><code v-text="snippet"></code></pre>
265+
</footer>
266+
</div>
267+
268+
<footer>
269+
<p class="hxSubBody hxSubdued">
270+
<hx-icon type="info-circle"></hx-icon>
271+
Refer to <a href="elements/hx-tabset#managing-state">"Managing State"</a>
272+
in <code>&lt;hx-tabset&gt;</code> documentation, for more details.
273+
</p>
274+
</footer>
275+
</section>
124276
{% endblock %}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import Util from '../../_util';
2+
3+
function getDefaultData (force = false) {
4+
let data = {
5+
nextID: 4,
6+
currentTab: 0,
7+
tabs: [
8+
{ id: 1 },
9+
{ id: 2 },
10+
{ id: 3 },
11+
],
12+
};
13+
14+
if (force === true) {
15+
data.autoUpdate = false;
16+
}
17+
18+
return data;
19+
}
20+
21+
if (document.getElementById('vue-dynamicTabsetDemo')) {
22+
new Vue({
23+
el: '#vue-dynamicTabsetDemo',
24+
data: getDefaultData(true),
25+
updated: function () {
26+
// keep currentTab in bounds
27+
if (this.currentTab >= this.tabs.length) {
28+
this.currentTab = (this.tabs.length ? this.tabs.length - 1 : 0);
29+
}
30+
31+
// keep correct tab/panel pair open when a "tab" is added
32+
if (this.autoUpdate) {
33+
this.update();
34+
}
35+
},
36+
methods: {
37+
addTab: function (dir) {
38+
let id = this.nextID++;
39+
40+
switch (dir) {
41+
case 'start':
42+
this.tabs.unshift({ id });
43+
break;
44+
case 'end':
45+
this.tabs.push({ id });
46+
break;
47+
default:
48+
// do nothing
49+
break;
50+
}
51+
},
52+
removeTab: function (dir) {
53+
switch (dir) {
54+
case 'start':
55+
this.tabs.shift();
56+
break;
57+
case 'end':
58+
this.tabs.pop();
59+
break;
60+
default:
61+
// do nothing
62+
break;
63+
}
64+
},
65+
onTabchange: function (evt) {
66+
this.currentTab = evt.target.currentTab;
67+
},
68+
reset: function () {
69+
Object.assign(this.$data, getDefaultData());
70+
71+
// defer update to next event loop to avoid
72+
// conflicting with resetting this.$data
73+
setTimeout(this.update, 0);
74+
},
75+
update: function () {
76+
this.$refs.tabset.update();
77+
},
78+
},
79+
computed: {
80+
_tabs: function () {
81+
return this.tabs.map((tab, idx) => {
82+
let html = `<hx-tab id="tab-${tab.id}"`;
83+
if (idx === this.currentTab) {
84+
html += ' current="true"';
85+
}
86+
html += '></hx-tab>';
87+
return html;
88+
});
89+
},
90+
_tabpanels: function () {
91+
return this.tabs.map((tab, idx) => {
92+
let html = `<hx-tabpanel id="panel-${tab.id}"`;
93+
if (idx === this.currentTab) {
94+
html += ' open';
95+
}
96+
html += '></hx-tabpanel>';
97+
return html;
98+
});
99+
},
100+
_tablist: function () {
101+
return this._tabs.reduce((all, tab) => {
102+
return `${all}\n ${tab}`;
103+
}, '');
104+
},
105+
_tabcontent: function () {
106+
return this._tabpanels.reduce((all, panel) => {
107+
return `${all}\n ${panel}`;
108+
}, '');
109+
},
110+
// Indentation is intentional because `Util.snippet()`
111+
// isn't smart enough to re-indent HTML tags.
112+
snippet: function () {
113+
return Util.snippet(`
114+
<hx-tabset current-tab="${this.currentTab}">
115+
<hx-tablist>
116+
${this._tablist}
117+
</hx-tablist>
118+
<hx-tabcontent>
119+
${this._tabcontent}
120+
</hx-tabcontent>
121+
</hx-tabset>
122+
`);
123+
},
124+
},
125+
});
126+
}

docs/docs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import './components/reveal/reveal-demo';
2424
import './components/search/search-demo';
2525
import './components/stepper/stepper-demo';
2626
import './components/table/table-demo';
27+
import './components/tabset/tabset-demo';
2728
import './components/text-input/text-input-demo';
2829
import './components/textarea/textarea-demo';
2930
import './components/toast/toast-demo';

docs/docs.less

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,6 @@ min-version {
233233
font-size: 0.875rem;
234234
font-weight: 300;
235235
}
236-
237-
hr {
238-
border-top-color: @gray-400;
239-
}
240236
}
241237

242238
// EXAMPLE component

0 commit comments

Comments
 (0)