Skip to content

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={12} delay={400} formatValue={(x) => Math.round(x).toString()} />
          <ScreenReaderOnly>selected</ScreenReaderOnly>
        </Counter>
      </FilterTrigger.Addon>
    </Dropdown.Trigger>
    <Dropdown.Popper aria-label='Advanced filters' 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 SettingsM from '@semcore/icon/Settings/m';
import Button from '@semcore/ui/button';
import Counter from '@semcore/ui/counter';
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 and required resources'
        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}>
      Cats
      {' '}
      <Text color='text-secondary'>12,457</Text>
    </Text>
    <br />
    <Text size={300}>
      Dogs:
      {' '}
      <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 WarningM from '@semcore/icon/Warning/m';
import { Flex } from '@semcore/ui/base-components';
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 NotificationM from '@semcore/icon/Notification/m';
import Button from '@semcore/ui/button';
import { AnimatedNumber } from '@semcore/ui/counter';
import Dot from '@semcore/ui/dot';
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;

Released under the MIT License.

Released under the MIT License.