Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Take your shot of Vitamin!

Gerard
March 23, 2023

Take your shot of Vitamin!

Decathlon has more than 160 frontend products, including 50 dedicated to mobile applications. Due to this context, it is hard to align the user interface across all these projects while respecting the platform.

Vitamin is a Design System developed by Decathlon as a product which can be adapted to any context and with multiple technical implementations for Android, iOS and Web. In theory, you can use this Design System in your application and customize it to fit your theme and your needs.

In this presentation, I'll focus on Vitamin Compose, the design and technical architecture, biases and what are the next steps.

Gerard

March 23, 2023
Tweet

More Decks by Gerard

Other Decks in Technology

Transcript

  1. IMPLEMENTATIONS 2,026,487 Figma instances 519 Editors 4411 Viewers 1,501,741 Npm

    downloads 4 variants (CSS, Svelte, Vue, React) 237 stars 356,041 Maven downloads 2 variants (XML, Compose) 275 stars 2 variants (UIKit, SwiftUI) 39 stars
  2. appbars modals quantity-pickers ratings tabs Organisms COMPOSE ARCHITECTURE Foundation Foundation

    Assets Foundation Icons progressbars chips skeleton snackbars switches
  3. Artifact COMPOSE ARCHITECTURE Foundation Foundation Assets Foundation Icons modals vitamin

    quantity-pickers ratings tabs text-inputs progressbars chips skeleton snackbars switches
  4. COLOR PALETTE object VitaminPalette { val vtmnPurple50 = Color(242, 237,

    242) val vtmnPurple100 = Color(220, 207, 221) val vtmnPurple200 = Color(172, 141, 175) val vtmnPurple300 = Color(150, 111, 154) val vtmnPurple400 = Color(108, 78, 111) val vtmnPurple500 = Color(91, 65, 93) val vtmnPurple600 = Color(73, 53, 75) val vtmnPurple700 = Color(44, 32, 45) !" same for blue, green, conifer, yellow, !" orange, red and grey }
  5. SEMANTIC COLORS val vtmnLightColors = VitaminColors( vtmnBackgroundPrimary = VitaminPalette.vtmnWhite, vtmnBackgroundSecondary

    = VitaminPalette.vtmnGrey50, vtmnBackgroundTertiary = VitaminPalette.vtmnGrey100, vtmnBackgroundBrandPrimary = VitaminPalette.vtmnBlue400, vtmnBackgroundBrandSecondary = VitaminPalette.vtmnBlue50, vtmnBackgroundAccent = VitaminPalette.vtmnYellow400, vtmnBackgroundDiscount = VitaminPalette.vtmnRed400, vtmnBackgroundPrimaryReversed = VitaminPalette.vtmnBlack, vtmnBackgroundBrandPrimaryReversed = VitaminPalette.vtmnWhite, !" same for contents, borders and decoratives ) !" same for vtmnDarkColors
  6. FONT FAMILY private val robotoCondensed = FontFamily( Font(R.font.roboto_condensed_regular, FontWeight.Normal, FontStyle.Normal),

    Font(R.font.roboto_condensed_regularitalic, FontWeight.Normal, FontStyle.Italic), Font(R.font.roboto_condensed_bold, FontWeight.Bold, FontStyle.Normal), Font(R.font.roboto_condensed_bolditalic, FontWeight.Bold, FontStyle.Italic), Font(R.font.roboto_condensed_light, FontWeight.Light, FontStyle.Normal), Font(R.font.roboto_condensed_lightitalic, FontWeight.Light, FontStyle.Italic), ) private val roboto = FontFamily( Font(R.font.roboto_regular, FontWeight.Normal, FontStyle.Normal), Font(R.font.roboto_italic, FontWeight.Normal, FontStyle.Italic), Font(R.font.roboto_bold, FontWeight.Bold, FontStyle.Normal), Font(R.font.roboto_bolditalic, FontWeight.Bold, FontStyle.Italic), Font(R.font.roboto_light, FontWeight.Light, FontStyle.Normal), Font(R.font.roboto_lightitalic, FontWeight.Light, FontStyle.Italic), )
  7. SEMANTIC TYPOGRAPHY val vtmnTypography = VitaminTypography( h1 = TextStyle( fontFamily

    = robotoCondensed, fontSize = 42.sp, fontWeight = FontWeight.W700, lineHeight = 44.sp ), !" h2, h3, h4, h5, h6, subtitle1, subtitle2 text1 = TextStyle( fontFamily = roboto, fontSize = 17.sp, fontWeight = FontWeight.W400, lineHeight = 28.sp ), !" text2, text3, text1 bold, text2 bold and text3 bold button = TextStyle( fontFamily = roboto, fontSize = 16.sp, fontWeight = FontWeight.W700, lineHeight = 16.sp ), !" caption, overline )
  8. SEMANTIC SHAPES private val radius100 = 4.dp private val radius200

    = 8.dp private val radius300 = 12.dp private val radius400 = 16.dp private val radius500 = 20.dp private val radius600 = 24.dp private val radius700 = 32.dp private val radius800 = 48.dp val vtmnShapes = VitaminShapes( radius100 = RoundedCornerShape(radius100), radius200 = RoundedCornerShape(radius200), radius300 = RoundedCornerShape(radius300), radius400 = RoundedCornerShape(radius400), radius500 = RoundedCornerShape(radius500), radius600 = RoundedCornerShape(radius600), radius700 = RoundedCornerShape(radius700), radius800 = RoundedCornerShape(radius800) ) Radius 100 Radius 200 Radius 300 Radius 400 Radius 500 Radius 600 Radius 700 Radius 800
  9. LOCALS internal val LocalVitaminColors = compositionLocalOf<VitaminColors> { error("No VitaminColorPalette provided")

    } internal val LocalVitaminTypographies = compositionLocalOf<VitaminTypography> { error("No VitaminTypography provided") } internal val LocalVitaminShapes = compositionLocalOf<VitaminShapes> { error("No VitaminShapes provided") }
  10. LOCALS internal val LocalVitaminColors = compositionLocalOf<VitaminColors> { !# !!$ !%

    } internal val LocalVitaminTypographies = compositionLocalOf<VitaminTypography> { !# !!$ !% } internal val LocalVitaminShapes = compositionLocalOf<VitaminShapes> { !" !!# !$ } object VitaminTheme { val colors: VitaminColors @Composable get() = LocalVitaminColors.current val typography: VitaminTypography @Composable get() = LocalVitaminTypographies.current val shapes: VitaminShapes @Composable get() = LocalVitaminShapes.current }
  11. LOCALS @Composable internal fun ProvideVitaminResources( colors: VitaminColors, typography: VitaminTypography, shapes:

    VitaminShapes, content: @Composable () !& Unit ) { val colors = remember { colors } colors.update(colors) CompositionLocalProvider( LocalVitaminColors provides colors, LocalVitaminTypographies provides typography, LocalVitaminShapes provides shapes ) { ProvideTextStyle(value = typography.text1, content = content) } }
  12. VITAMIN THEME @Composable internal fun ProvideVitaminResources(!# !!$ !%) { !#

    !!$ !% } @Composable fun VitaminTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () !& Unit ) { val colors = if (darkTheme) vtmnDarkColors else vtmnLightColors ProvideVitaminResources(colors, vtmnTypography, vtmnShapes) { MaterialTheme( colors = mdColors(darkTheme, colors), typography = mdTypography, shapes = shapes, content = content ) } }
  13. MATERIAL 2 @Composable fun TopAppBar( title: @Composable () !& Unit,

    modifier: Modifier = Modifier, navigationIcon: @Composable (() !& Unit)? = null, actions: @Composable RowScope.() !& Unit = {}, backgroundColor: Color = MaterialTheme.colors.primarySurface, contentColor: Color = contentColorFor(backgroundColor), elevation: Dp = AppBarDefaults.TopAppBarElevation )
  14. MATERIAL 2 @Composable fun TopAppBar( title: @Composable () !& Unit,

    modifier: Modifier = Modifier, navigationIcon: @Composable (() !& Unit)? = null, actions: @Composable RowScope.() !& Unit = {}, backgroundColor: Color = MaterialTheme.colors.primarySurface, contentColor: Color = contentColorFor(backgroundColor), elevation: Dp = AppBarDefaults.TopAppBarElevation )
  15. MATERIAL 2 @Composable fun TopAppBar( title: @Composable () !& Unit,

    modifier: Modifier = Modifier, navigationIcon: @Composable (() !& Unit)? = null, actions: @Composable RowScope.() !& Unit = {}, backgroundColor: Color = MaterialTheme.colors.primarySurface, contentColor: Color = contentColorFor(backgroundColor), elevation: Dp = AppBarDefaults.TopAppBarElevation )
  16. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  17. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  18. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  19. VITAMIN open class ActionItem( val icon: Painter? = null, val

    contentDescription: String?, val content: @Composable () !& Unit = {}, val onClick: () !& Unit, )
  20. VITAMIN VitaminTopBars.Primary( title = "Title", actions = arrayListOf( ActionItem( icon

    = painterResource(R.drawable.ic_vtmn_android_line), contentDescription = "Android", onClick = { } ), ActionItem( contentDescription = null, content = { Text("Windows") }, onClick = { } ), ActionItem( icon = painterResource(R.drawable.ic_vtmn_apple_line), contentDescription = "Apple", onClick = { } ) ) )
  21. MATERIAL 2 TopAppBar( title = { Text(text = "Title") },

    actions = { IconButton(onClick = { }) { Icon( painter = painterResource(R.drawable.ic_vtmn_android_line), contentDescription = "Android" ) } TextButton(onClick = { }) { Text(text = "Windows") } IconButton(onClick = { }) { Icon( painter = painterResource(R.drawable.ic_vtmn_apple_line), contentDescription = "Apple" ) } } )
  22. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  23. VITAMIN object VitaminTopBarColors { @Composable fun primary( background: Color =

    VitaminTheme.colors.vtmnBackgroundPrimary, contentColor: Color = VitaminTheme.colors.vtmnContentPrimary, iconColor: Color = VitaminTheme.colors.vtmnContentPrimary ): TopBarColors = !" !!$ @Composable fun contextual( background: Color = VitaminTheme.colors.vtmnContentActive, contentColor: Color = VitaminTheme.colors.vtmnContentPrimaryReversed, iconColor: Color = VitaminTheme.colors.vtmnContentPrimaryReversed ): TopBarColors = !" !!$ }
  24. MATERIAL 2 !" primary TopAppBar( title = { Text(text =

    "Title") }, backgroundColor = MaterialTheme.colors.surface, contentColor = MaterialTheme.colors.onSurface ) !" contextual TopAppBar( title = { Text(text = "Title") }, backgroundColor = MaterialTheme.colors.primarySurface, contentColor = MaterialTheme.colors.onPrimary )
  25. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  26. VITAMIN object VitaminNavigationIconButtons { @Composable fun PreviousPage( !" !!$ )

    @Composable fun Drawer( !" !!$ ) @Composable fun Context( !" !!$ ) @Composable fun Close( !" !!$ ) }
  27. VITAMIN object VitaminNavigationIconButtons { @Composable fun PreviousPage( onClick: () !&

    Unit, contentDescription: String?, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { IconButtons( onClick = onClick, modifier = modifier, enabled = enabled, interactionSource = interactionSource, icon = { Icon( painter = painterResource(R.drawable.ic_vtmn_chevron_left_line), contentDescription = contentDescription ) } ) } }
  28. VITAMIN VitaminTopBars.Primary( title = "Title", navigationIcon = { PreviousPage( onClick

    = { }, contentDescription = "Come back to previous page" ) } )
  29. MATERIAL 2 TopAppBar( title = { Text(text = "Title") },

    navigationIcon = { IconButton( onClick = { }, content = { Icon( painter = painterResource(R.drawable.ic_vtmn_chevron_left_line), contentDescription = "Come back to previous page" ) } ) } )
  30. VITAMIN object VitaminTopBars { private const val MAX_ACTIONS = 3

    @Composable fun Primary( title: String, modifier: Modifier = Modifier, maxActions: Int = MAX_ACTIONS, actions: List<ActionItem> = emptyList(), expandedMenu: MutableState<Boolean> = remember { mutableStateOf(false) }, colors: TopBarColors = VitaminTopBarColors.primary(), onDismissOverflowMenu: (() !& Unit)? = null, overflowIcon: (@Composable VitaminMenuIconButtons.() !& Unit)? = null, navigationIcon: (@Composable VitaminNavigationIconButtons.() !& Unit)? = null ) }
  31. VITAMIN object VitaminMenuIconButtons { @Composable fun More( onClick: () !&

    Unit, contentDescription: String?, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { IconButtons( onClick = onClick, modifier = modifier, enabled = enabled, interactionSource = interactionSource, icon = { Icon( painter = painterResource(R.drawable.ic_vtmn_more_2_line), contentDescription = contentDescription ) } ) } }
  32. VITAMIN @Composable internal fun OverflowMenu( actions: List<ActionItem>, modifier: Modifier =

    Modifier, expanded: MutableState<Boolean> = remember { mutableStateOf(false) }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onDismissRequest: (() !& Unit)? = null, overflowIcon: @Composable VitaminMenuIconButtons.() !& Unit )
  33. VITAMIN @Composable internal fun OverflowMenu( actions: List<ActionItem>, modifier: Modifier =

    Modifier, expanded: MutableState<Boolean> = remember { mutableStateOf(false) }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onDismissRequest: (() !& Unit)? = null, overflowIcon: @Composable VitaminMenuIconButtons.() !& Unit )
  34. VITAMIN object VitaminMenus { @Composable fun Dropdown( anchor: @Composable ()

    !& Unit, modifier: Modifier = Modifier, expanded: MutableState<Boolean> = remember { mutableStateOf(false) }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onDismissRequest: () !& Unit = {}, children: @Composable VitaminMenuItems.() !& Unit ) }
  35. VITAMIN object VitaminMenus { @Composable fun Dropdown( anchor: @Composable ()

    !& Unit, modifier: Modifier = Modifier, expanded: MutableState<Boolean> = remember { mutableStateOf(false) }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onDismissRequest: () !& Unit = {}, children: @Composable VitaminMenuItems.() !& Unit ) }
  36. VITAMIN val expanded = remember { mutableStateOf(false) } VitaminTopBars.Primary( title

    = "Title", overflowIcon = { More( onClick = { expanded.value = true }, contentDescription = "More", ) }, expandedMenu = expanded )
  37. MATERIAL 2 @Composable fun OverflowMenu() { val expanded = remember

    { mutableStateOf(false) } Box { IconButton(onClick = { expanded.value = true }) { Icon( painter = painterResource(id = R.drawable.ic_vtmn_more_2_line), contentDescription = "More" ) } DropdownMenu( expanded = expanded.value, onDismissRequest = { expanded.value = false }, content = { DropdownMenuItem( onClick = { }, content = { Text(text = "Sub menu 1") } ) } ) } }
  38. @Composable fun TopAppBar( onNavigationClick: () !& Unit, onShareClick: () !&

    Unit, onFavoriteClick: () !& Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false ) { !% !!# }
  39. @Composable fun TopAppBar( onNavigationClick: () !& Unit, onShareClick: () !&

    Unit, onFavoriteClick: () !& Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false ) { VitaminTopBars.Primary( title = "Product page", modifier = modifier ) }
  40. @Composable fun TopAppBar( onNavigationClick: () !& Unit, onShareClick: () !&

    Unit, onFavoriteClick: () !& Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false ) { VitaminTopBars.Primary( title = "Product page", modifier = modifier, navigationIcon = { Context( onClick = onNavigationClick, contentDescription = "Back to product list" ) } ) }
  41. @Composable fun TopAppBar( onNavigationClick: () !& Unit, onShareClick: () !&

    Unit, onFavoriteClick: () !& Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false ) { VitaminTopBars.Primary( !% !!# actions = listOf( ActionItem( icon = rememberVectorPainter(VitaminIcons.Line.Share), contentDescription = "Share product", onClick = onShareClick ), ActionItem( icon = rememberVectorPainter(if (isFavorite) VitaminIcons.Fill.Heart else VitaminIcons.Line.Heart), contentDescription = "Favorite", onClick = onFavoriteClick ) )
  42. @Composable fun ProductInfo( productUi: ProductPageUi, modifier: Modifier = Modifier )

    { Column( modifier = modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { FlowRow( horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically ) { productUi.tags.forEach { tag !" VitaminTags.DecorativeGravel(label = tag) } } } }
  43. @Composable fun ProductInfo( productUi: ProductPageUi, modifier: Modifier = Modifier )

    { Column( modifier = modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { !" !!$ Text( text = productUi.title, style = VitaminTheme.typography.h4 ) } }
  44. @Composable fun ProductInfo( productUi: ProductPageUi, modifier: Modifier = Modifier )

    { Column( modifier = modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { !" !!$ Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { VitaminRatings.ReadOnlyCompact( number = productUi.ratingNote, maxValue = productUi.ratingMax, nbComments = productUi.nbComments, sizes = VitaminRatingSizes.small() ) VitaminPrices.Accent( text = productUi.price ) }
  45. @Composable fun ProductActions( onBasketClick: (ProductPageUi) !& Unit, onOneClick: (ProductPageUi) !&

    Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { VitaminButtons.Conversion( text = "Add to basket", modifier = Modifier.fillMaxWidth(), onClick = { onBasketClick(productUi) }, icon = rememberVectorPainter(VitaminIcons.Line.ShoppingCart) ) } }
  46. @Composable fun ProductActions( onBasketClick: (ProductPageUi) !& Unit, onOneClick: (ProductPageUi) !&

    Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { VitaminButtons.Conversion( text = "Add to basket", modifier = Modifier.fillMaxWidth(), onClick = { onBasketClick(productUi) }, icon = rememberVectorPainter(VitaminIcons.Line.ShoppingCart) ) VitaminButtons.PrimaryReversed( text = "Buy in one click", modifier = Modifier.fillMaxWidth(), onClick = { onOneClick(productUi) }, icon = rememberVectorPainter(VitaminIcons.Line.Flashlight) )
  47. @Composable fun ProductPage( productUi: ProductPageUi, onNavigationClick: () !& Unit, onShareClick:

    () !& Unit, onFavoriteClick: () !& Unit, onBasketClick: (ProductPageUi) !& Unit, onOneClick: (ProductPageUi) !& Unit, picturePagersState: PagerState = rememberPagerState() ) { !% !!# }
  48. @Composable fun ProductPage( !# parameters !% ) { Scaffold( topBar

    = { TopAppBar( onNavigationClick = onNavigationClick, onShareClick = onShareClick, onFavoriteClick = onFavoriteClick, isFavorite = productUi.isFavorite ) }, ) { !" !!# } }
  49. @Composable fun ProductPage( !# parameters !% ) { Scaffold(topBar =

    { !# parameters !% }) { LazyColumn( modifier = Modifier.padding(it), verticalArrangement = Arrangement.spacedBy(32.dp) ) { item { HorizontalPager( index = productUi.selectedPicture, count = productUi.pictures.size, state = picturePagersState, modifier=Modifier.height(300.dp).fillMaxWidth() ) { index !" Image(painter = rememberAsyncImagePainter( model = productUi.pictures[index], ), contentDescription = null, contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth() ) }
  50. @Composable fun ProductPage( !# parameters !% ) { Scaffold(topBar =

    { !# parameters !% }) { LazyColumn( modifier = Modifier.padding(it), verticalArrangement = Arrangement.spacedBy(32.dp) ) { !# !!$ item { ProductInfo(productUi = productUi) } } } }
  51. @Composable fun ProductPage( !# parameters !% ) { Scaffold(topBar =

    { !# parameters !% }) { LazyColumn( modifier = Modifier.padding(it), verticalArrangement = Arrangement.spacedBy(32.dp) ) { !# !!$ item { ProductActions( onBasketClick = onBasketClick, onOneClick = onOneClick ) } } } }
  52. ATOMIC DESIGN Atom Molecule Organism Template Pages Ions core.color.blue 50

    #E7F3F9 100 #BEDEEF 400 #007DBC 500 #00689D 700 #012B49 core.spacing core.typography
  53. Button component.button-medium.primary.color.background.default component.button-medium.primary.color.content.inverse BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100

    #BEDEEF core.color white #FFFFFF semantic.color.container default {core.color.blue.400} semantic.color.content component.button-medium.primary.color.background default {semantic.color.container.highlight} hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.400} tertiary {core.color.blue.50} component.button-medium.primary.color.content text icon {semantic.color.content.inverse} {semantic.color.content.inverse} highlight inverse {core.color.white} {core.color.blue.500} {semantic.color.container.default}
  54. Button component.button-medium.tertiary.color.background.default BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100 #BEDEEF

    core.color white #FFFFFF semantic.color.container default {core.color.blue.400} semantic.color.content component.button-medium.tertiary.color.background default hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.400} tertiary {core.color.blue.50} component.button-medium.tertiary.color.content text icon {semantic.color.content.highlight} highlight inverse {core.color.white} {core.color.blue.500} {semantic.color.container.default} {semantic.color.container.tertiary} {semantic.color.content.highlight} component.button-medium.tertiary.color.content.inverse
  55. Button component.button-medium.tertiary.color.background.default BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100 #BEDEEF

    core.color white #FFFFFF semantic.color.container default {core.color.blue.500} semantic.color.content component.button-medium.tertiary.color.background default hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.500} tertiary component.button-medium.tertiary.color.content text icon {semantic.color.content.highlight} highlight inverse {core.color.white} {core.color.blue.400} {semantic.color.container.default} {semantic.color.container.tertiary} {semantic.color.content.highlight} {core.color.blue.700} component.button-medium.tertiary.color.content.inverse
  56. Button BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100 #BEDEEF core.color

    white #FFFFFF semantic.color.container default {core.color.blue.500} semantic.color.content component.button-medium.tertiary.color.background default hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.500} tertiary component.button-medium.tertiary.color.content text icon {semantic.color.content.highlight} highlight inverse {core.color.white} {core.color.blue.400} {semantic.color.container.default} {semantic.color.container.tertiary} {semantic.color.content.highlight} {core.color.blue.700} component.button-medium.tertiary.color.content.inverse component.button-medium.tertiary.color.background.default
  57. interface ButtonMediumTokens { val containerColor: ColorToken val containerCornerRadius: RadiusToken val

    containerPaddingTop: SpacingToken val containerPaddingBottom: SpacingToken val containerPaddingStart: SpacingToken val containerPaddingEnd: SpacingToken val containerSpaceBetween: SpacingToken val containerElevation: SizingToken val startIconSize: SizingToken val startIconColor: ColorToken val endIconSize: SizingToken val endIconColor: ColorToken val textColor: ColorToken val textStyle: TypographyToken val borderWidth: SizingToken val borderColor: ColorToken }
  58. interface ButtonMediumTokens { val containerColor: ColorToken val containerCornerRadius: RadiusToken val

    containerPaddingTop: SpacingToken val containerPaddingBottom: SpacingToken val containerPaddingStart: SpacingToken val containerPaddingEnd: SpacingToken val containerSpaceBetween: SpacingToken val containerElevation: SizingToken val startIconSize: SizingToken val startIconColor: ColorToken val endIconSize: SizingToken val endIconColor: ColorToken val textColor: ColorToken val textStyle: TypographyToken val borderWidth: SizingToken val borderColor: ColorToken }
  59. object ButtonMediumPrimaryDefaults : ButtonMediumTokens { override val containerColor: ColorToken =

    VitaminColorToken.BackgroundPrimary override val startIconColor: ColorToken = VitaminColorToken.ContentInverse override val endIconColor: ColorToken = VitaminColorToken.ContentInverse override val textColor: ColorToken = VitaminColorToken.ContentInverse !% other component tokens }
  60. Button BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100 #BEDEEF core.color

    white #FFFFFF semantic.color.container default {core.color.blue.500} semantic.color.content component.button-medium.tertiary.color.background default hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.500} tertiary component.button-medium.tertiary.color.content text icon {semantic.color.content.highlight} highlight inverse {core.color.white} {core.color.blue.400} {semantic.color.container.default} {semantic.color.container.tertiary} {semantic.color.content.highlight} {core.color.blue.700} component.button-medium.tertiary.color.content.inverse component.button-medium.tertiary.color.background.default
  61. enum class VitaminColorToken { BackgroundPrimary, BackgroundSecondary, BackgroundTertiary, BackgroundBrandPrimary, BackgroundBrandSecondary, BackgroundAccent,

    BackgroundDiscount, BackgroundPrimaryReversed, BackgroundBrandPrimaryReversed, !" same for contents, borders and decoratives }
  62. class VitaminColors( backgroundPrimary: Color, backgroundSecondary: Color, backgroundTertiary: Color, backgroundBrandPrimary: Color,

    backgroundBrandSecondary: Color, backgroundAccent: Color, backgroundDiscount: Color, backgroundPrimaryReversed: Color, backgroundBrandPrimaryReversed: Color, !" same for content, border and decorative ) { var backgroundPrimary by mutableStateOf(backgroundPrimary) internal set !" same for other properties }
  63. fun VitaminColors.fromToken(value: VitaminColorToken): Color = when (value) { VitaminColorToken.BackgroundPrimary !&

    backgroundPrimary VitaminColorToken.BackgroundSecondary !& backgroundSecondary VitaminColorToken.BackgroundTertiary !& backgroundTertiary VitaminColorToken.BackgroundBrandPrimary !& backgroundBrandPrimary VitaminColorToken.BackgroundBrandSecondary !& backgroundBrandSecondary VitaminColorToken.BackgroundAccent !& backgroundAccent VitaminColorToken.BackgroundDiscount !& backgroundDiscount VitaminColorToken.BackgroundPrimaryReversed !& backgroundPrimaryReversed VitaminColorToken.BackgroundBrandPrimaryReversed !& backgroundBrandPrimaryReversed } @Composable fun VitaminColorToken.toColor(): Color = LocalVitaminColors.current.fromToken(this)
  64. Button BUTTON COLOR TOKENS core.color.blue 50 #E7F3F9 100 #BEDEEF core.color

    white #FFFFFF semantic.color.container default {core.color.blue.500} semantic.color.content component.button-medium.tertiary.color.background default hover active black #001018 400 #007DBC 500 #00689D 700 #012B49 {semantic.color.container.default} highlight {core.color.blue.500} tertiary component.button-medium.tertiary.color.content text icon {semantic.color.content.highlight} highlight inverse {core.color.white} {core.color.blue.400} {semantic.color.container.default} {semantic.color.container.tertiary} {semantic.color.content.highlight} {core.color.blue.700} component.button-medium.tertiary.color.content.inverse component.button-medium.tertiary.color.background.default
  65. object VitaminPalette { val vtmnPurple50 = Color(242, 237, 242) val

    vtmnPurple100 = Color(220, 207, 221) val vtmnPurple200 = Color(172, 141, 175) val vtmnPurple300 = Color(150, 111, 154) val vtmnPurple400 = Color(108, 78, 111) val vtmnPurple500 = Color(91, 65, 93) val vtmnPurple600 = Color(73, 53, 75) val vtmnPurple700 = Color(44, 32, 45) !" same for blue, green, conifer, yellow, !" orange, red and grey }
  66. val darkTheme = VitaminColors( backgroundPrimary = VitaminPalette.grey900, backgroundSecondary = VitaminPalette.grey950,

    backgroundTertiary = VitaminPalette.purple400, backgroundBrandPrimary = VitaminPalette.grey900, backgroundBrandSecondary = VitaminPalette.grey50, backgroundAccent = VitaminPalette.grey50, backgroundDiscount = VitaminPalette.white, backgroundPrimaryReversed = VitaminPalette.white, backgroundBrandPrimaryReversed = VitaminPalette.purple400 )
  67. WHAT IS THE DIFFERENCE? • One source of truth about

    the structure of components • Can build an instance of tokens adapted for each platform • The structure can be read and generate core, semantic and component tokens • You are focus on the external contract of the component
  68. class ButtonMediumColors( val containerColor: Color, val startIconColor: Color, val endIconColor:

    Color, val textColor: Color ) @Composable fun ButtonMediumTokens.colors( containerColor: Color = this.containerColor.toColor(), startIconColor: Color = this.startIconColor.toColor(), endIconColor: Color = this.endIconColor.toColor(), textColor: Color = this.textColor.toColor() ): ButtonMediumColors { return ButtonMediumColors( containerColor = containerColor, startIconColor = startIconColor, endIconColor = endIconColor, textColor = textColor ) }
  69. class ButtonMediumColors( val containerColor: Color, val startIconColor: Color, val endIconColor:

    Color, val textColor: Color ) @Composable fun ButtonMediumTokens.colors( containerColor: Color = this.containerColor.toColor(), startIconColor: Color = this.startIconColor.toColor(), endIconColor: Color = this.endIconColor.toColor(), textColor: Color = this.textColor.toColor() ): ButtonMediumColors { return ButtonMediumColors( containerColor = containerColor, startIconColor = startIconColor, endIconColor = endIconColor, textColor = textColor ) }
  70. @Composable fun ButtonMedium( text: String, modifier: Modifier = Modifier, enabled:

    Boolean = true, icon: Painter? = null, iconSide: IconSide = IconSide.START, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, style: TextStyle = ButtonMediumPrimaryDefaults.textStyle.textStyle(), colors: ButtonMediumColors = ButtonMediumPrimaryDefaults.colors(), sizes: ButtonMediumSizes = ButtonMediumPrimaryDefaults.sizes(), shape: Shape = ButtonMediumPrimaryDefaults.shape(), border: BorderStroke = ButtonMediumPrimaryDefaults.border(), elevation: ButtonElevation = ButtonMediumPrimaryDefaults.elevation(), onClick: () !& Unit )
  71. @Composable fun ButtonMedium( text: String, modifier: Modifier = Modifier, enabled:

    Boolean = true, icon: Painter? = null, iconSide: IconSide = IconSide.START, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, style: TextStyle = ButtonMediumPrimaryDefaults.textStyle.textStyle(), colors: ButtonMediumColors = ButtonMediumPrimaryDefaults.colors(), sizes: ButtonMediumSizes = ButtonMediumPrimaryDefaults.sizes(), shape: Shape = ButtonMediumPrimaryDefaults.shape(), border: BorderStroke = ButtonMediumPrimaryDefaults.border(), elevation: ButtonElevation = ButtonMediumPrimaryDefaults.elevation(), onClick: () !& Unit )
  72. REFERENCES Vitamin Figma Community https://www.figma.com/@decathlon Vitamin Compose https://github.com/Decathlon/vitamin-compose Vitamin Android

    https://github.com/Decathlon/vitamin-android Vitamin iOS https://github.com/Decathlon/vitamin-ios Vitamin Web https://github.com/Decathlon/vitamin-web Slides: https://speakerdeck.com/gerardpaligot/take-your-shot-of-vitamin GitHub Project https://github.com/GerardPaligot/take-your-shot-of-vitamin Decathlon Outdoor https://play.google.com/store/apps/details?id=com.decathlon.quechuafinder