utils#formatNumberWithCommas TypeScript Examples

The following examples show how to use utils#formatNumberWithCommas. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx    From exevo-pan with The Unlicense 6 votes vote down vote up
AuctionBid = ({ hasBeenBidded, currentBid, past }: AuctionBidProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const bidLabelText = useMemo(() => {
    if (past) {
      return hasBeenBidded
        ? common.CharacterCard.bidLabelText.auctionSuccessful
        : common.CharacterCard.bidLabelText.auctionFailed
    }
    return hasBeenBidded
      ? common.CharacterCard.bidLabelText.currentBid
      : common.CharacterCard.bidLabelText.minimumBid
  }, [past, hasBeenBidded, common])

  return (
    <LabeledTextBox labelText={bidLabelText}>
      <Icons.TibiaCoin />
      {formatNumberWithCommas(currentBid)}
    </LabeledTextBox>
  )
}
Example #2
Source File: Achievements.tsx    From exevo-pan with The Unlicense 6 votes vote down vote up
CharacterAchievements = ({
  achievementPoints,
  ...props
}: CharacterAchievementsProps) => (
  <S.TitleWrapper {...props}>
    <S.Icons.Achievements />
    Achievement points: {formatNumberWithCommas(achievementPoints)}
  </S.TitleWrapper>
)
Example #3
Source File: Charms.tsx    From exevo-pan with The Unlicense 6 votes vote down vote up
CharacterCharms = ({
  charmInfo,
  items,
  placement,
  ...props
}: CharacterCharmsProps) => {
  const {
    translations: { common },
  } = useTranslations()

  return (
    <Tooltip
      aria-label={common.CharacterCard.Tooltips.labels.charms}
      content={
        <Lister maxLines={MAX_LINES} partialList={items} fullList={tokens} />
      }
      placement={placement}
    >
      <S.TitleWrapper {...props}>
        <S.Icons.Charm />
        Charms: {items.length}/{tokens.length}
        {charmInfo && (
          <>
            {' '}
            (
            <strong style={{ marginRight: 3 }}>
              {formatNumberWithCommas(charmInfo.total)}
            </strong>{' '}
            total points,
            <strong style={{ margin: '0 3px' }}>
              {formatNumberWithCommas(charmInfo.unspent)}
            </strong>{' '}
            unspent)
          </>
        )}
      </S.TitleWrapper>
    </Tooltip>
  )
}
Example #4
Source File: utils.ts    From exevo-pan with The Unlicense 6 votes vote down vote up
formatDiffValue = (value: number): string =>
  `${getNumberSign(value)}${formatNumberWithCommas(Math.abs(value))}`
Example #5
Source File: TibiaCoin.tsx    From exevo-pan with The Unlicense 6 votes vote down vote up
TibiaCoin = ({ value, ...props }: TibiaCoinProps) => {
  const formattedValue = formatNumberWithCommas(value)

  return (
    <S.Flex title={`${formattedValue} Tibia Coins`} {...props}>
      <Image
        src={tibiaCoinSrc}
        alt="Tibia Coin"
        unoptimized
        width={12}
        height={12}
      />
      {formattedValue}
    </S.Flex>
  )
}
Example #6
Source File: index.tsx    From exevo-pan with The Unlicense 6 votes vote down vote up
Scoreboard = ({
  guildA,
  guildB,
  className,
  ...props
}: ScoreboardProps) => (
  <section
    className={clsx('card p-5 text-center transition-colors', className)}
    {...props}
  >
    <div className="flex flex-col items-center justify-center gap-6 md:flex-row md:gap-[132px] lg:flex-col lg:gap-12">
      <GuildSummary
        guildName={guildA.name}
        href={guildA.href}
        displayValue={formatNumberWithCommas(guildA.kills)}
        diffText={formatDiffValue(guildA.diff)}
        winning={guildA.kills >= guildB.kills}
        label="Kills"
      />

      <GuildSummary
        guildName={guildB.name}
        href={guildB.href}
        displayValue={formatNumberWithCommas(guildB.kills)}
        diffText={formatDiffValue(guildB.diff)}
        winning={guildB.kills >= guildA.kills}
        label="Kills"
      />
    </div>
  </section>
)
Example #7
Source File: utils.ts    From exevo-pan with The Unlicense 6 votes vote down vote up
formatDisplayValue = (value: number): string =>
  `${getNumberSign(value)}${formatNumberWithCommas(Math.abs(value))}`
