Skip to content

Commit

Permalink
feat(NcCounterBubble)!: add count prop and humanize output and remove…
Browse files Browse the repository at this point in the history
… slot

Signed-off-by: Grigorii K. Shartsev <[email protected]>
  • Loading branch information
ShGKme committed Aug 20, 2024
1 parent 127aefa commit 3e53785
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 17 deletions.
171 changes: 154 additions & 17 deletions src/components/NcCounterBubble/NcCounterBubble.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,143 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<docs>
<docs>

### Normal Counter
### Default usage

```
<NcCounterBubble>314+</NcCounterBubble>
```
NcCounterBubble displays a number from the `count` prop in a bubble.

### Outlined Counter (e.g team mentions)
By default, the number is **humanized** according to Nextcloud user's locale setting. Humanization can be disabled via `raw` prop.

```
<NcCounterBubble type="outlined">314+</NcCounterBubble>
```vue
<template>
<table>
<tr>
<th>count</th>
<th>default</th>
<th>raw</th>
</tr>
<tr v-for="num in numbers" :key="num">
<td>{{ num }}</td>
<td>
<NcCounterBubble :count="num" />
</td>
<td>
<NcCounterBubble :count="num" raw />
</td>
</tr>
</table>
</template>

<script>
export default {
setup() {
return {
numbers: [1, 9, 75, 450, 1042, 1750, 1999, 14567, 14567890, 2000000008],
}
},
}
</script>

<style scoped>
table {
border-collapse: collapse;
}
th,
td {
border: 1px solid var(--color-border);
padding: var(--default-grid-baseline) calc(var(--default-grid-baseline) * 2);
}
th {
color: var(--color-text-maxcontrast);
}
</style>
```

### Highlighted Counter (e.g direct mentions)
### Styles

Use different styles for different types of counters.

```
<NcCounterBubble type="highlighted">314+</NcCounterBubble>
<template>
<table>
<tr>
<th>type</th>
<th>counter</th>
<th>Usage example</th>
</tr>
<tr>
<td>'' (default)</td>
<td>
<NcCounterBubble :count="3" />
</td>
<td></td>
</tr>
<tr>
<td>outlined</td>
<td><NcCounterBubble :count="3" type="outlined" /></td>
<td>Team/group mentions</td>
</tr>
<tr>
<td>highlighted</td>
<td>
<NcCounterBubble :count="3" type="highlighted" />
</td>
<td>Direct mentions</td>
</tr>
<tr>
<td>outlined active</td>
<td class="active-like">
<NcCounterBubble :count="3" type="outlined" active />
</td>
<td>Same as "outlined", but in an "active" container</td>
</tr>
<tr>
<td>highlighted active</td>
<td class="active-like">
<NcCounterBubble :count="3" type="highlighted" active />
</td>
<td>Same as "highlighted", but in an "active" container</td>
</tr>
</table>
</template>

