New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V4 (WIP) #200

Open
wants to merge 158 commits into
base: master
from

Conversation

Projects
None yet
@jeroenransijn
Contributor

jeroenransijn commented Jun 12, 2018

The main update in this release the addition of a theming layer in combination with a overhaul of the color system, typography system and icon system. This is the biggest major release so far.

v4 documentation available on evergreen-v4.surge.sh

High Level Improvements

  • React upgraded to v16.3.
  • Theming support with React.createContext.
    • New color system.
    • Updated typography system.
    • Themer object for help with generating styles.
  • New Icon component using BlueprintJS icons
    • All legacy icons deprecated.
  • Improvements to Buttons
    • intent property that accepts: none, success, danger, warning
    • appearance property changed to accept: default, minimal, primary
    • Icons now use BlueprintJS icons.
  • Pane APIs changed.
    • appearance is deprecated for background.
  • Improvements to z-index management with the new Stack component.
    • Using React.createContext
  • Documentation upgraded to Gatsby v2.
  • Improvement to the Table component
    • Components are exported from Table directly. Table.TextCell, Table.Row etc.
    • No longer a borderRight on table cells
    • Height is now managed by the Table.Row and default is 48
  • Menu component added with sub components. No docs yet. Storybook available.
  • minorScale and majorScale exported.

Deprecated Components and Styles

  • Theme related
    • colors
    • TextStyles,
    • FontFamilies,
    • TextColors,
    • ButtonAppearances
    • BadgeAppearances
    • LinkAppearances
    • TextInputAppearances
    • CheckboxAppearances,
    • controlBaseStyle,
    • FillAppearances,
    • InputAppearances,
    • selectableRowStyle,
    • selectableTabStyle,
    • getBorderRadiusForControlHeight,
    • getBorderRadiusForTextSize,
    • getIconSizeForControlHeight,
    • getTextSizeForControlHeight,
    • getTextStyleForControlHeight
    • SegmentedControlAppearances
    • SelectAppearances
    • ElevationStyles,
    • BorderColors,
    • LayerAppearances
  • icon related
    • IconAim
    • IconColors
    • IconMap
    • AddIcon
    • ArrowIcon
    • CheckCircleIcon
    • CogIcon
    • DangerIcon
    • QuestionIcon
    • SearchIcon
    • TriangleIcon
    • WarningIcon
    • Icon (is now a completely different component with the same name)

Components with Breaking Changes

  • Button
    • IconButton
    • BackButton
  • Pane
  • Card
  • Avatar
  • Badge
  • Pill
  • Dialog
  • Alert
  • SelectMenu
  • Typography
    • Link
    • Heading
    • Text
  • Table
    • TableBody
    • TableCell
    • TableHead
    • TableHeaderCell
    • TableRow
    • TextTableCell
    • TextTableHeaderCell
    • SearchTableHeaderCell

Upgrade Guide

Theme

One of the biggest changes in Evergreen v4 is the addition of a theming layer. The goal of this theming layer in this version is not to offer a simple theming mechanism, but rather create a flexible foundational API we can simplify in the future. Although Evergreen exposes theming capabilities. It's still considered a private API. Breaking changes may occur in minor releases.

Theme Utilities

The theming API uses the React.createContext API added in React v16.3.0. Evergreen exports the following utilities for theming:

  • ThemeProvider
  • ThemeConsumer
  • withTheme
  • defaultTheme

ThemeProvider

The ThemeProvider is used to provide a new theme to all child ThemeConsumers. Please refer to the code to learn how this works.

ThemeConsumer

The ThemeConsumer is the best way to access the current theme object. This is primarily useful for documentation. To create components that rely on the theme object, the withTheme HoC is the preferred method to access the theme object.

 <ThemeConsumer>
    {theme => (
      ...
    )}
</ThemeConsumer>

withTheme

To create components that rely on the theme object, use the withTheme HoC. You will see the following pattern being used within Evergreen:

import React from 'react'
import PropTypes from 'prop-types'
import { withTheme } from 'evergreen-ui' // Within Evergreen this is relative.

class Alert extends React.Component {
  static propTypes = {
    /**
     * Theme provided by ThemeProvider.
     */
    theme: PropTypes.object.isRequired
  }
  
  // Component definition...
}

// Export the component with the withTheme HoC
export default withTheme(Alert)

