Counter
Counter in filters
FilterTrigger is normally used together with Select or Dropdown. Go to the guide for more information.
tsx
import { ScreenReaderOnly } from '@semcore/ui/base-components';
import { FilterTrigger } from '@semcore/ui/base-trigger';
import Counter, { AnimatedNumber } from '@semcore/ui/counter';
import Dropdown from '@semcore/ui/dropdown';
import React from 'react';
const Demo = () => (
<Dropdown>
<Dropdown.Trigger aria-label='Link to website' tag={FilterTrigger}>
<FilterTrigger.Text aria-hidden>Link to website</FilterTrigger.Text>
<FilterTrigger.Addon>
<Counter theme='info'>
<AnimatedNumber value={500} delay={1000} formatValue={(x) => Math.round(x).toString()} />
<ScreenReaderOnly>selected</ScreenReaderOnly>
</Counter>
</FilterTrigger.Addon>
</Dropdown.Trigger>
<Dropdown.Popper aria-label='Link to website' p={4}>
Filter content
</Dropdown.Popper>
</Dropdown>
);
export default Demo;
Counter in Button
TIP
Don't forget to place counters inside the Addon to create correct margins.
tsx
import Button from '@semcore/ui/button';
import Counter from '@semcore/ui/counter';
import SettingsM from '@semcore/ui/icon/Settings/m';
import React from 'react';
const Demo = () => (
<>
<Button mr={4}>
<Button.Addon>
<SettingsM />
</Button.Addon>
<Button.Text>Manage columns</Button.Text>
<Button.Addon>
<Counter>23</Counter>
</Button.Addon>
</Button>
<Button use='primary'>
<Button.Addon>
<SettingsM />
</Button.Addon>
<Button.Text>Manage columns</Button.Text>
<Button.Addon>
<Counter theme='bg-primary-neutral'>23</Counter>
</Button.Addon>
</Button>
</>
);
export default Demo;
Counter in forms
As the design guide recommends, the counter changes color to orange shortly before the limit is reached, and then to red when the limit is exceeded.
tsx
import { Flex, ScreenReaderOnly } from '@semcore/ui/base-components';
import Counter from '@semcore/ui/counter';
import Textarea from '@semcore/ui/textarea';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const maxSymbols = 150;
const Demo = () => {
const [value, setValue] = React.useState('');
const [valueLength, setValueLength] = React.useState(0);
const [theme, setTheme] = React.useState<string>('');
const handleChange = React.useCallback((value: string) => {
setValue(value);
}, []);
const valueTimer = React.useRef<number>();
React.useEffect(() => {
if (valueTimer.current) {
window.clearTimeout(valueTimer.current);
}
valueTimer.current = window.setTimeout(() => {
setValueLength(value.length);
}, 1000);
}, [value]);
React.useEffect(() => {
if (value.length >= 140) {
if (value.length <= maxSymbols) {
setTheme('warning');
} else {
setTheme('danger');
}
} else {
setTheme('');
}
}, [value]);
return (
<Flex direction='column' w={350}>
<Flex mb={2} justifyContent='space-between'>
<Flex alignItems='center'>
<Text size={200} tag='label' htmlFor='limited-text-field'>
Project description
</Text>
<Counter ml={1} theme={theme} id='counter-for-textarea'>
{value.length}
<span aria-hidden='true'>/</span>
<ScreenReaderOnly>of</ScreenReaderOnly>
{maxSymbols}
<ScreenReaderOnly>allowed characters</ScreenReaderOnly>
{theme === 'warning' && <ScreenReaderOnly>Limit is almost reached</ScreenReaderOnly>}
{theme === 'danger' && <ScreenReaderOnly>Limit is exceeded</ScreenReaderOnly>}
</Counter>
</Flex>
<Text size={200} color='text-secondary' id='optional-for-textarea'>
optional
</Text>
</Flex>
<Textarea
placeholder='The goal of your project, required resources, and so on'
id='limited-text-field'
aria-describedby='optional-for-textarea counter-for-textarea'
onChange={handleChange}
/>
<ScreenReaderOnly aria-live='polite' aria-atomic={true}>
{valueLength}
{' '}
of
{maxSymbols}
{' '}
allowed characters
{valueLength >= 140 && valueLength <= 150 && (
<ScreenReaderOnly>Limit is almost reached</ScreenReaderOnly>
)}
{valueLength > 150 && <ScreenReaderOnly>Limit is exceeded</ScreenReaderOnly>}
</ScreenReaderOnly>
</Flex>
);
};
export default Demo;
Counter and typography
Plain text counters should be implemented using Typography, without the Counter component.
tsx
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => (
<>
<Text size={300}>
Lorem ipsum
{' '}
<Text color='text-secondary'>12,457</Text>
</Text>
<br />
<Text size={300}>
Dolor sit amet:
{' '}
<Text color='text-secondary'>149</Text>
</Text>
</>
);
export default Demo;
Counter in Pills
Counters inside Pills are implemented using Typography, without the Counter component.
tsx
import Pills from '@semcore/ui/pills';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => (
<Pills defaultValue='all' aria-label='Pills with counters'>
<Pills.Item value='all'>
<Pills.Item.Text>All</Pills.Item.Text>
<Pills.Item.Addon>
<Text color='text-secondary'>1,259</Text>
</Pills.Item.Addon>
</Pills.Item>
<Pills.Item value='follow'>
<Pills.Item.Text>Follow</Pills.Item.Text>
<Pills.Item.Addon>
<Text color='text-secondary'>557</Text>
</Pills.Item.Addon>
</Pills.Item>
<Pills.Item value='nofollow'>
<Pills.Item.Text>Nofollow</Pills.Item.Text>
<Pills.Item.Addon>
<Text color='text-secondary'>736</Text>
</Pills.Item.Addon>
</Pills.Item>
</Pills>
);
export default Demo;
Counter in limits
Displaying limits is done using Typography, without the Counter component.
tsx
import { Flex } from '@semcore/ui/base-components';
import WarningM from '@semcore/ui/icon/Warning/m';
import ProgressBar from '@semcore/ui/progress-bar';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const limitsMax = 10;
const limitsUsed = 10;
const warning = limitsUsed >= limitsMax;
const Demo = () => (
<Flex direction='column' w={350}>
<Flex mb={1} justifyContent='space-between'>
<Text size={200}>SEO Ideas Units</Text>
<Flex alignItems='center'>
{warning ? <WarningM color='icon-primary-warning' /> : null}
<Text size={200} ml={1} bold aria-hidden>
{limitsUsed}
<Text color='text-secondary'>
/
{limitsMax}
</Text>
</Text>
</Flex>
</Flex>
<ProgressBar
value={(limitsUsed / limitsMax) * 100}
aria-valuetext={`${limitsUsed} out of ${limitsMax}`}
aria-label={`limits used ${warning ? ', warning' : ''}`}
size='s'
>
<ProgressBar.Value theme={warning ? 'bg-primary-warning' : 'bg-primary-success'} />
</ProgressBar>
</Flex>
);
export default Demo;
Counter in Dot
The Dot component also contains a text counter. For more information, refer to Dot.
tsx
import Button from '@semcore/ui/button';
import { AnimatedNumber } from '@semcore/ui/counter';
import Dot from '@semcore/ui/dot';
import NotificationM from '@semcore/ui/icon/Notification/m';
import React from 'react';
const notificationsCount = 18;
const Demo = () => (
<Button title='Notifications' aria-describedby='notification-count'>
<Button.Addon>
<NotificationM />
<Dot up>
<AnimatedNumber
initValue={10}
value={notificationsCount}
duration={1000}
delay={500}
formatValue={(x) => Math.round(x).toString()}
id='notification-count'
/>
</Dot>
</Button.Addon>
</Button>
);
export default Demo;
Animated number
The AnimatedNumber component allows showing numeric value changes with animation.
tsx
import Button from '@semcore/ui/button';
import { AnimatedNumber } from '@semcore/ui/counter';
import React from 'react';
type AnimatedNumberBaseProps = { duration?: number; delay?: number };
const Demo = (props: AnimatedNumberBaseProps) => {
const [value, setValue] = React.useState(20);
const handleClick = () => {
setValue(value + 20);
};
return (
<>
<AnimatedNumber value={value} duration={props.duration} delay={props.delay} />
<Button onClick={handleClick} mt={2}>
Rerender value
</Button>
</>
);
};
export const defaultProps: AnimatedNumberBaseProps = {
duration: undefined,
delay: undefined,
};
Demo.defaultProps = defaultProps;
export default Demo;