Honeycomb

Honeycomb React 16 migration guide

Upgrade package

First upgrade to the latest version of Honeycomb-React:

npm install @flixbus/honeycomb-react@latest

Then proceed to the breaking changes.

Breaking Changes

Avatar component and avatar element of HeaderUserWidget changed it's default size

In order to align icon sizes in the header widget area we decided to change the default size of the Avatar component. The new size is spacing-4 whereas in prior versions it was spacing-6. The change also affected HeaderUserWidget component that by default now renders a spacing-4 user avatar.

This means that if you were using a smaller version of the Avatar in your code (by applying size="sm") you will need to remove this prop.

Before:

<Avatar size="sm" alt="Picture of John Doe" />

Now:

<Avatar alt="Picture of John Doe" />

If your designs require a bigger Avatar you'll need to set the size to 6 to have things as before.

Before:

<Avatar alt="Picture of John Doe" />

Now:

<Avatar size={6} alt="Picture of John Doe" />

Avatar size prop accepts numbers instead of t-shirt sizes

Ver. 16 also brings changes to the values size prop accepts. Instead of t-shirt size you need to pass actual size numbers according to our spacing scale Instead of sm, lg, xl the prop now accepts numbers 6, 8, 16.

Before:

// smaller avatar
<Avatar size="sm" alt="Picture of John Doe" />
// bigger spacing-6 avatar
<Avatar alt="Picture of John Doe" />
// bigger spacing-8 avatar
<Avatar size="lg" alt="Picture of John Doe" />
// largest spacing-16 avatar
<Avatar size="xl" alt="Picture of John Doe" />

Now:

// smaller avatar, default
<Avatar alt="Picture of John Doe" />
// bigger spacing-6 avatar
<Avatar size={6} alt="Picture of John Doe" />
// bigger spacing-8 avatar
<Avatar size={8} alt="Picture of John Doe" />
// largest spacing-16 avatar
<Avatar size={16} alt="Picture of John Doe" />

See Avatar component docs for more details.

ThemeWrapper themes prop signature changed

Many changes behind the scenes for the ThemeWrapper and how we handle theme tokens were taken place. As a result we couldn't avoid some small breaking changes.

The themes prop signature has changed, and the theme files themeFlix, themeKamil, themeNeptune, themeHighContrast now will export a complete theme configuration (instead of only the design tokens).

This means you don't need to create the theme objects on your side anymore, and can only pass an array of themes you want to include in the styles of your application.

Before:

The files contained only tokens so you had to create a theme configuration object for each.

import { ThemeWrapper, themeFlix, themeKamil, themeNeptune } from '@flixbus/honeycomb-react';

<ThemeWrapper theme="flix" themes={{
  'flix': themeFlix,
  'kamil': themeKamil,
  'neptune': themeNeptune,
}}>
  {`...your themed app`}
</ThemeWrapper>

Now:

Simply pass the theme files in an array.

import { ThemeWrapper, themeFlix, themeKamil, themeNeptune } from '@flixbus/honeycomb-react';

<ThemeWrapper theme="flix" themes={[themeFlix, themeKamil, themeNeptune]}>
  {`...your themed app`}
</ThemeWrapper>

Dark and Tvm token files removed and replaced by mode props on ThemeWrapper

Similarly as above, the "modes" token files are no longer being exported and will be handled internally by the ThemeWrapper component.

Before:

import { ThemeWrapper, themeNeptune, themeNeptuneDark } from '@flixbus/honeycomb-react';

<ThemeWrapper theme="neptune-dark" themes={{
  'neptune': themeNeptune,
  'neptune-dark': themeNeptuneDark,
}}>
  {`...your Neptune app with dark mode`}
</ThemeWrapper>

Now:

Theme file contains the tokens for dark and tvm modes and you can toggle dark mode with the dark prop.

import { ThemeWrapper, themeNeptune } from '@flixbus/honeycomb-react';

<ThemeWrapper theme="neptune" dark themes={[themeNeptune]}>
  {`...your Neptune app with dark mode`}
</ThemeWrapper>

Some input components id prop now no longer accepts numbers (only string)

In order to keep Honeycomb props types more aligned with React and HTML types, we changed some form input components id prop.

Historically these components used to accept numbers as id as well as string (formerly: string | number) so we removed many types that were overriding the defaults and now we solely rely on whatever comes from the element types, which at the moment is only string. We won't be overriding id props any more so simply refer to React and/or HTML types documentation for guidance.