defaultTheme

The recommended way to access the theme should be through the ThemeConsumer. However, the default theme is also directly exported to help with migration from v3 to v4. The main use case is to migrate places in which you import colors and TextStyles directly.

Colors

The color system in Evergreen is located in the theme and is used throughout the theme. There is no real dependency on any of the colors directly within components. Components always access a theme color or property through a get function. For example, theme.getTextColor is a required function in the Evergreen theme, theme.colors is not a required property and not directly used.

Colors are no longer directly exported from Evergreen. They are available on the defaultTheme or through a ThemeConsumer preferably.

To help with the upgrade process, some useful variables are available on the defaultTheme:

import { defaultTheme } from 'evergreen-ui'
  • defaultTheme.colors — functional theme colors.
  • defaultTheme.palette — palette colors. Each color group has 4 variations: lightest, light, base, dark.
  • defaultTheme.scales — blue and neutral have a more advanced scale of 10 colors.
  • defaultTheme.fills — colors used for Avatars, Badges, Pills.

Palette

image

Mapping old colors

  • turquoise is renamed to teal.
  • pink colors is deprecated.
  • orange color is added
  • v4 no longer uses yellow for the warning intent. Please use the orange warning color instead.

Mapping base 500 colors to the defaultTheme.palette

The easiest colors to map to the new colors are base colors. Which previously were labeled as 500.

v3 v4
colors.turquoise['500'] defaultTheme.teal.base
colors.red['500'] defaultTheme.red.base
colors.yellow['500'] defaultTheme.yellow.base
colors.red['500'] defaultTheme.red.base
colors.blue['500'] defaultTheme.blue.base
colors.neutral['500'] defaultTheme.neutral.base
colors.green['500'] defaultTheme.green.base
colors.purple['500'] defaultTheme.purple.base
colors.pink['500'] defaultTheme.orange.base

Mapping dark 1000 colors to the defaultTheme.palette

You can map any color that is 900 or 1000 to the dark variant.

v3 v4
colors.turquoise['1000'] defaultTheme.teal.dark
colors.red['1000'] defaultTheme.red.dark
colors.yellow['1000'] defaultTheme.yellow.dark
colors.red['1000'] defaultTheme.red.dark
colors.blue['1000'] defaultTheme.blue.dark
colors.neutral['1000'] defaultTheme.neutral.dark
colors.green['1000'] defaultTheme.green.dark
colors.purple['1000'] defaultTheme.purple.dark
colors.pink['1000'] defaultTheme.orange.dark

Mapping light 30 colors to the defaultTheme.palette

You can colors that are around 30 to the light variant.
If you need different colors for different states use a lighten/darken function.

v3 v4
colors.turquoise['30'] defaultTheme.teal.light
colors.red['30'] defaultTheme.red.light
colors.yellow['30'] defaultTheme.yellow.light
colors.red['30'] defaultTheme.red.light
colors.blue['30'] defaultTheme.blue.light
colors.neutral['30'] defaultTheme.neutral.light
colors.green['30'] defaultTheme.green.light
colors.purple['30'] defaultTheme.purple.light
colors.pink['30'] defaultTheme.orange.light

Mapping lightest 5 colors to the defaultTheme.palette

You can map colors that are around 5 to the lightest variant.
If you need different colors for different states use a lighten/darken function.

v3 v4
colors.turquoise['5'] defaultTheme.teal.lightest
colors.red['5'] defaultTheme.red.lightest
colors.yellow['5'] defaultTheme.yellow.lightest
colors.red['5'] defaultTheme.red.lightest
colors.blue['5'] defaultTheme.blue.lightest
colors.neutral['5'] defaultTheme.neutral.lightest
colors.green['5'] defaultTheme.green.lightest
colors.purple['5'] defaultTheme.purple.lightest
colors.pink['5'] defaultTheme.orange.lightest

Exact Mapping 3A–400A colors with tinycolor2