Example #8
Source File: index.tsx    From exevo-pan with The Unlicense 5 votes vote down vote up
describe('<List />', () => {
  test('should render all head content', () => {
    const { container } = renderWithProviders(
      <List title="Level" charactersList={mockData} displayedDataKey="level" />,
    )

    expect(screen.getByRole('heading', { name: 'Level' })).toBeInTheDocument()
    const [rankingPosition, nickname, value] =
      container.querySelectorAll('table thead tr th')

    expect(rankingPosition).toHaveAttribute('aria-label', 'Ranking position')
    expect(rankingPosition).toHaveTextContent('#')

    expect(nickname).toHaveTextContent('Nickname')

    expect(value).toHaveTextContent('Level')
  })

  test('should render every list item', () => {
    const { container } = renderWithProviders(
      <List title="Level" charactersList={mockData} displayedDataKey="level" />,
    )

    container.querySelectorAll('table tbody tr').forEach((row, index) => {
      const [rankingPosition, nicknameLink, value] = row.querySelectorAll('td')
      const currentCharacter = mockData[index]

      expect(rankingPosition).toHaveTextContent((index + 1).toString())
      expect(nicknameLink).toHaveTextContent(currentCharacter.nickname)
      expect(value).toHaveTextContent(
        currentCharacter.level as unknown as string,
      )
    })
  })

  test('should render every list item with formatted values', () => {
    const { container } = renderWithProviders(
      <List
        title="Level"
        charactersList={mockData}
        displayedDataKey="level"
        format={formatNumberWithCommas}
      />,
    )

    container.querySelectorAll('table tbody tr').forEach((row, index) => {
      const [rankingPosition, nicknameLink, value] = row.querySelectorAll('td')
      const currentCharacter = mockData[index]

      expect(rankingPosition).toHaveTextContent((index + 1).toString())
      expect(nicknameLink).toHaveTextContent(currentCharacter.nickname)
      expect(value).toHaveTextContent(
        formatNumberWithCommas(currentCharacter.level as unknown as number),
      )
    })
  })
})
Example #9
Source File: index.tsx    From exevo-pan with The Unlicense 5 votes vote down vote up
HighscoresGrid = ({
  statisticsData,
  className,
  style,
  ...props
}: HighscoresGridProps) => {
  const {
    translations: { statistics },
  } = useTranslations()

  return (
    <div
      className={clsx('inner-container grid gap-6 py-4', className)}
      style={{
        gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
        ...style,
      }}
      {...props}
    >
      <h2 className="hidden">{statistics.HighscoresGrid.title}</h2>
      <List
        title={statistics.HighscoresGrid.top10BidTitle}
        charactersList={statisticsData.top10Bid}
        displayedDataKey="currentBid"
        format={useCallback(
          (value: number) => `${formatNumberWithCommas(value)} TC`,
          [],
        )}
      />
      <List
        title="Level"
        charactersList={statisticsData.top10Level}
        displayedDataKey="level"
        format={formatNumberWithCommas}
      />
      <List
        title="Magic"
        charactersList={statisticsData.top10Magic}
        displayedDataKey="magic"
      />
      <List
        title="Distance"
        charactersList={statisticsData.top10Distance}
        displayedDataKey="distance"
      />
      <List
        title="Sword"
        charactersList={statisticsData.top10Sword}
        displayedDataKey="sword"
      />
      <List
        title="Axe"
        charactersList={statisticsData.top10Axe}
        displayedDataKey="axe"
      />
      <List
        title="Club"
        charactersList={statisticsData.top10Club}
        displayedDataKey="club"
      />
      <List
        title="Fist"
        charactersList={statisticsData.top10Fist}
        displayedDataKey="fist"
      />
      <List
        title="Shielding"
        charactersList={statisticsData.top10Shielding}
        displayedDataKey="shielding"
      />
      <List
        title="Fishing"
        charactersList={statisticsData.top10Fishing}
        displayedDataKey="fishing"
      />
    </div>
  )
}
Example #10
Source File: index.tsx    From exevo-pan with The Unlicense 5 votes vote down vote up
GuildXPGrid = ({ warData }: GuildXPGridProps) => {
  const {
    translations: { war },
  } = useTranslations()

  const {
    xpStats: { todayDiff, dailyXPDiff, currentXP },
  } = warData

  return (
    <article className="inner-container custom-scrollbar bg-background relative grid max-h-[calc(100%-44px)] gap-4 overflow-auto py-4 transition-colors">
      <h2 className="hidden">{war.PageTitle}</h2>

      <div className="child:w-full flex flex-wrap gap-4">
        <ScoreboardXP
          guildA={{
            name: 'Libertabra Pune',
            todayDiff: todayDiff.guildA,
            href: 'https://www.tibia.com/community/?subtopic=guilds&page=view&order=level_desc&GuildName=Libertabra%20Pune&onlyshowonline=0',
          }}
          guildB={{
            name: 'Bones Alliance',
            todayDiff: todayDiff.guildB,
            href: 'https://www.tibia.com/community/?subtopic=guilds&page=view&order=level_desc&GuildName=Bones%20Alliance&onlyshowonline=0',
          }}
        />

        <ComparisonChart
          title={war.GuildXPGrid.comparisonChartTitle}
          guildA={{
            name: 'Libertabra Pune',
            summaryValue: `${formatNumberWithCommas(
              Math.trunc(currentXP.guildA / 1000000),
            )}M ${war.GuildXPGrid.guildSummaryValueSuffix}`,
            dataArray: xpToDataSnapshot(dailyXPDiff.guildA),
          }}
          guildB={{
            name: 'Bones Alliance',
            summaryValue: `${formatNumberWithCommas(
              Math.trunc(currentXP.guildB / 1000000),
            )}M ${war.GuildXPGrid.guildSummaryValueSuffix}`,
            dataArray: xpToDataSnapshot(dailyXPDiff.guildB),
          }}
          tooltipSuffix={war.GuildXPGrid.comparisonChartSuffix}
          dateLabelType="Date"
        />
      </div>
    </article>
  )
}
Example #11
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
CharacterCard = ({
  characterData,
  highlighted = false,
  lazyRender = false,
  expandable = false,
  past = false,
  ...props
}: CharacterCardProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const {
    id,
    nickname,
    outfitId,
    level,
    vocationId,
    serverData,
    transfer,
    auctionEnd,
    hasBeenBidded,
    currentBid,
    items,
    skills,
    imbuements,
    charms,
    quests,
    charmInfo,
    preySlot,
  } = characterData

  const tcInvested = useMemo(
    () => formatNumberWithCommas(calculateTotalInvestment(characterData)),
    [characterData],
  )

  const ref = useRef<HTMLDivElement>()

  const [isExpanded, setExpanded] = useState(false)

  const expandCard = useCallback(() => setExpanded(true), [])
  const handleKeyPress = useCallback(
    (event: React.KeyboardEvent) => {
      if (checkKeyboardTrigger(event.code)) expandCard()
    },
    [expandCard],
  )

  return (
    <>
      <S.Wrapper
        ref={ref as React.RefObject<HTMLDivElement>}
        highlighted={highlighted}
        role={expandable ? 'button' : undefined}
        tabIndex={expandable ? 1 : undefined}
        aria-label={expandable ? common.CharacterCard.expand : undefined}
        onClick={expandable ? expandCard : undefined}
        onKeyPress={expandable ? handleKeyPress : undefined}
        {...props}
      >
        <Head
          id={id}
          outfitId={outfitId}
          nickname={nickname}
          level={level}
          vocationId={vocationId}
          serverName={serverData.serverName}
          highlighted={highlighted}
        >
          {highlighted && <TagButton />}
        </Head>

        <S.Body lazy={lazyRender}>
          <S.InfoGrid>
            <Textbox.Server
              serverData={serverData}
              nickname={nickname}
              transfer={transfer}
            />
            <Textbox.Pvp serverData={serverData} />
            <Textbox.AuctionEnd auctionEnd={auctionEnd} past={past} />
            <Textbox.AuctionBid
              hasBeenBidded={hasBeenBidded}
              currentBid={currentBid}
              past={past}
            />
          </S.InfoGrid>

          <CharacterItems items={items} />

          <CharacterSkills skills={skills} />

          <S.FlexFooter>
            <S.FlexColumn>
              <ImbuementsTooltip items={imbuements} />
              <CharmsTooltip items={charms} />
              <QuestsTooltip items={quests} />
            </S.FlexColumn>

            <S.FlexColumn storeColumn>
              <S.Checkbox
                label="Charm Expansion"
                checked={charmInfo.expansion}
              />

              <S.Checkbox label="Prey Slot" checked={preySlot} />

              {tcInvested !== '0' && (
                <div
                  className="flex items-center justify-between gap-1.5"
                  title={`${common.CharacterCard.tcInvested.prefix} ${tcInvested} ${common.CharacterCard.tcInvested.suffix}`}
                >
                  <S.CheckboxContainer>
                    <S.Icons.TibiaCoin />
                  </S.CheckboxContainer>
                  <S.Strong>
                    {tcInvested} {common.CharacterCard.tcInvested.invested}
                  </S.Strong>
                </div>
              )}
            </S.FlexColumn>
          </S.FlexFooter>
        </S.Body>

        <SpecialTags character={characterData} />
      </S.Wrapper>
      {isExpanded && (
        <CharacterModal
          characterData={characterData}
          onClose={() => setExpanded(false)}
          past={past}
        />
      )}
    </>
  )
}
Example #12
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
Chart = ({
  totalLabel,
  yesterdayLabel,
  tooltipLabel,
  chartData,
  className,
  ...props
}: ChartProps) => {
  const {
    translations: { statistics, common },
  } = useTranslations()

  const { colors } = useTheme()

  const [dataSize, setDataSize] = useState(DAYS_IN_A_WEEK)

  const { current, lastMonth: originalLastMonth } = chartData
  const lastMonth = useMemo(
    () => fillArrayUntil(originalLastMonth, DAYS_IN_A_MONTH),
    [originalLastMonth],
  )
  const todayValue = lastMonth[lastMonth.length - 1]
  const yesterdayValue = lastMonth[lastMonth.length - 2]
  const dailyDifference = todayValue - yesterdayValue

  const options = useMemo(
    () => ({
      responsive: true,
      maintainAspectRatio: false,
      animation: {
        duration: 400,
        easing: 'easeOutCubic',
      },
      elements: {
        line: {
          tension: 0,
        },
      },
      legend: {
        display: false,
      },
      scales: {
        xAxes: [
          {
            ticks: {
              fontColor: colors.onSurface,
            },
            gridLines: {
              display: false,
            },
          },
        ],
        yAxes: [
          {
            ticks: {
              callback: (value: number) => formatNumberWithCommas(value),
              fontColor: colors.onSurface,
            },
            gridLines: {
              color: `${colors.separator}60`,
            },
          },
        ],
      },
      tooltips: {
        callbacks: {
          title: (tooltipItem: Record<string, string>[]) =>
            `Day ${tooltipItem[0].xLabel}`,
          label: (tooltipItem: Record<string, number>) =>
            `${tooltipLabel}: ${formatNumberWithCommas(tooltipItem.yLabel)} TC`,
        },
        displayColors: false,
      },
    }),
    [colors, tooltipLabel],
  )

  const chartDataObject = useMemo(
    () => ({
      labels: lastMonth
        .slice(lastMonth.length - dataSize)
        .map((_, index) => {
          const date = new Date()
          date.setDate(date.getDate() - index)
          return `${date.getDate()}/${date.getMonth() + 1}, ${
            common.Weekdays[date.getDay()]
          }`
        })
        .reverse(),
      datasets: [
        {
          label: tooltipLabel,
          data: lastMonth.slice(lastMonth.length - dataSize),
          fill: false,
          backgroundColor: colors.primary,
          borderColor: colors.primary,
        },
      ],
    }),
    [colors, tooltipLabel, dataSize, lastMonth],
  )

  return (
    <section
      className={clsx(
        styles.wrapper,
        'card w-full py-5 pr-4 pl-[26px] transition-colors',
        className,
      )}
      {...props}
    >
      <div className="mb-5 flex items-center gap-12">
        <Summary
          title={totalLabel}
          value={current}
          percentage={(todayValue / (current - todayValue)) * 100}
        />
        <Summary
          title={yesterdayLabel}
          value={todayValue}
          percentage={(Math.abs(dailyDifference) / yesterdayValue) * 100}
          positive={dailyDifference > 0}
        />
      </div>

      <div className="h-[260px] w-full">
        <Line data={chartDataObject} options={options} />
      </div>

      <div className="mt-[22px] flex gap-2">
        <Chip
          overrideStatus={dataSize === DAYS_IN_A_MONTH}
          onClick={() => setDataSize(DAYS_IN_A_MONTH)}
        >
          {DAYS_IN_A_MONTH} {statistics.Days}
        </Chip>
        <Chip
          overrideStatus={dataSize === DAYS_IN_A_WEEK}
          onClick={() => setDataSize(DAYS_IN_A_WEEK)}
        >
          {DAYS_IN_A_WEEK} {statistics.Days}
        </Chip>
      </div>
    </section>
  )
}
Example #13
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
ComparisonChart = ({
  guildA,
  guildB,
  tooltipSuffix,
  dateLabelType,
  className,
  ...props
}: ComparisonChartProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const { colors } = useTheme()

  const formatDateLabel = useCallback(
    (timestamp: number, formatType: 'Time' | 'Date'): string => {
      const currentDate = new Date(timestamp)

      switch (formatType) {
        case 'Time': {
          const hours = currentDate.getHours().toString().padStart(2, '0')
          const minutes = currentDate.getMinutes().toString().padStart(2, '0')
          return `${hours}:${minutes}h`
        }

        case 'Date': {
          const day = currentDate.getDate().toString()
          const month = currentDate.getMonth() + 1
          const weekday = currentDate.getDay()

          return `${day}/${month}, ${common.Weekdays[weekday]}`
        }

        default:
          return currentDate.toLocaleString()
      }
    },
    [common],
  )

  const options = useMemo(
    () => ({
      responsive: true,
      maintainAspectRatio: false,
      animation: {
        duration: 400,
        easing: 'easeOutCubic',
      },
      elements: {
        line: {
          tension: 0,
        },
      },
      legend: {
        display: false,
      },
      scales: {
        xAxes: [
          {
            ticks: {
              fontColor: colors.onSurface,
            },
            gridLines: {
              display: false,
            },
          },
        ],
        yAxes: [
          {
            ticks: {
              fontColor: colors.onSurface,
              callback: (value: number) => compactNumberFormatter(value),
            },
            gridLines: {
              color: `${colors.separator}60`,
            },
          },
        ],
        displayColors: false,
      },
      tooltips: {
        callbacks: {
          label: (tooltipItem: Record<string, number>) =>
            `${formatNumberWithCommas(tooltipItem.value)} ${tooltipSuffix}`,
        },
        displayColors: false,
      },
    }),
    [colors],
  )

  const chartDataObject = useMemo(
    () => ({
      labels: guildA.dataArray.map((snapshot) =>
        formatDateLabel(snapshot.timeStamp, dateLabelType),
      ),
      datasets: [
        {
          label: guildA.name,
          data: guildA.dataArray.map((snapshot) => snapshot.value),
          fill: false,
          backgroundColor: colorA,
          borderColor: colorA,
        },
        {
          label: guildB.name,
          data: guildB.dataArray.map((snapshot) => snapshot.value),
          fill: false,
          backgroundColor: colorB,
          borderColor: colorB,
        },
      ],
    }),
    [colors, guildA],
  )

  return (
    <section
      className={clsx(
        styles.wrapper,
        'card h-[500px] pt-5 pr-4 pb-[86px] pl-[26px] transition-colors',
        className,
      )}
      {...props}
    >
      <div className="child:text-onSurface mb-5 flex gap-6 md:gap-9">
        <div style={{ color: colorA }}>
          <GuildName>{guildA.name}</GuildName>
          <OnlineCount>{guildA.summaryValue}</OnlineCount>
        </div>

        <div style={{ color: colorB }}>
          <GuildName>{guildB.name}</GuildName>
          <OnlineCount>{guildB.summaryValue}</OnlineCount>
        </div>
      </div>

      <Line data={chartDataObject} options={options} />
    </section>
  )
}
Example #14
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
describe('<CharacterCard />', () => {
  test.each(characterList)('should write every info correctly', (character) => {
    renderWithProviders(<CharacterCard characterData={character} />)

    expect(screen.getByText(character.nickname)).toBeInTheDocument()

    expect(
      screen.getByText(
        `Level ${character.level} - ${vocation.getFullName(
          character.vocationId,
          character.level,
        )}`,
      ),
    ).toBeInTheDocument()

    expect(
      screen.getByAltText(character.serverData.serverLocation.string),
    ).toBeInTheDocument()

    expect(
      screen.getByText(character.serverData.serverName),
    ).toBeInTheDocument()

    expect(
      screen.getByLabelText(
        character.transfer
          ? 'Regular World Transfer available'
          : 'Regular World Transfer NOT available',
      ),
    ).toBeInTheDocument()

    expect(
      screen.getByText(character.serverData.pvpType.string),
    ).toBeInTheDocument()

    expect(
      screen.getByText(character.hasBeenBidded ? 'Current Bid' : 'Minimum Bid'),
    ).toBeInTheDocument()

    expect(
      screen.getByText(formatNumberWithCommas(character.currentBid)),
    ).toBeInTheDocument()

    expect(screen.queryAllByAltText('Featured item')).toHaveLength(
      character.items.length,
    )

    character.items.forEach((itemId) => {
      const [, tier] = itemId.toString().split('.')
      if (tier) {
        const [first] = screen.queryAllByTitle(`tier ${tier}`)
        expect(first).toBeInTheDocument()
      }
    })

    expect(
      screen.getByText(
        `Imbuements: ${character.imbuements.length}/${imbuement.tokens.length}`,
      ),
    ).toBeInTheDocument()

    expect(
      screen.getByText(
        `Charms: ${character.charms.length}/${charm.tokens.length}`,
      ),
    ).toBeInTheDocument()

    expect(
      screen.getByText(
        `Quests: ${character.quests.length}/${quest.tokens.length}`,
      ),
    ).toBeInTheDocument()

    const charmCheckbox = screen.getByRole('checkbox', {
      name: 'Charm Expansion',
    })
    if (character.charmInfo.expansion) {
      expect(charmCheckbox).toBeChecked()
    } else {
      expect(charmCheckbox).not.toBeChecked()
    }

    const preyCheckbox = screen.getByRole('checkbox', {
      name: 'Prey Slot',
    })
    if (character.preySlot) {
      expect(preyCheckbox).toBeChecked()
    } else {
      expect(preyCheckbox).not.toBeChecked()
    }

    const totalInvestment = formatNumberWithCommas(
      calculateTotalInvestment(character),
    )

    if (totalInvestment === '0') {
      expect(screen.queryByText(totalInvestment)).not.toBeInTheDocument()
    } else {
      expect(
        screen.getByText(totalInvestment, { exact: false }),
      ).toBeInTheDocument()
    }
  })

  test.each(characterList)(
    `should render a different bid label for ${routes.BAZAAR_HISTORY} route`,
    (character) => {
      renderWithProviders(<CharacterCard characterData={character} past />)
      expect(
        screen.getByText(
          character.hasBeenBidded ? 'Auction Successful' : 'Auction Failed',
        ),
      ).toBeInTheDocument()
    },
  )

  test.each(characterList)(
    `should render highlighted card correctly`,
    (character) => {
      renderWithProviders(
        <CharacterCard characterData={character} highlighted />,
      )

      expect(screen.getByText('Highlight your auction!')).toBeInTheDocument()
    },
  )

  test('the character card should be able to be expanded', () => {
    const [character] = characterList
    renderWithProviders(<CharacterCard characterData={character} expandable />)

    const expandButton = screen.getByRole('button', {
      name: 'Expand for full auction details',
    })

    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()

    userEvent.click(expandButton)
    expect(screen.getByRole('dialog')).toBeInTheDocument()

    userEvent.click(screen.getByRole('button', { name: 'Close dialog' }))

    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
  })
})
Example #15
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
CharacterModal = ({
  characterData,
  onClose,
  past = false,
}: CharacterModalProps) => {
  const {
    id,
    sex,
    outfitId,
    nickname,
    level,
    vocationId,
    serverData,
    transfer,
    auctionEnd,
    hasBeenBidded,
    currentBid,
    items,
    skills,
    hirelings,
    charmInfo,
    preySlot,
    huntingSlot,
    storeItems,
    imbuements,
    charms,
    quests,
    outfits,
    mounts,
    storeOutfits,
    storeMounts,
    achievementPoints,
  } = characterData

  const {
    translations: { common },
  } = useTranslations()

  const checkboxRecords = useMemo(() => checkStore(storeItems), [])

  const tcInvested = useMemo(
    () => formatNumberWithCommas(calculateTotalInvestment(characterData)),
    [characterData],
  )

  const tabRef = useRef<HTMLDivElement>(null)
  const isDesktop = useIsDesktop()

  const handleTabChange = useCallback(() => {
    if (!isDesktop) {
      tabRef.current?.scrollIntoView({
        block: 'start',
      })
    }
  }, [isDesktop])

  return (
    <Dialog
      isOpen
      onClose={onClose}
      className={clsx(
        styles.wrapper,
        'w-full max-w-[var(--cardMaxMobileWidth)] p-[var(--lateralMargin)] outline-none md:w-fit md:max-w-[calc(100%-80px)]',
      )}
    >
      <Head
        id={id}
        outfitId={outfitId}
        nickname={nickname}
        level={level}
        vocationId={vocationId}
        serverName={serverData.serverName}
      />

      <div className="custom-scrollbar -mx-[var(--lateralMargin)] h-[var(--gridMobileHeight)] overflow-y-auto px-[var(--lateralMargin)] md:h-[var(--cardFixedHeight)]">
        <S.Spacer className="w-full md:flex md:gap-6">
          <S.Spacer className="md:z-4 h-fit pt-1.5 md:sticky md:top-0 md:min-w-[280px] md:max-w-fit md:shrink-0">
            <InfoGrid>
              <Textbox.Server
                serverData={serverData}
                nickname={nickname}
                transfer={transfer}
                placement="bottom"
              />
              <Textbox.Pvp serverData={serverData} />
              <Textbox.AuctionEnd auctionEnd={auctionEnd} past={past} />
              <Textbox.AuctionBid
                hasBeenBidded={hasBeenBidded}
                currentBid={currentBid}
                past={past}
              />
            </InfoGrid>

            <CharacterItems items={items} />

            <S.Section border>
              <CharacterSkills skills={skills} />
            </S.Section>

            <S.TooltipSection>
              <ImbuementsTooltip placement="top-start" items={imbuements} />
              <CharmsTooltip
                placement="top-start"
                items={charms}
                charmInfo={charmInfo}
              />
              <QuestsTooltip placement="top-start" items={quests} />
              <Hirelings hirelingsInfo={hirelings} />
              <Achievements achievementPoints={achievementPoints} />
            </S.TooltipSection>
          </S.Spacer>

          <S.Spacer className="h-fit w-full">
            <S.Section border className="z-3">
              <div
                title={`${common.CharacterCard.tcInvested.prefix} ${tcInvested} ${common.CharacterCard.tcInvested.suffix}`}
                className="text-tsm flex items-center gap-[5px]"
              >
                <Icons.TibiaCoin />{' '}
                {common.CharacterCard.CharacterModal.totalInvested}:{' '}
                <strong
                  className={clsx(
                    tcInvested === '0'
                      ? 'font-normal'
                      : 'text-primaryHighlight',
                  )}
                >
                  {tcInvested} Tibia Coins
                </strong>
              </div>

              <div className="grid grid-flow-col grid-rows-3 gap-2 md:max-w-[400px]">
                <Checkbox
                  label="Training Dummy"
                  checked={checkboxRecords.dummy}
                />
                <Checkbox
                  label="Gold pouch"
                  checked={checkboxRecords.goldPouch}
                />
                <Checkbox label="Hirelings" checked={hirelings.count > 0} />
                <Checkbox
                  label="Charm expansion"
                  checked={charmInfo.expansion}
                />
                <Checkbox label="Prey Slot" checked={preySlot} />
                <Checkbox label="Hunting Task Slot" checked={huntingSlot} />
                <Checkbox
                  label="Imbuement Shrine"
                  checked={checkboxRecords.imbuementShrine}
                />
                <Checkbox
                  label="Reward Shrine"
                  checked={checkboxRecords.rewardShrine}
                />
                <Checkbox label="Mailbox" checked={checkboxRecords.mailbox} />
              </div>
            </S.Section>

            <Tabs.Group
              className="min-h-[var(--gridMobileHeight)] md:min-h-[unset]"
              style={{ display: 'block', overflow: 'unset' }}
              onChange={handleTabChange}
              ref={tabRef}
            >
              <Tabs.Panel
                label={
                  <>
                    <OutfitIcon />
                    Outfits {tabCounter(outfits.length, storeOutfits.length)}
                  </>
                }
              >
                <S.SpriteSection>
                  {outfits.map(({ name, type }) => (
                    <SpriteBox
                      key={name}
                      offset
                      auctionId={id}
                      name={name}
                      sex={sex}
                      src={resolvers.outfit(name, sex, type)}
                      type={type}
                      checkRareOutfit
                    />
                  ))}
                </S.SpriteSection>

                {storeOutfits.length > 0 && (
                  <>
                    <S.SpriteSectionDivisor>
                      Store outfits ({storeOutfits.length})
                    </S.SpriteSectionDivisor>
                    <S.SpriteSection>
                      {storeOutfits.map(({ name, type }) => (
                        <SpriteBox
                          key={name}
                          offset
                          auctionId={id}
                          name={name}
                          sex={sex}
                          src={resolvers.storeOutfit(name, sex, type)}
                        />
                      ))}
                    </S.SpriteSection>
                  </>
                )}
              </Tabs.Panel>

              {mounts.length + storeMounts.length > 0 && (
                <Tabs.Panel
                  label={
                    <>
                      <MountIcon />
                      Mounts {tabCounter(mounts.length, storeMounts.length)}
                    </>
                  }
                >
                  <S.SpriteSection>
                    {mounts.map((name) => (
                      <SpriteBox
                        key={name}
                        offset
                        auctionId={id}
                        name={name}
                        sex={sex}
                        src={resolvers.mount(name)}
                        checkRareMount
                      />
                    ))}
                  </S.SpriteSection>

                  {storeMounts.length > 0 && (
                    <>
                      <S.SpriteSectionDivisor>
                        Store mounts ({storeMounts.length})
                      </S.SpriteSectionDivisor>
                      <S.SpriteSection>
                        {storeMounts.map((name) => (
                          <SpriteBox
                            key={name}
                            offset
                            auctionId={id}
                            name={name}
                            sex={sex}
                            src={resolvers.storeMount(name)}
                          />
                        ))}
                      </S.SpriteSection>
                    </>
                  )}
                </Tabs.Panel>
              )}

              {storeItems.length > 0 && (
                <Tabs.Panel
                  label={
                    <>
                      <StoreIcon />
                      Store Items ({storeItems.length})
                    </>
                  }
                >
                  <S.SpriteSection>
                    {storeItems.map(({ name, amount }, childIndex) => (
                      <SpriteBox
                        // eslint-disable-next-line react/no-array-index-key
                        key={`${childIndex}-${name}`}
                        auctionId={id}
                        name={name}
                        amount={amount}
                        sex={sex}
                        src={resolvers.storeItem(name)}
                      />
                    ))}
                  </S.SpriteSection>
                </Tabs.Panel>
              )}
            </Tabs.Group>
          </S.Spacer>
        </S.Spacer>
      </div>
    </Dialog>
  )
}
Example #16
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
describe('<CharacterModal />', () => {
  beforeEach(() => {
    mockedFetch.mockClear()
    scrollIntoViewMock.mockClear()
    mockOnClose.mockClear()
    setup.setTimeout()
  })

  test.each(characterList)(
    'should render every element correctly',
    (character) => {
      const {
        outfits,
        storeOutfits,
        mounts,
        storeMounts,
        storeItems,
        charms,
        charmInfo,
        hirelings,
        achievementPoints,
      } = character

      renderWithProviders(
        <CharacterModal characterData={character} onClose={mockOnClose} />,
      )

      const totalInvestment = formatNumberWithCommas(
        calculateTotalInvestment(character),
      )

      if (totalInvestment === '0') {
        expect(screen.queryByText(totalInvestment)).not.toBeInTheDocument()
      } else {
        expect(
          screen.getByText(`${totalInvestment} Tibia Coins`, { exact: false }),
        ).toBeInTheDocument()
      }

      expect(
        screen.getByText(
          `Outfits ${tabCounter(outfits.length, storeOutfits.length)}`,
          { exact: false },
        ),
      ).toBeInTheDocument()

      expect(
        screen.getByText(
          `Mounts ${tabCounter(mounts.length, storeMounts.length)}`,
          { exact: false },
        ),
      ).toBeInTheDocument()

      if (storeItems.length > 0) {
        expect(
          screen.getByText(`Store Items (${storeItems.length})`, {
            exact: false,
          }),
        ).toBeInTheDocument()
      } else {
        expect(
          screen.queryByText(`Store Items (${storeItems.length})`, {
            exact: false,
          }),
        ).not.toBeInTheDocument()
      }

      expect(screen.getByText('Charms:', { exact: false }).textContent).toEqual(
        `Charms: ${charms.length}/19 (${formatNumberWithCommas(
          charmInfo.total,
        )} total points,${formatNumberWithCommas(charmInfo.unspent)} unspent)`,
      )

      expect(
        screen.getByText('Hirelings:', { exact: false }).textContent,
      ).toEqual(
        `Hirelings: ${hirelings.count} (${hirelings.jobs}/4 jobs, ${hirelings.outfits} outfits)`,
      )

      expect(
        screen.getByText(
          `Achievement points: ${formatNumberWithCommas(achievementPoints)}`,
          {
            exact: false,
          },
        ),
      ).toBeInTheDocument()
    },
  )

  test('if a sprite is not found, it should call `NotifyErrorClient`', async () => {
    const [character] = characterList

    const { rerender } = renderWithProviders(
      <CharacterModal
        characterData={{ ...character, storeMounts: [] }}
        onClose={mockOnClose}
      />,
    )

    expect(mockedFetch).toHaveBeenCalledTimes(0)

    rerender(
      <CharacterModal
        characterData={{
          ...character,
          storeMounts: ['weird mount'],
        }}
        onClose={mockOnClose}
      />,
    )

    await waitFor(() => expect(mockedFetch).toHaveBeenCalledTimes(1))
  })

  test('should call `onClose` handler', () => {
    const [character] = characterList
    renderWithProviders(
      <CharacterModal characterData={character} onClose={mockOnClose} />,
    )

    expect(mockOnClose).toHaveBeenCalledTimes(0)

    userEvent.click(screen.getByRole('button', { name: 'Close dialog' }))
    expect(mockOnClose).toHaveBeenCalledTimes(1)
  })

  test('should be tabbable', () => {
    const [character] = characterList
    renderWithProviders(
      <CharacterModal characterData={character} onClose={mockOnClose} />,
    )

    const [tab1, tab2] = screen.getAllByRole('tab')
    expect(scrollIntoViewMock).toHaveBeenCalledTimes(0)

    expect(tab1).toHaveAttribute('aria-selected', 'true')
    expect(tab2).toHaveAttribute('aria-selected', 'false')

    userEvent.click(tab2)
    expect(tab1).toHaveAttribute('aria-selected', 'false')
    expect(tab2).toHaveAttribute('aria-selected', 'true')
    expect(scrollIntoViewMock).toHaveBeenCalledTimes(1)
  })
})
Example #17
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
SpriteBox = ({
  auctionId,
  offset = false,
  name,
  sex,
  src,
  type,
  amount = 0,
  checkRareMount = false,
  checkRareOutfit = false,
}: SpriteBoxProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const isRareMount = checkRareMount ? rareMountSet.has(name) : false
  const isRareOutfit =
    checkRareOutfit && type ? testRareOutfit({ name, type, sex }) : false

  const isRare = isRareMount || isRareOutfit
  const showAddon = type !== undefined

  const notifyError = useCallback(
    () => NotifyErrorClient.setMessage({ auctionId, name, src }),
    [auctionId, name, src],
  )

  return (
    <div
      title={amount > 1 ? `${formatNumberWithCommas(amount)}x ${name}` : name}
      className={clsx(
        'relative rounded-md',
        showAddon && 'pb-2 shadow',
        isRare ? 'bg-primary' : 'bg-primaryVariant',
        isRare && styles.highlighted,
      )}
    >
      <SpritePortrait
        offset={offset}
        alt={name}
        src={src}
        width={offset ? 64 : 32}
        height={offset ? 64 : 32}
        onError={notifyError}
        className="pointer-events-none"
        style={showAddon ? { boxShadow: 'unset' } : undefined}
      />

      {showAddon && (
        <div className="flex justify-evenly">
          <Checkbox
            aria-label={
              common.CharacterCard.CharacterModal.SpriteBox.firstAddon
            }
            checked={addonCheck.first(type ?? 0)}
            greenVariant={isRare}
          />
          <Checkbox
            aria-label={
              common.CharacterCard.CharacterModal.SpriteBox.secondAddon
            }
            checked={addonCheck.second(type ?? 0)}
            greenVariant={isRare}
          />
        </div>
      )}

      {amount > 1 && (
        <ActiveCount
          className="z-1 absolute -top-1.5 -right-1.5 py-0.5 px-1 font-bold after:ml-[1px] after:font-normal after:content-['x']"
          style={{
            width: 'unset',
            height: 'unset',
            borderRadius: '4px',
          }}
        >
          {amount}
        </ActiveCount>
      )}
    </div>
  )
}