Skip to content

Commit 4f182ab

Browse files
committed
feat: add new component
1 parent 65306f4 commit 4f182ab

File tree

8 files changed

+219
-1
lines changed

8 files changed

+219
-1
lines changed

src/components/molecules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './dropdown';
66
export * from './stepper';
77
export * from './form';
88
export * from './steps';
9+
export * from './tabs';

src/components/molecules/steps/steps.stories.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Meta, ArgsTable, Story, Canvas } from "@storybook/addon-docs";
22
import { HSteps, HStep } from "./index.js";
33

44
<Meta
5-
title="Design System/Molecules/Toast"
5+
title="Design System/Molecules/Steps"
66
component={HSteps}
77
decorators={[
88
() => ({
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import HTabs from './tabs.vue';
2+
import HTab from './tab.vue';
3+
4+
export { HTabs, HTab };
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Meta, ArgsTable, Story, Canvas } from "@storybook/addon-docs";
2+
import { HTabs, HTab } from "./index.js";
3+
4+
<Meta
5+
title="Design System/Molecules/Tabs"
6+
component={HTabs}
7+
decorators={[
8+
() => ({
9+
template:
10+
'<div style="transform: scale(1); height: 40vh;"><story /></div>',
11+
}),
12+
]}
13+
/>
14+
15+
## Tabs
16+
17+
### Default use:
18+
19+
<Canvas>
20+
<Story name="Default use">
21+
{{
22+
data() {
23+
return {
24+
currentTab: 'first',
25+
};
26+
},
27+
template: `
28+
<h-tabs
29+
v-model="currentTab"
30+
>
31+
<h-tab value="first">
32+
first
33+
</h-tab>
34+
<h-tab value="second">
35+
second
36+
</h-tab>
37+
<h-tab
38+
value="third"
39+
disabled
40+
>
41+
third
42+
</h-tab>
43+
</h-tabs>
44+
`,
45+
}}
46+
</Story>
47+
</Canvas>

src/components/molecules/tabs/tab.vue

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<template>
2+
<div
3+
class="h-tab"
4+
:class="{
5+
'h-tab--behavior-active': isCurrentTab,
6+
'h-tab--behavior-disabled': $props.disabled,
7+
}"
8+
@click="handleChange"
9+
>
10+
{{ label }}<slot />
11+
</div>
12+
</template>
13+
14+
<script setup>
15+
import { inject, computed } from 'vue';
16+
import { BTabsKey } from './tabs-key';
17+
18+
const $props = defineProps({
19+
label: {
20+
type: String,
21+
default: undefined,
22+
},
23+
value: {
24+
type: [String, Number],
25+
required: true,
26+
},
27+
disabled: {
28+
type: Boolean,
29+
default: undefined,
30+
},
31+
});
32+
33+
const { currentTab, handleTabsChange } = inject(BTabsKey);
34+
const $emits = defineEmits(['change']);
35+
36+
const handleChange = () => {
37+
if ($props.disabled) return;
38+
$emits('change', $props.value);
39+
handleTabsChange($props.value);
40+
};
41+
const isCurrentTab = computed(() => currentTab.value === $props.value);
42+
</script>
43+
44+
<style lang="scss">
45+
:root {
46+
--h-tab--background-color: var(--color-theme-secondary-800);
47+
--h-tab--color: var(--color-theme-white);
48+
--h-tab--font-weight: 500;
49+
}
50+
51+
.h-tab {
52+
display: flex;
53+
justify-content: center;
54+
align-items: center;
55+
flex:1;
56+
text-transform: uppercase;
57+
background-color: var(--h-tab--background-color);
58+
color: var(--h-tab--color);
59+
font-weight: var(--h-tab--font-weight);
60+
61+
&--behavior {
62+
&-disabled {
63+
--h-tab--background-color: var(--color-grey-scale-400);
64+
--h-tab--color: var(--color-grey-scale-700);
65+
}
66+
}
67+
68+
&:not(.h-tab--behavior-active):not(.h-tab--behavior-disabled) {
69+
--h-tab--color: var(--color-grey-scale-100);
70+
}
71+
}
72+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BTabsKey = Symbol('BTabsKey');
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<template>
2+
<div
3+
ref="tab"
4+
class="h-tabs"
5+
>
6+
<slot />
7+
8+
<div class="h-tabs__line" />
9+
</div>
10+
</template>
11+
12+
<script setup>
13+
import {
14+
provide,
15+
computed,
16+
reactive,
17+
ref,
18+
onMounted,
19+
onUnmounted,
20+
watch,
21+
} from 'vue';
22+
import { BTabsKey } from './tabs-key';
23+
24+
const $props = defineProps({
25+
modelValue: {
26+
type: [Number, String],
27+
default: undefined,
28+
},
29+
});
30+
31+
const $emits = defineEmits(['update:modelValue']);
32+
33+
const tab = ref();
34+
35+
const $state = reactive({
36+
lineWidth: undefined,
37+
});
38+
39+
const updatePositionLine = () => {
40+
const tabActiveRect = tab.value.getElementsByClassName('h-tab--behavior-active')?.[0]?.getBoundingClientRect();
41+
if (!tabActiveRect) {
42+
console.warn('Not found tab.');
43+
return;
44+
}
45+
const tabsWrapperRect = tab.value?.getBoundingClientRect();
46+
$state.lineWidth = `${tabActiveRect.width}px`;
47+
$state.lineTranslateX = `translateX(${tabActiveRect.left - tabsWrapperRect.left}px)`;
48+
};
49+
50+
const handleTabsChange = (newValue) => {
51+
$emits('update:modelValue', newValue);
52+
};
53+
54+
watch(() => $props.modelValue, () => {
55+
updatePositionLine();
56+
}, { flush: 'post' });
57+
58+
provide(BTabsKey, { handleTabsChange, currentTab: computed(() => $props.modelValue) });
59+
60+
onMounted(() => {
61+
updatePositionLine();
62+
window.addEventListener('resize', updatePositionLine, { passive: true });
63+
});
64+
65+
onUnmounted(() => {
66+
window.removeEventListener('resize', updatePositionLine);
67+
});
68+
</script>
69+
70+
<style lang="scss">
71+
.h-tabs {
72+
box-shadow: var(--shadow-e-002);
73+
width: 100%;
74+
min-height: 48px;
75+
display: flex;
76+
position: relative;
77+
overflow-x: auto;
78+
}
79+
80+
.h-tabs__line {
81+
position: absolute;
82+
bottom: 0;
83+
height: 2px;
84+
background-color: var(--color-white);
85+
width: v-bind('$state.lineWidth');
86+
transform: v-bind('$state.lineTranslateX');
87+
transition: transform 0.25s ease-in;
88+
}
89+
</style>

src/plugins/core-ui-plugin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
HForm,
3333
HSteps,
3434
HStep,
35+
HTabs,
36+
HTab,
3537
} from '@components/molecules';
3638

3739
import { ClickOutsidePlugin } from './click-outside-plugin';
@@ -77,6 +79,8 @@ const CoreUIPlugin = {
7779
Vue.component('HForm', HForm);
7880
Vue.component('HSteps', HSteps);
7981
Vue.component('HStep', HStep);
82+
Vue.component('HTabs', HTabs);
83+
Vue.component('HTab', HTab);
8084
},
8185
installPlugins(Vue) {
8286
Vue.use(ClickOutsidePlugin);

0 commit comments

Comments
 (0)