v3 v4
color['3A'] tinycolor(color).setAlpha(0.025).toString()
color['5A'] tinycolor(color).setAlpha(0.041).toString()
color['7A'] tinycolor(color).setAlpha(0.057).toString()
color['10A'] tinycolor(color).setAlpha(0.079).toString()
color['15A'] tinycolor(color).setAlpha(0.114).toString()
color['20A'] tinycolor(color).setAlpha(0.146).toString()
color['30A'] tinycolor(color).setAlpha(0.204).toString()
color['40A'] tinycolor(color).setAlpha(0.255).toString()
color['50A'] tinycolor(color).setAlpha(0.301).toString()
color['60A'] tinycolor(color).setAlpha(0.342).toString()
color['70A'] tinycolor(color).setAlpha(0.38).toString()
color['80A'] tinycolor(color).setAlpha(0.415).toString()
color['90A'] tinycolor(color).setAlpha(0.447).toString()
color['100A'] tinycolor(color).setAlpha(0.477).toString()
color['125A'] tinycolor(color).setAlpha(0.544).toString()
color['150A'] tinycolor(color).setAlpha(0.602).toString()
color['175A'] tinycolor(color).setAlpha(0.653).toString()
color['200A'] tinycolor(color).setAlpha(0.699).toString()
color['300A'] tinycolor(color).setAlpha(0.845).toString()
color['400A'] tinycolor(color).setAlpha(0.954).toString()

Exact Mapping 3–400 colors with tinycolor2

v3 v4
color['3A'] tinycolor.mix('white', color, 0.025 * 100).toString()
color['5A'] tinycolor.mix('white', color, 0.041 * 100).toString()
color['7A'] tinycolor.mix('white', color, 0.057 * 100).toString()
color['10A'] tinycolor.mix('white', color, 0.079 * 100).toString()
color['15A'] tinycolor.mix('white', color, 0.114 * 100).toString()
color['20A'] tinycolor.mix('white', color, 0.146 * 100).toString()
color['30A'] tinycolor.mix('white', color, 0.204 * 100).toString()
color['40A'] tinycolor.mix('white', color, 0.255 * 100).toString()
color['50A'] tinycolor.mix('white', color, 0.301 * 100).toString()
color['60A'] tinycolor.mix('white', color, 0.342 * 100).toString()
color['70A'] tinycolor.mix('white', color, 0.38 * 100).toString()
color['80A'] tinycolor.mix('white', color, 0.415 * 100).toString()
color['90A'] tinycolor.mix('white', color, 0.447 * 100).toString()
color['100A'] tinycolor.mix('white', color, 0.477 * 100).toString()
color['125A'] tinycolor.mix('white', color, 0.544 * 100).toString()
color['150A'] tinycolor.mix('white', color, 0.602 * 100).toString()
color['175A'] tinycolor.mix('white', color, 0.653 * 100).toString()
color['200A'] tinycolor.mix('white', color, 0.699 * 100).toString()
color['300A'] tinycolor.mix('white', color, 0.845 * 100).toString()
color['400A'] tinycolor.mix('white', color, 0.954 * 100).toString()

Almost Exact Mapping 600–1000 colors with tinycolor2

There is currently no longer any colors for 600, 700, 800, 900 colors for palette colors.
We do export a new defaultTheme.scales property that exports some extra shades for blue and neutral. However, if you need a almost exact match to the old colors use tinycolor2.

v3 v4
colors.green['600'] tinycolor(palette.green.base).darken(3).toString()
colors.green['700'] tinycolor(palette.green.base).darken(5).toString()
colors.green['800'] tinycolor(palette.green.base).darken(9).toString()
colors.green['900'] tinycolor(palette.green.base).darken(13).toString()
colors.green['1000'] tinycolor(palette.green.base).darken(20).toString()

Mapping Icon example

v3 v4
<WarningIcon color={colors.yellow['500']} /> <Icon icon="warning-sign" color="warning" />

Pane + Card

Appearance is now background.

  • The Pane component no longer accepts the appearance property. Use the background property instead.
    • appearance="selected" is deprecated.
    • appearance="dark" is deprecated.
    • appearance="tint3" is deprecated. Use background="tint2" instead.
v3 v4
<Pane appearance="tint1" /> <Pane background="tint1" />
<Pane appearance="tint2" /> <Pane background="tint2" />
<Pane appearance="tint3" /> <Pane background="tint2" />
<Card appearance="tint1" /> <Card background="tint1" />
<Card appearance="tint2" /> <Card background="tint2" />
<Card appearance="tint3" /> <Card background="tint2" />