<style scoped>
table {
border-collapse: collapse;
}
th,
td {
border: 1px solid var(--color-border);
padding: var(--default-grid-baseline) calc(var(--default-grid-baseline) * 2);
&.active-like {
background-color: var(--color-primary-element);
}
}
th {
color: var(--color-text-maxcontrast);
}
</style>
```

### Custom content

In v8 it was possible ti to pass any custom content via the default slot. This feature has been removed. Use `count` prop for numbers or [NcChip](#/Components/NcChip) component for a custom content.
</docs>

<template>
<div :class="counterClassObject"
class="counter-bubble__counter">
<slot />
<div class="counter-bubble__counter" :class="counterClassObject">
{{ humanizeCount }}
</div>
</template>

<script>
import { getCanonicalLocale } from '@nextcloud/l10n'
export default {
name: 'NcCounterBubble',
Expand All @@ -42,7 +149,7 @@ export default {
type: String,
default: '',
validator(value) {
return ['highlighted', 'outlined', ''].indexOf(value) !== -1
return ['highlighted', 'outlined', ''].includes(value)
},
},
Expand All @@ -55,6 +162,24 @@ export default {
type: Boolean,
default: false,
},
/**
* The count to display in the counter bubble.
*/
count: {
type: Number,
required: false,
default: undefined,
},
/**
* Disables humanization to display count as it is.
*/
raw: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
Expand All @@ -65,6 +190,19 @@ export default {
active: this.active,
}
},
humanizedCount() {
if (this.raw) {
return this.count
}
const formatter = new Intl.NumberFormat(getCanonicalLocale(), {
notation: 'compact',
compactDisplay: 'short',
})
return formatter.format(this.count)
},
},
}
Expand All @@ -73,13 +211,11 @@ export default {
<style lang="scss" scoped>
.counter-bubble__counter {
--counter-bubble-line-height: 1em;
font-size: calc(var(--default-font-size) * .8);
font-size: var(--font-size-small, 13px);
overflow: hidden;
width: fit-content;
max-width: var(--default-clickable-area);
min-width: calc(var(--counter-bubble-line-height) + 2 * var(--default-grid-baseline)); // Make it not narrower than a circle
text-align: center;
text-overflow: ellipsis;
line-height: var(--counter-bubble-line-height);
padding: var(--default-grid-baseline);
border-radius: var(--border-radius-pill);
Expand Down Expand Up @@ -107,6 +243,7 @@ export default {
background: transparent;
box-shadow: inset 0 0 0 2px;
}
&--outlined.active {
color: var(--color-main-background);
box-shadow: inset 0 0 0 2px;
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/components/NcCounterBubble/NcCounterBubble.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { describe, it, expect } from '@jest/globals'
import { mount } from '@vue/test-utils'
import NcCounterBubble from '../../../../src/components/NcCounterBubble/NcCounterBubble.vue'

describe('NcCounterBubble', () => {
describe('displaying count', () => {
it('should render count from prop', () => {
const wrapper = mount(NcCounterBubble, { propsData: { count: 314 } })
expect(wrapper.text()).toBe('314')
})

it('should render non-number content as it is', () => {
const wrapper = mount(NcCounterBubble, { slots: { default: '14 rows' } })
expect(wrapper.text()).toBe('14 rows')
})
})

describe('with humanization', () => {
it('should render count 1020 with humanization as "1K"', () => {
const wrapper = mount(NcCounterBubble, { propsData: { count: 1042 } })
expect(wrapper.text()).toBe('1K')
})

it('should not humanize with raw', () => {
const wrapper = mount(NcCounterBubble, { propsData: { count: 1042, raw: true } })
expect(wrapper.text()).toBe('1042')
})

it('should render slot content 1020 with humanization as "1K"', () => {
const wrapper = mount(NcCounterBubble, { slots: { default: '1042' } })
expect(wrapper.text()).toBe('1K')
})

it('should render slot content 1020 as it is with raw prop', () => {
const wrapper = mount(NcCounterBubble, { propsData: { raw: true }, slots: { default: '1042' } })
expect(wrapper.text()).toBe('1042')
})
})

describe('with styling', () => {
it('should not have any additional classes', () => {
const wrapper = mount(NcCounterBubble)
expect(wrapper.classes('counter-bubble__counter--highlighted')).toBeFalsy()
expect(wrapper.classes('counter-bubble__counter--outlined')).toBeFalsy()
expect(wrapper.classes('active')).toBeFalsy()
})

it('should have class "counter-bubble__counter--highlighted" when type="highlighted"', () => {
const wrapper = mount(NcCounterBubble, { propsData: { type: 'highlighted' } })
expect(wrapper.classes('counter-bubble__counter--highlighted')).toBeTruthy()
})

it('should have class "counter-bubble__counter--outlined" when type="outlined"', () => {
const wrapper = mount(NcCounterBubble, { propsData: { type: 'outlined' } })
expect(wrapper.classes('counter-bubble__counter--outlined')).toBeTruthy()
})

it('should have class "active" when active', () => {
const wrapper = mount(NcCounterBubble, { propsData: { active: true } })
expect(wrapper.classes('active')).toBeTruthy()
})
})
})

0 comments on commit 3e53785

Please sign in to comment.