Skip to content

Commit 9a2d752

Browse files
committed
2 parents 4680ab6 + 8c82e4f commit 9a2d752

File tree

5 files changed

+351
-1
lines changed

5 files changed

+351
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sysvale/cuida",
3-
"version": "3.136.1",
3+
"version": "3.137.1",
44
"description": "A design system built by Sysvale, using storybook and Vue components",
55
"repository": {
66
"type": "git",
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<template>
2+
<div
3+
ref="truncateContainerEl"
4+
class="truncate-container"
5+
>
6+
<div class="content">
7+
<!-- @slot Slot padrão utilizado para exibir o conteúdo enviado para o container -->
8+
<slot />
9+
</div>
10+
11+
<div
12+
v-if="isOverflowing && !expanded"
13+
class="fade-overlay"
14+
/>
15+
16+
<CdsFlexbox :justify="computedTextAlign">
17+
<CdsFlatButton
18+
class="truncate-container__button"
19+
:text="computedText"
20+
:variant
21+
@click="handleFlatButtonClick"
22+
/>
23+
</CdsFlexbox>
24+
</div>
25+
</template>
26+
27+
<script setup>
28+
import { ref, computed, nextTick, watch } from 'vue';
29+
import CdsFlatButton from './FlatButton.vue';
30+
import CdsFlexbox from './Flexbox.vue';
31+
32+
const props = defineProps({
33+
/**
34+
* Altura do container.
35+
*/
36+
height: {
37+
type: String,
38+
default: '250px'
39+
},
40+
/**
41+
* Define o alinhamento do texto.
42+
*/
43+
textAlign: {
44+
type: String,
45+
default: 'center',
46+
},
47+
/**
48+
* A variante da FlatButton. São 9 variantes.
49+
* @values green, teal, turquoise, blue, indigo, violet, pink, red, orange, amber e dark.
50+
*/
51+
variant: {
52+
type: String,
53+
default: 'dark',
54+
},
55+
});
56+
57+
const emits = defineEmits([
58+
/**
59+
* Evento emitido quando o FlatButton do TruncateContainer é clicado.
60+
* @event button-click
61+
* @type {Event}
62+
*/
63+
'button-click',
64+
/**
65+
* Evento emitido o TruncateContainer é expandido.
66+
* @event expand
67+
* @type {Event}
68+
*/
69+
'expand',
70+
/**
71+
* Evento emitido o TruncateContainer é colapsado.
72+
* @event collapse
73+
* @type {Event}
74+
*/
75+
'collapse'
76+
]);
77+
78+
const isOverflowing = ref(false);
79+
const expanded = ref(false);
80+
const truncateContainerEl = ref(null);
81+
82+
const computedHeight = computed(() => (
83+
expanded.value ? `${truncateContainerEl.value.scrollHeight}px` : `${props.height.replace('px', '')}px`
84+
));
85+
86+
const computedText = computed(() => expanded.value ? 'Mostrar menos' : 'Mostrar mais');
87+
const computedTextAlign = computed(() => props.textAlign);
88+
89+
watch(() => props.height, () => {
90+
nextTick(() => {
91+
checkOverflow();
92+
});
93+
}, { immediate: true });
94+
95+
watch(expanded, () => expanded.value ? emits('expand') : emits('collapse'));
96+
97+
98+
function checkOverflow() {
99+
const el = truncateContainerEl.value;
100+
if (!el) return;
101+
102+
isOverflowing.value = el.scrollHeight > el.clientHeight;
103+
}
104+
105+
function handleFlatButtonClick() {
106+
expanded.value = !expanded.value;
107+
emits('button-click')
108+
}
109+
</script>
110+
111+
<style lang="scss" scoped>
112+
@use '../assets/sass/tokens/index' as tokens;
113+
114+
.truncate-container {
115+
position: relative;
116+
height: v-bind(computedHeight);
117+
overflow: hidden;
118+
transition: height 0.25s ease-in-out;
119+
}
120+
121+
.fade-overlay {
122+
position: absolute;
123+
bottom: 0;
124+
left: 0;
125+
right: 0;
126+
height: 48px;
127+
pointer-events: none;
128+
background: linear-gradient(
129+
to bottom,
130+
rgba(255, 255, 255, 0.0) 0%,
131+
rgba(255, 255, 255, 0.5) 50%,
132+
rgba(255, 255, 255, 0.7) 75%,
133+
rgba(255, 255, 255, 0.9) 100%
134+
);
135+
backdrop-filter: blur(1px);
136+
}
137+
138+
.truncate-container__button {
139+
position: absolute;
140+
bottom: 8px;
141+
margin: tokens.mx(3);
142+
}
143+
</style>
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
2+
import TruncateContainer from '../../components/TruncateContainer.vue';
3+
4+
<Meta
5+
title="Componentes/Containers/TruncateContainer"
6+
component={ TruncateContainer }
7+
argTypes={{
8+
variant: {
9+
control:{
10+
type: 'select',
11+
options: [
12+
'green',
13+
'teal',
14+
'turquoise',
15+
'blue',
16+
'indigo',
17+
'violet',
18+
'pink',
19+
'red',
20+
'orange',
21+
'amber',
22+
'dark',
23+
],
24+
}
25+
},
26+
textAlign: {
27+
control:{
28+
type: 'select',
29+
options: [
30+
'start',
31+
'center',
32+
'end',
33+
],
34+
}
35+
},
36+
}}
37+
parameters={{
38+
viewMode: 'docs',
39+
previewTabs: { canvas: { hidden: true }},
40+
docs: {
41+
source: {
42+
language: 'html',
43+
format:true,
44+
code: /*html*/
45+
`<CdsTruncateContainer
46+
height="250px"
47+
text-align="center"
48+
variant="dark"
49+
>
50+
<ul>
51+
<li v-for="n in 25">List Item {{n}}</li>
52+
</ul>
53+
</CdsTruncateContainer>`
54+
},
55+
}
56+
}}
57+
/>
58+
59+
export const Template = (args) => ({
60+
components: { CdsTruncateContainer: TruncateContainer },
61+
setup() {
62+
return { args };
63+
},
64+
template: /*html*/ `
65+
<CdsTruncateContainer
66+
v-bind="args"
67+
@button-click="logClick"
68+
@expand="logExpand"
69+
@collapse="logCollapse"
70+
>
71+
<ul>
72+
<li v-for="n in 25">List Item {{n}}</li>
73+
</ul>
74+
</CdsTruncateContainer>`,
75+
methods: {
76+
logClick(event) {
77+
console.info('⚡ %cButtonClick emitted: ', 'color: hsl(152, 77.20%, 51.80%);', event);
78+
},
79+
logExpand(event) {
80+
console.info('⚡ %cExpand emitted: ', 'color: hsl(152, 77.20%, 51.80%);', event);
81+
},
82+
logCollapse(event) {
83+
console.info('⚡ %cCollapse emitted: ', 'color: hsl(152, 77.20%, 51.80%);', event);
84+
},
85+
}
86+
});
87+
88+
89+
# TruncateContainer
90+
91+
### TruncateContainer é um componente utilizado para controlar a visibilidade de conteúdos longos.
92+
---
93+
<br />
94+
95+
## Quando usar:
96+
- Quando o conteúdo pode ser muito grande e você quer limitar a altura inicial.
97+
- Para indicar visualmente que há mais conteúdo disponível para leitura.
98+
99+
100+
<br />
101+
102+
## Quando não usar:
103+
- Quando todo o conteúdo precisa estar visível imediatamente.
104+
- Se não houver necessidade de interação do usuário para expandir o conteúdo.
105+
106+
<br />
107+
108+
## Preview
109+
110+
<Canvas>
111+
<Story
112+
name="TruncateContainer"
113+
args={{
114+
variant: 'dark',
115+
textAlign: 'center',
116+
height: '250',
117+
}}
118+
>
119+
{ Template.bind({}) }
120+
</Story>
121+
</Canvas>
122+
123+
<ArgsTable story="TruncateContainer" />
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { mount } from '@vue/test-utils';
2+
import { describe, test, expect } from 'vitest';
3+
import TruncateContainer from '../components/TruncateContainer.vue';
4+
import CdsFlatButton from '../components/FlatButton.vue';
5+
6+
describe('TruncateContainer.vue', () => {
7+
test('renders correctly', () => {
8+
const wrapper = mount(TruncateContainer, {
9+
slots: {
10+
default: '<p>Some content inside the container</p>'
11+
}
12+
});
13+
expect(wrapper.html()).toMatchSnapshot();
14+
});
15+
16+
test('displays "Mostrar mais" button by default', () => {
17+
const wrapper = mount(TruncateContainer, {
18+
slots: {
19+
default: '<p>Content</p>'
20+
}
21+
});
22+
const button = wrapper.findComponent(CdsFlatButton);
23+
expect(button.exists()).toBe(true);
24+
expect(button.props('text')).toBe('Mostrar mais');
25+
});
26+
27+
test('emits "button-click" and toggles expanded state when button is clicked', async () => {
28+
const wrapper = mount(TruncateContainer, {
29+
slots: {
30+
default: '<p>Content</p>'
31+
}
32+
});
33+
const button = wrapper.findComponent(CdsFlatButton);
34+
await button.trigger('click');
35+
36+
expect(wrapper.emitted()).toHaveProperty('button-click');
37+
expect(wrapper.vm.expanded).toBe(true);
38+
39+
await button.trigger('click');
40+
expect(wrapper.vm.expanded).toBe(false);
41+
});
42+
43+
test('emits "expand" and "collapse" when expanded changes', async () => {
44+
const wrapper = mount(TruncateContainer, {
45+
slots: {
46+
default: '<p>Content</p>'
47+
}
48+
});
49+
50+
wrapper.vm.expanded = true;
51+
await wrapper.vm.$nextTick();
52+
expect(wrapper.emitted()).toHaveProperty('expand');
53+
54+
wrapper.vm.expanded = false;
55+
await wrapper.vm.$nextTick();
56+
expect(wrapper.emitted()).toHaveProperty('collapse');
57+
});
58+
59+
test('computes textAlign correctly', () => {
60+
const wrapper = mount(TruncateContainer, {
61+
props: { textAlign: 'right' },
62+
slots: {
63+
default: '<p>Content</p>'
64+
}
65+
});
66+
expect(wrapper.vm.computedTextAlign).toBe('right');
67+
});
68+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`TruncateContainer.vue > renders correctly 1`] = `
4+
"<div data-v-f5ae6e5e="" class="truncate-container">
5+
<div data-v-f5ae6e5e="" class="content">
6+
<!-- @slot Slot padrão utilizado para exibir o conteúdo enviado para o container -->
7+
<p>Some content inside the container</p>
8+
</div>
9+
<!--v-if-->
10+
<div data-v-c2ac4e6d="" data-v-f5ae6e5e="" class="flexbox">
11+
<!-- @slot Slot com o conteúdo interno do FlexBox --><button data-v-4a94b6f3="" data-v-f5ae6e5e="" class="flat-button__container flat-button--dark--active truncate-container__button">
12+
<!-- @slot Slot padrão utilizado para exibir texto do botão. -->Mostrar mais
13+
</button>
14+
</div>
15+
</div>"
16+
`;

0 commit comments

Comments
 (0)