Borders are different. extraMuted is deprecated.

  • Borders don't support the extraMuted property anymore. Use muted instead.
  • BorderColors.muted in v3 is default in v4
  • BorderColors.extraMuted in v3 is muted in v4
  • BorderColors.default is deprecated
v3 v4
<Pane borderRight <Pane borderRight />
<Pane borderRight="muted" /> <Pane borderRight="default" />
<Pane borderRight="extraMuted" /> <Pane borderRight="muted" />
<Pane borderRight="default" /> <Pane borderRight="default" />

Button

  • Improvements to Buttons
  • intent property that accepts: none, success, danger, warning
  • appearance property changed to accept: default, minimal, primary
  • Icons now use BlueprintJS icons.

Before / After

v3 v4
<Button appearance="green" /> <Button appearance="primary" intent="success" />
<Button appearance="blue" /> <Button appearance="primary" />
<Button appearance="danger" /> <Button appearance="primary" intent="danger" />
<Button appearance="ghostBlue" /> <Button appearance="minimal" />
<Button appearance="ghost" /> <Button appearance="minimal" />

Icon

  • Icons now use BlueprintJS icons
  • Icons are no longer wrapped in a Box.
  • Icons now accept the size property.
  • aim prop deprecated
  • iconWidth prop deprecated
  • iconHeight prop deprecated
v3 v4
<AddIcon /> <Icon icon="plus" />
<CheckCircleIcon /> <Icon icon="tick-circle" />
<CloseIcon /> <Icon icon="cross" />
<CogIcon /> <Icon icon="cog" />
<DangerIcon /> <Icon icon="error" />
<QuestionIcon /> <Icon icon="info-sign" />
<SearchIcon /> <Icon icon="search" />
<WarningIcon /> <Icon icon="warning-sign" />

Icons with aim

v4 no longer supports the aim property on icons. Instead use the icon for it.

v3 v4
<ArrowIcon /> <Icon icon="arrow-up" />
<ArrowIcon aim="right" /> <Icon icon="arrow-right" />
<ArrowIcon aim="left" /> <Icon icon="arrow-left" />
<ArrowIcon aim="bottom" /> <Icon icon="arrow-bottom" />
<TriangleIcon /> <Icon icon="caret-up" />
<TriangleIcon aim="right" /> <Icon icon="caret-right" />
<TriangleIcon aim="left" /> <Icon icon="caret-left" />
<TriangleIcon aim="down" /> <Icon icon="caret-down" />

Avatar

  • Avatar appearance is deprecated. Use color prop instead.
v3 v4
<Avatar appearance="green" /> <Avatar color="green" />

Badge + Pills

  • Badge + Pill appearance is deprecated. Use color prop instead.
v3 v4
<Badge appearance="green" /> <Badge color="green" />
<Pill appearance="green" /> <Pill color="green" />

Dialog

  • Dialog now implements the intent API.
  • type prop deprecated. Use intent instead.
v3 v4
<Dialog type="danger" {...otherProps} /> <Dialog intent="danger" {...otherProps} />

Alert

  • Alert now implements the intent API.
  • type prop deprecated. Use intent instead.
v3 v4
<Alert type="danger" {...otherProps} /> <Alert intent="danger" {...otherProps} />
<Alert type="warning" {...otherProps} /> <Alert intent="warning" {...otherProps} />
<Alert type="info" {...otherProps} /> <Alert intent="info" {...otherProps} />
<Alert type="success" {...otherProps} /> <Alert intent="success" {...otherProps} />

Typography

All typography information is now passed through the theme.
The following documentation describes the default theme settings.

  • The SubHeading component is deprecated.
  • Typography components now export the ability to set a default margin top: marginTop="default"

Typography - Text

  • Text styles are passed through the theme
  • Text only support size={300 | 400 | 500 | 600}.
    • Default size is now 400
    • 600 is only there for the Link component that can be used as a big breadcrumb in some cases and is based on the Text component

Deprecated

  • color="extraMuted" is deprecated. Use color="muted" instead.
  • isUppercase prop is deprecated.
  • textStyles prop is deprecated.
  • textUppercaseStyles prop is deprecated.

Typography - Paragraph

  • Paragraph no longer implements the Text component...

  • ...instead paragraph styles are passed through the theme independently.

  • Paragraph only supports size={300 | 400 | 500}. Not 600!

    • Default size is now 400
  • inherited props from Text are deprecated

    • isUppercase prop is deprecated.
    • textStyles prop is deprecated.
    • textUppercaseStyles prop is deprecated.