The affected components are:

  • Checkbox
  • ChoiceWrapperItem
  • PhoneInput
  • Quantity
  • Radio
  • Range
  • Select
  • SelectGroup options
  • Switch
  • Textarea

Before:

<Checkbox label="Some checkbox" id={1} value="1-checked" />
<Radio label="Some radio" id={2} name="radio" value="2-radio" />

Now:

<Checkbox label="Some checkbox" id="1" value="1-checked" />
<Radio label="Some radio" id="2" name="radio" value="2-radio" />

Helpers functions are now considered hooks

We added the helpers styles to InjecssContext so the helpers work properly in SSR environments, this means the helpers functions now depend on a hook call for React.useContext, and are now considered hooks themselves.

You should call helpers functions inside of the component lifecycle to get rid of the Rules of Hooks React error message.

We will only show the spaceHelpers as an example, but this is valid for all helpers.

Before:

import { Box, spaceHelpers } from '@flixbus/honeycomb-react';

// ❌ `spaceHelpers` now calls a hook, so should respect React's Rules of Hooks
const { 4: space4 } = spaceHelpers();

function MyBox({ children }) {
  return (
    <Box extraClasses={space4.bottom}>
      {children}
    </Box>
  )
}

Now:

import { Box, spaceHelpers } from '@flixbus/honeycomb-react';

function MyBox({ children }) {
  // 👍 Move helpers calls inside the component
  const { 4: space4 } = spaceHelpers();

  return (
    <Box extraClasses={space4.bottom}>
      {children}
    </Box>
  )
}

Note: we are aware the helper functions do not follow the hooks naming conventions and decided to keep them as is to reduce the breaking changes. If you feel we should renamed them or provide aliases (to take advantage of linters such as eslint-plugin-react-hooks) please do let us know and we can work on this.

useSmartPosition now works with refs differently

useSmartPosition now returns targetRef and elementRef instead of accepting them as an input. You need to rewire your smart positioned components by attaching the refs returned from the hook to your respective target and positioned elements. This change is related to a number of cases the hook was unable to calculate position properly due to not tracking the ref updates. Now it uses ref callbacks under the hood which is allowing it to recalculate positions when the target elements change.

Before:

const SmartTooltip = ({ id, content, targetContent }) => {
  // refs defined separately and then passed to the hook as params
  const targetRef = React.useRef(null);
  const elementRef = React.useRef(null);
  const [isActive, setIsActive] = React.useState(false);
  const [smartPosition, smartAlignment] = useSmartPosition({
    active: isActive,
    targetRef: targetRef,
    elementRef: elementRef,
  });

  return (
    <Tooltip
      balloonRef={elementRef}
      id={id}
      content={content}
      position={smartPosition}
      alignment={smartAlignment}
      onToggle={setIsActive}
    >
      <Button innerRef={targetRef} appearance="link"><Icon InlineIcon={IconInfo}/>{targetContent}</Button>
    </Tooltip>
  );
}

Now:

const SmartTooltip = ({ id, content, targetContent }) => {
  const [isActive, setIsActive] = React.useState(false);
  // notice the hook returns the refs you need to attach to your components
  const [smartPosition, smartAlignment, targetRef, elementRef] = useSmartPosition({
    active: isActive,
  });

  return (
    <Tooltip
      balloonRef={elementRef}
      id={id}
      content={content}
      position={smartPosition}
      alignment={smartAlignment}
      onToggle={setIsActive}
    >
      <Button innerRef={targetRef} appearance="link"><Icon InlineIcon={IconInfo}/>{targetContent}</Button>
    </Tooltip>
  );
}

Deleted

dist/scss/honeycomb-themes.scss endpoint removed due to it being redundant

With the latest changes this endpoint because redundant and is no longer available.

Before:

@use '@flixbus/honeycomb-react/dist/scss/honeycomb-helpers.scss';

For a similar functionality you should import the aggregation file located in the themes folder instead.

Now:

@use '@flixbus/honeycomb-react/dist/scss/themes/all.scss';

Or better yet, use only the theme files you actually need from the same folder:

@use '@flixbus/honeycomb-react/dist/scss/themes/flix.scss';
@use '@flixbus/honeycomb-react/dist/scss/themes/kamil.scss';
@use '@flixbus/honeycomb-react/dist/scss/themes/neptune.scss';

You can now configure these endpoints according to your needs. Check the SCSS themes page for more information.

checkbox prop on DataTableHead, DataTableFoot and DataTableRow no longer supported

Before:

You could pass a checkbox via the checkbox prop that would be rendered in the first cell.

