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 React from 'react';
import Counter, { AnimatedNumber } from 'intergalactic/counter';
import { FilterTrigger } from 'intergalactic/base-trigger';

const Demo = () => (
  <FilterTrigger>
    <FilterTrigger.Text>Link to website</FilterTrigger.Text>
    <FilterTrigger.Addon>
      <Counter theme='info'>
        <AnimatedNumber value={500} delay={1000} formatValue={(x) => Math.round(x).toString()} />
      </Counter>
    </FilterTrigger.Addon>
  </FilterTrigger>
);

export default Demo;
import React from 'react';
import Counter, { AnimatedNumber } from 'intergalactic/counter';
import { FilterTrigger } from 'intergalactic/base-trigger';

const Demo = () => (
  <FilterTrigger>
    <FilterTrigger.Text>Link to website</FilterTrigger.Text>
    <FilterTrigger.Addon>
      <Counter theme='info'>
        <AnimatedNumber value={500} delay={1000} formatValue={(x) => Math.round(x).toString()} />
      </Counter>
    </FilterTrigger.Addon>
  </FilterTrigger>
);

export default Demo;

Counter in Button

TIP

Don't forget to place counters inside the Addon to create correct margins.

tsx
import React from 'react';
import Counter from 'intergalactic/counter';
import Button from 'intergalactic/button';
import SettingsM from 'intergalactic/icon/Settings/m';

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;
import React from 'react';
import Counter from 'intergalactic/counter';
import Button from 'intergalactic/button';
import SettingsM from 'intergalactic/icon/Settings/m';

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 React from 'react';
import { Flex } from 'intergalactic/flex-box';
import { Text } from 'intergalactic/typography';
import Textarea from 'intergalactic/textarea';
import Counter from 'intergalactic/counter';
import { ScreenReaderOnly } from 'intergalactic/utils/lib/ScreenReaderOnly';

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;
import React from 'react';
import { Flex } from 'intergalactic/flex-box';
import { Text } from 'intergalactic/typography';
import Textarea from 'intergalactic/textarea';
import Counter from 'intergalactic/counter';
import { ScreenReaderOnly } from 'intergalactic/utils/lib/ScreenReaderOnly';

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 React from 'react';
import { Text } from 'intergalactic/typography';

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;
import React from 'react';
import { Text } from 'intergalactic/typography';

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 React from 'react';
import Pills from 'intergalactic/pills';
import { Text } from 'intergalactic/typography';

const Demo = () => (
  <Pills defaultValue='all'>
    <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='not-follow'>
      <Pills.Item.Text>Not Follow</Pills.Item.Text>
      <Pills.Item.Addon>
        <Text color='text-secondary'>736</Text>
      </Pills.Item.Addon>
    </Pills.Item>
  </Pills>
);

export default Demo;
import React from 'react';
import Pills from 'intergalactic/pills';
import { Text } from 'intergalactic/typography';

const Demo = () => (
  <Pills defaultValue='all'>
    <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='not-follow'>
      <Pills.Item.Text>Not Follow</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 React from 'react';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';
import ProgressBar from 'intergalactic/progress-bar';
import WarningM from 'intergalactic/icon/Warning/m';

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;
import React from 'react';
import { Text } from 'intergalactic/typography';
import { Flex } from 'intergalactic/flex-box';
import ProgressBar from 'intergalactic/progress-bar';
import WarningM from 'intergalactic/icon/Warning/m';

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 React from 'react';
import Button from 'intergalactic/button';
import NotificationM from 'intergalactic/icon/Notification/m';
import Dot from 'intergalactic/dot';
import { AnimatedNumber } from 'intergalactic/counter';
import { Hint } from 'intergalactic/tooltip';

const notificationsCount = 18;

const Demo = () => (
  <Hint tag={Button} title={`${notificationsCount} notifications`}>
    <Button.Addon>
      <NotificationM />
      <Dot up>
        <AnimatedNumber
          initValue={10}
          value={notificationsCount}
          duration={1000}
          delay={500}
          formatValue={(x) => Math.round(x).toString()}
        />
      </Dot>
    </Button.Addon>
  </Hint>
);

export default Demo;
import React from 'react';
import Button from 'intergalactic/button';
import NotificationM from 'intergalactic/icon/Notification/m';
import Dot from 'intergalactic/dot';
import { AnimatedNumber } from 'intergalactic/counter';
import { Hint } from 'intergalactic/tooltip';

const notificationsCount = 18;

const Demo = () => (
  <Hint tag={Button} title={`${notificationsCount} notifications`}>
    <Button.Addon>
      <NotificationM />
      <Dot up>
        <AnimatedNumber
          initValue={10}
          value={notificationsCount}
          duration={1000}
          delay={500}
          formatValue={(x) => Math.round(x).toString()}
        />
      </Dot>
    </Button.Addon>
  </Hint>
);

export default Demo;

Animated number

The AnimatedNumber component allows showing numeric value changes with animation.

tsx
import React from 'react';
import { AnimatedNumber } from 'intergalactic/counter';
import Button from 'intergalactic/button';

const Demo = () => {
  const [value, setValue] = React.useState(20);
  const handleClick = () => {
    setValue(value + 20);
  };

  return (
    <>
      <AnimatedNumber value={value} />
      <Button onClick={handleClick} mt={2}>
        Rerender value
      </Button>
    </>
  );
};

export default Demo;
import React from 'react';
import { AnimatedNumber } from 'intergalactic/counter';
import Button from 'intergalactic/button';

const Demo = () => {
  const [value, setValue] = React.useState(20);
  const handleClick = () => {
    setValue(value + 20);
  };

  return (
    <>
      <AnimatedNumber value={value} />
      <Button onClick={handleClick} mt={2}>
        Rerender value
      </Button>
    </>
  );
};

export default Demo;

Released under the MIT License.

Released under the MIT License.