Typography - Heading

  • Heading no longer implements the Text component...

  • ...instead heading styles are passed through the theme independently.

  • Font family display is used for text sizes above and including 20px.

  • Font family ui is used for text sizes below 20px

  • Heading sizes support size={100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900}.

    • size={100} is uppercase
    • Colors are explicitly set in the theme.
  • isUppercase prop is deprecated.

  • inherited props from Text are deprecated

    • textStyles prop is deprecated.
    • textUppercaseStyles prop is deprecated.

Typography - Link

  • appearance deprecated for color.
  • Default color is blue instead of green.
  • Link accepts size={300 | 400 | 500 | 600}.
v3 v4
<Link /> <Link color="green" />
<Link appearance="blue" /> <Link />
<Link appearance="neutral" /> <Link color="neutral" />

Table

  • Components are exported from Table directly. Table.TextCell, Table.Row etc.
  • No longer a default borderRight on table cells.
  • Height is now managed by the Table.Row and default is 48.
  • intent prop added to Table.Row.
v3 v4
TableBody Table.Body
TableHead Table.Head
TableHeaderCell Table.HeaderCell
TextTableHeaderCell Table.TextHeaderCell
SearchTableHeaderCell Table.SearchHeaderCell
TableRow Table.Row
TableCell Table.Cell
TextTableCell Table.TextCell

Scales minorScale and majorScale

Evergreen now exports two helper methods to conform to the 4 (px) minor scale and 8 (px) major scale.

import { minorScale, majorScale } from 'evergreen-ui'

minorScale(1) // => 1*4 = 4
minorScale(3) // => 3*4 = 12

majorScale(1) // => 1*8 = 8
majorScale(2) // => 2*8 = 16
majorScale(3) // => 3*8 = 24
majorScale(4) // => 4*8 = 32

Both scales only except integers as input and will otherwise throw a TypeError.

Stack component and Stacking

V4 ships a fix for nesting Popovers, SelectMenu and Combobox in Dialogs and SideSheets. And layering Dialogs on top of Dialogs. Essentially allowing infinite layering anything that uses a Stack component.

Changes

  • Use React 16.3.0
  • Use createContext API
  • Export StackingContext from evergreen-ui
  • Export Stack component from evergreen-ui
  • Export StackingOrder object with z-index presets from evergreen-ui.
  • Remove zIndex props from Popover, Combobox and Positioner.

StackingContext

The StackingContext is a React context with a default value (z-index) of 10. The StackingContext is currently only used within the Stack component within Evergreen.

Stack component

The Stack component uses the StackingContext which accepts a function as children. That function takes in the zIndex and should return a React node:

static propTypes = {
  /**
   * Function that takes the current z-index and returns a React Node.
   * (zIndex) => ReactNode.
   */
  children: PropTypes.func.isRequired,

  /**
   * Set the value of the stack. This will increment for children.
   */
  value: PropTypes.number
}

Inside of the render function the Stack component first looks for the current value. Passing a value to the component will make sure the highest of the two are used. This is useful because Overlays start at a z-index of 20. See more info below about StackingOrder. The Stack component will pass the current value to the current children, and increment the value for the next consumer.

render() {
  const { children, value } = this.props
  return (
    <StackingContext.Consumer>
      {previousValue => {
        const currentValue = Math.max(value, previousValue)
        const nextValue = currentValue + 1
        return (
          <StackingContext.Provider value={nextValue}>
            {children(currentValue)}
          </StackingContext.Provider>
        )
      }}
    </StackingContext.Consumer>
  )
}

Stack component usage

In most cases Stack will be an internal component, we are exposing it if you want to build custom components on top of this logic.

<Stack value={StackingOrder.POSITIONER}>
  {zIndex => {
    return (
      /* ... code omitted */
    )
  }}
</Stack>

StackingOrder

The values here are somewhat random, the reason why POSITIONER and OVERLAY are 10 apart is that in between the Stack component can increment the z-index — giving a head room of 10 z-indexes.

/**
 * Stacking order contains z-index values that are used through.
 * Note that the Stack component might increase the z-index for certain components.
 */