import { DataTable, DataTableRow, DataTableCell, Checkbox } from '@flixbus/honeycomb-react';

<DataTable>
  <DataTableRow checkbox={(<Checkbox small defaultChecked aria-label="Select this item" id="item-1-id" value="item-value" />)}>
    <DataTableCell scope="row">Item 1</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
  </DataTableRow>
</DataTable>

Now:

You should add the checkbox as part of any other content, this means you are in control of it's added and should handle the selected modifier on your side.

import { DataTable, DataTableRow, DataTableCell, Checkbox } from '@flixbus/honeycomb-react';

<DataTable>
  <DataTableRow>
    <DataTableCell>
      <Checkbox small aria-label="Select this item" id="item-2-id" value="item-value" />
    </DataTableCell>
    <DataTableCell scope="row">Item 1</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
    <DataTableCell>Item info</DataTableCell>
  </DataTableRow>
</DataTable>

HoneyBem init function

HoneyBem was refactored as a class and the init function is no longer supported.

Before:

import { HoneyBem } from '@flixbus/honeycomb-react';

const bem = HoneyBem.init('my-component');

Now:

import { HoneyBem } from '@flixbus/honeycomb-react';

const bem = new HoneyBem('my-component');

Passing Legend as children to Fieldset

The Fieldset component no longer treats Legend component differently when wrapping the children with the items container. For that reason you should move all your fieldset legends to the legend prop instead.

Before:

import { Fieldset, Legend, Checkbox } from '@flixbus/honeycomb-react';

<Fieldset info="Please confirm you're fully aware of the choice made above">
  <Legend>Are you a dog person?</Legend>
  <Checkbox
    id="checkbox-2"
    value="yes"
    label="Yes!"
  />
  <Checkbox
    id="checkbox-3"
    value="absolutely"
    label="Absolutely!"
  />
</Fieldset>

Now:

Before:

import { Fieldset, Checkbox } from '@flixbus/honeycomb-react';

<Fieldset legend="Are you a dog person?" info="Please confirm you're fully aware of the choice made above">
  <Checkbox
    id="checkbox-2"
    value="yes"
    label="Yes!"
  />
  <Checkbox
    id="checkbox-3"
    value="absolutely"
    label="Absolutely!"
  />
</Fieldset>

Deprecated

PagerArrow component and contentFit prop in Pager

The PagerArrow component is deprecated and will be removed in the next major version. You can now create custom pager controls by using Icons as the content of the PagerItem component.

Before:

import { Pager, PagerItem, PagerArrow } from '@flixbus/honeycomb-react';

<Pager aria-label="Pagination">
  <PagerArrow href="#" side="prev" aria-label="Previous page" />
  <PagerItem href="#1">1</PagerItem>
  <PagerItem href="#2">2</PagerItem>
  <PagerItem href="#3">3</PagerItem>
  <PagerItem href="#4">4</PagerItem>
  <PagerItem href="#5">5</PagerItem>
  <PagerArrow href="#" side="next" aria-label="Next page" />
</Pager>

Now:

import { Pager, PagerItem } from '@flixbus/honeycomb-react';
import { Icon, IconArrowLeft, IconArrowRight } from '@flixbus/honeycomb-icons-react';

<Pager aria-label="Pagination">
  <PagerItem href="#">
    <Icon appearance="primary" InlineIcon={IconArrowLeft} title="Back" />
  </PagerItem>
  <PagerItem href="#1">1</PagerItem>
  <PagerItem href="#2">2</PagerItem>
  <PagerItem href="#3">3</PagerItem>
  <PagerItem href="#4">4</PagerItem>
  <PagerItem href="#5">5</PagerItem>
  <PagerItem href="#">
    <Icon appearance="primary" InlineIcon={IconArrowRight} title="Next" />
  </PagerItem>
</Pager>

In addition to that, the contentFit prop is deprecated since this is the default behavior of Pager items from now on. You may safely remove this prop to remove the deprecation warning.

useThemeSwitcher hook is deprecated, and cannot be used to toggle themes anymore

Due to the changes mentioned above in the ThemeWrapper component, the useThemeSwitcher cannot be used to toggle light/dark modes anymore. We moved the hook's most interesting feature (user-preference detected dark mode) to the ThemeWrapper dark="user-preference" prop.

With that being said you should stop using the hook and replace it with simpler logic on your side.

The most common use case for the theme switcher was to toggle light/dark mode and respect user preferences.

Before:

import { Box, Switch, Text, ThemeWrapper, themeFlixDark, useThemeSwitcher } from '@flixbus/honeycomb-react';

const [theme, toggleTheme] = useThemeSwitcher({
  lightTheme: 'flix',
  darkTheme: 'flix-dark'
});

const isLight = theme === 'flix';

<ThemeWrapper theme={theme} themes={{ 'flix-dark': themeFlixDark }}>
  <Box>
    <Text>
      I prefer "{isLight ? 'light' : 'dark'}" theme
    </Text>
    <Switch
      label={`Turn ${isLight ? 'off' : 'on'} the lights`}
      id="default-theme-switch"
      checked={isLight}
      onChange={() => toggleTheme()}
    />
  </Box>
</ThemeWrapper>

Now:

The ThemeWrapper dark prop accepts the value "user-preference" that will handle showing dark mode by matching the media query: (prefers-color-scheme: dark).

import { Box, Switch, Text, ThemeWrapper } from '@flixbus/honeycomb-react';

// Initialize the state with `user-preference`. 👇
const [darkScheme, setDarkScheme] = React.useState('user-preference');

const prefersDarkScheme = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)').matches : false;

const parsePreferredScheme = () => {
  switch (darkScheme) {
    case 'user-preference': return prefersDarkScheme ? 'dark' : 'light';
    case true: return 'dark';
    case false: return 'light';
  }
}

<ThemeWrapper dark={darkScheme}>
  <Box>
    <Text>
      I prefer "{parsePreferredScheme()}" scheme.
    </Text>
    <Switch
      label="Toggle color scheme"
      id="new-dark-scheme-switch"
      onChange={(event) => setDarkScheme(!event.target.checked)}
    />
  </Box>
</ThemeWrapper>

For more examples on how to theme switch with the ThemeWrapper, check the ThemeWrapper documentation.

dayLabel prop in Calendar

The dayLabel prop is deprecated in favor to a more flexible dayProps. You can use dayProps that is a function that will be called with the day and should return rest props forwarded to the respective day cell.

Before:

import { Calendar } from '@flixbus/honeycomb-react';

const dayLabelFun = (day: number, month: number, year: number) => `${day}-${month}-${year}`;

<Calendar
  dayLabel={dayLabelFun}
  defaultMonth={new Date(2019, 10, 0)}
  endDate={new Date(2019, 11, 0)}
  startDate={new Date(2019, 10, 0)}
/>

Now:

import { Calendar } from '@flixbus/honeycomb-react';

// dayProps should return a valid object with rest props for a respective date
const dayPropsFunc = (date: Date) => ({
  'data-test-day': `day-${date.getDate()}-${date.getMonth()}-${date.getFullYear()}`,
  'aria-label': `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()}`,
});

<Calendar
  dayProps={dayPropsFunc}
  defaultMonth={new Date(2019, 10, 0)}
  endDate={new Date(2019, 11, 0)}
  startDate={new Date(2019, 10, 0)}
/>

Calendar appearance and header specific props

The props appearance, compactPrevButton, compactNextButton, compactSelect, birthdaySelectMonth and birthdaySelectYear are deprecated as the way the Calendar handles the header appearance is changed.

Before, the appearance prop was used to control wether to use the birthday or compact headers:

import { Calendar } from '@flixbus/honeycomb-react';

<Calendar
  id="compact-picker"
  startDate={new Date(2077, 4, 1)}
  endDate={new Date(2077, 11, 0)}
  defaultMonth={new Date(2077, 6, 1)}
  appearance="compact" // 👈 Show compact header
  compactSelect={{ 'aria-label': 'Select month and year' }} // 👈 Compact specific prop
  compactPrevButton={{ 'aria-label': 'Previous month' }} // 👈 Compact specific prop
  compactNextButton={{ 'aria-label': 'Next month' }} // 👈 Compact specific prop
/>

Now you can use the calendarHeader prop to create the header you need. The header specific props are removed from the Calendar and available on their respective calendar header component.

import { Calendar, CalendarHeaderCompact } from '@flixbus/honeycomb-react';

<Calendar
  id="compact-picker"
  startDate={new Date(2077, 4, 1)}
  endDate={new Date(2077, 11, 0)}
  defaultMonth={new Date(2077, 6, 1)}
  calendarHeader={
    <CalendarHeaderCompact
      selectProps={{ 'aria-label': 'Select month and year' }}
      prevButtonProps={{ 'aria-label': 'Previous month' }}
      nextButtonProps={{ 'aria-label': 'Next month' }}
    />
  }
/>

See the newly exposed calendar headers: CalendarHeaderBirthday, CalendarHeaderCompact and CalendarHeaderSimple (new)