export default {
  /**
   * Used for focused buttons and controls.
   */
  FOCUSED: 2,

  /**
   * Used as the default for the StackingContext.
   */
  STACKING_CONTEXT: 5,

  /**
   * Used as the default for the Positioner.
   */
  POSITIONER: 10,

  /**
   * Used for the Overlay and everything that's inside such as Dialog + SideSheet.
   */
  OVERLAY: 20,

  /**
   * Used for the toasts in the toaster. Appears on top of everything else.
   */
  TOASTER: 30
}

See Example

I tweeted about this with a video attached. Click the link below to see.

React.createContext is a great solution for solving automatic stacking order when using portals for modal dialogs, popovers, panels and sheets. #reactjs pic.twitter.com/9RImVl2ISV

— Jeroen Ransijn 🌲 (@Jeroen_Ransijn) April 17, 2018

Stuff that came up

When I was doing this PR, I realized there is an issue with the getPosition for the Positioner that creates an infinite loop when there is not enough space on the top or the bottom of the screen. I will fix this outside of the scope of this PR so it will be included in v3 as well.

Another issue that arose from creating examples is when a Popover contains a Combobox — which means another Popover. The Popover closes when trying to select an item from the Combobox. This is because the Popover inside the Popover does not live within the same tree. This should be fixed in a different PR as well.

SelectMenu

  • isMultiSelect prop added to SelectMenu. Use when using multi select.

Other Changes

  • Docs is now using Gatsby v2.

Open Questions

  • Should we implement a intent API for Link instead of color?
  • Add more size for Text component. Useful because Link depends on Text.

jeroenransijn added some commits Apr 17, 2018

jeroenransijn and others added some commits Aug 14, 2018

[v4] Improve (SelectMenu/Editable)Cell and SegmentedControl (#281)
* improve select menu/editable cell and segmented control

* remove border right from cell
[v4] Table.(Editable/SelectMenu)Cell Fixes (#284)
* fix size and isSelectable={true}

* fixes to size and isSelectable={true}
add left, right, top, bottom anchors to side-sheet (#252)
* add left, right, top, bottom anchors to side-sheet

* use Position enum in Side-sheet and cache calls to generating sheetCloseClassName

* move Position to constants and update imports

* fix another Position import

* change SheetClose animation name and make position a required prop
Make the Dialog mobile friendly (#301)
* Make the dialog mobile friendly

This change makes the dialog resize gracefuly to fit the available viewport.

It should be a non-breaking change and the dialog should behave the same on desktop as it did before.

* Add sideOffset

* Remove unnecessary sideOffsetWithUnit variable
dialog: add horizontal scrolling support (#314)
This allows the Dialog to gracefully handle block level content that's too wide by scrolling, preventing the Dialog from overflowing the sides of the viewport.

This should be a non-breaking change.
Add "indeterminate" prop to <Checkbox> (#313)
* Add "indeterminate" prop to <Checkbox>

* Delete extraneous line

* Remove permutation function, use plain JSX

* Fix label

* Add ref callback to set indeterminate prop

* Add indeterminate styles

* Remove console.log

* Make prop order consistent

* Clean up story

* Uncomment commented-out styles

* Remove duplicate styles
Allow grammarly to be disabled for Textarea (#323)
* Allow grammarly to be disabled for Textarea

* Destructure grammarly from props
Improve cancelation behavior for SideSheet, Dialog, and Overlay (#324)
* Extend cancelation handling in Dialog and Overlay

This adds:
- `shouldCloseOnEscapePress` and `shouldCloseOnClick` to `Overlay` and `Dialog`
- Fixes a bug where `Dialog`'s did not trigger the `onCancel` handler when the close button was clicked.

* Add Stories and SideSheet support
add Positioner support for Position.LEFT and Position.RIGHT (#299)
* add Position.LEFT and Position.RIGHT positions to Positioner, Tooltip, and Popover

* alter y axis to keep popover in viewport
@mytototo

This comment has been minimized.

Show comment
Hide comment
@mytototo

mytototo Sep 12, 2018

Hi there,

Thank you for the work on Evergreen, it's amazing !
Do you know when a public release will be available for the v4 ?

Thanks.

mytototo commented Sep 12, 2018

Hi there,

Thank you for the work on Evergreen, it's amazing !
Do you know when a public release will be available for the v4 ?

Thanks.

jeroenransijn and others added some commits Sep 19, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment