diff --git a/NOTICE.txt b/NOTICE.txt index fd6f8617e..f4695b31d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -70,16 +70,17 @@ docs/assets/logo-icon.svg theme-contract/src/main/res/drawable/ic_checkbox_indeterminate.xml theme-contract/src/main/res/drawable/ic_checkbox_selected.xml theme-contract/src/main/res/drawable/ic_chevron_left.xml -theme-contract/src/main/res/drawable/ic_tick.xml theme-contract/src/main/res/drawable/ic_radio_button_selected.xml theme-contract/src/main/res/drawable/ic_switch_selected.xml +theme-contract/src/main/res/drawable/ic_tag_bullet.xml +theme-contract/src/main/res/drawable/ic_tick.xml theme-sosh/src/main/res/drawable/sosh_checkbox_indeterminate.xml theme-sosh/src/main/res/drawable/sosh_checkbox_selected.xml theme-sosh/src/main/res/drawable/sosh_chevron_left.xml -theme-sosh/src/main/res/drawable/sosh_tick.xml theme-sosh/src/main/res/drawable/sosh_radio_button_selected.xml theme-sosh/src/main/res/drawable/sosh_switch_selected.xml +theme-sosh/src/main/res/drawable/sosh_tick.xml theme-sosh/src/main/res/font/sosh_black.ttf theme-sosh/src/main/res/font/sosh_bold.ttf theme-sosh/src/main/res/font/sosh_medium.ttf diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/Component.kt b/app/src/main/java/com/orange/ouds/app/ui/components/Component.kt index 36b001b7a..325f87cca 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/Component.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/Component.kt @@ -29,6 +29,7 @@ import com.orange.ouds.app.ui.components.radiobutton.RadioButtonDemoScreen import com.orange.ouds.app.ui.components.radiobutton.RadioButtonItemDemoScreen import com.orange.ouds.app.ui.components.switch.SwitchDemoScreen import com.orange.ouds.app.ui.components.switch.SwitchItemDemoScreen +import com.orange.ouds.app.ui.components.tag.TagDemoScreen import com.orange.ouds.app.ui.utilities.previewCompatibleClass val components = Component::class.sealedSubclasses.mapNotNull { it.objectInstance } @@ -110,6 +111,13 @@ sealed class Component( { SwitchIllustration() }, listOf(Variant.Switch, Variant.SwitchItem) ) + + data object Tag : Component( + R.string.app_components_tag_label, + R.string.app_components_tag_description_text, + { TagIllustration() }, + demoScreen = { TagDemoScreen() } + ) } sealed class Variant( diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/ComponentIllustrations.kt b/app/src/main/java/com/orange/ouds/app/ui/components/ComponentIllustrations.kt index dadd6fbaa..ace8696e5 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/ComponentIllustrations.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/ComponentIllustrations.kt @@ -37,6 +37,7 @@ import com.orange.ouds.core.component.OudsHorizontalDivider import com.orange.ouds.core.component.OudsLink import com.orange.ouds.core.component.OudsRadioButton import com.orange.ouds.core.component.OudsSwitch +import com.orange.ouds.core.component.OudsTag import com.orange.ouds.core.theme.isOudsInDarkTheme @Composable @@ -147,6 +148,11 @@ fun SwitchIllustration() = ComponentIllustration { } } +@Composable +fun TagIllustration() = ComponentIllustration { + OudsTag(label = stringResource(id = R.string.app_components_common_label_label), status = OudsTag.Status.Positive) +} + @Composable private fun ComponentIllustration(content: @Composable () -> Unit) { CompositionLocalProvider( diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/badge/BadgeDemoScreen.kt b/app/src/main/java/com/orange/ouds/app/ui/components/badge/BadgeDemoScreen.kt index 81d3a39a6..0ede51392 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/badge/BadgeDemoScreen.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/badge/BadgeDemoScreen.kt @@ -32,6 +32,7 @@ import com.orange.ouds.app.ui.components.contentDescriptionArgument import com.orange.ouds.app.ui.components.painterArgument import com.orange.ouds.app.ui.utilities.Code import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenu +import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenuItem import com.orange.ouds.app.ui.utilities.composable.CustomizationFilterChips import com.orange.ouds.app.ui.utilities.composable.CustomizationTextField import com.orange.ouds.app.ui.utilities.composable.DemoScreen @@ -66,26 +67,28 @@ private fun BadgeDemoBottomSheetContent(state: BadgeDemoState) { ) CustomizationFilterChips( modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), - label = stringResource(R.string.app_components_badge_size_label), + label = stringResource(R.string.app_components_common_size_label), chipLabels = OudsBadge.Size.entries.map { it.formattedName }, selectedChipIndex = OudsBadge.Size.entries.indexOf(size), onSelectionChange = { id -> size = OudsBadge.Size.entries[id] } ) val statuses = OudsBadge.Status.entries CustomizationDropdownMenu( - label = stringResource(id = R.string.app_components_badge_status_label), - itemLabels = statuses.map { it.formattedName }, + label = stringResource(id = R.string.app_components_common_status_label), + items = statuses.map { status -> + CustomizationDropdownMenuItem( + label = status.formattedName, + leadingIcon = { + Box( + modifier = Modifier + .fillMaxSize() + .background(status.backgroundColor) + ) + } + ) + }, selectedItemIndex = statuses.indexOf(status), - onSelectionChange = { status = statuses[it] }, - itemLeadingIcons = statuses.map { status -> - { - Box( - modifier = Modifier - .fillMaxSize() - .background(status.backgroundColor) - ) - } - } + onSelectionChange = { status = statuses[it] } ) CustomizationTextField( label = stringResource(R.string.app_components_badge_count_label), diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/button/ButtonDemoScreen.kt b/app/src/main/java/com/orange/ouds/app/ui/components/button/ButtonDemoScreen.kt index 4a797bb1f..5bbfe63fd 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/button/ButtonDemoScreen.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/button/ButtonDemoScreen.kt @@ -67,7 +67,7 @@ private fun ButtonDemoBottomSheetContent(state: ButtonDemoState) { ) CustomizationFilterChips( modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), - label = stringResource(R.string.app_components_button_hierarchy_label), + label = stringResource(R.string.app_components_common_hierarchy_label), chipLabels = OudsButton.Hierarchy.entries.map { it.name }, selectedChipIndex = OudsButton.Hierarchy.entries.indexOf(hierarchy), onSelectionChange = { id -> hierarchy = OudsButton.Hierarchy.entries[id] } diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/coloredbackground/ColoredBackgroundDemoScreen.kt b/app/src/main/java/com/orange/ouds/app/ui/components/coloredbackground/ColoredBackgroundDemoScreen.kt index ff96e3981..b0d4c6c90 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/coloredbackground/ColoredBackgroundDemoScreen.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/coloredbackground/ColoredBackgroundDemoScreen.kt @@ -33,6 +33,7 @@ import com.orange.ouds.app.ui.components.labelArgument import com.orange.ouds.app.ui.components.onClickArgument import com.orange.ouds.app.ui.utilities.Code import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenu +import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenuItem import com.orange.ouds.app.ui.utilities.composable.DemoScreen import com.orange.ouds.app.ui.utilities.formattedName import com.orange.ouds.core.component.OudsButton @@ -58,18 +59,20 @@ private fun ColoredBackgroundDemoBottomSheetContent(state: ColoredBackgroundDemo val colors = OudsColoredBox.Color.entries.filter { it.mode.isSupported } CustomizationDropdownMenu( label = stringResource(id = R.string.app_components_coloredBackground_color_label), - itemLabels = colors.map { it.formattedName }, + items = colors.map { color -> + CustomizationDropdownMenuItem( + label = color.formattedName, + leadingIcon = { + Box( + modifier = Modifier + .fillMaxSize() + .background(color.value) + ) + } + ) + }, selectedItemIndex = colors.indexOf(color), onSelectionChange = { color = colors[it] }, - itemLeadingIcons = colors.map { color -> - { - Box( - modifier = Modifier - .fillMaxSize() - .background(color.value) - ) - } - } ) } } diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/divider/DividerDemoScreen.kt b/app/src/main/java/com/orange/ouds/app/ui/components/divider/DividerDemoScreen.kt index ac18571d9..93390ad1b 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/components/divider/DividerDemoScreen.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/components/divider/DividerDemoScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp import com.orange.ouds.app.R import com.orange.ouds.app.ui.utilities.Code import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenu +import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenuItem import com.orange.ouds.app.ui.utilities.composable.DemoScreen import com.orange.ouds.app.ui.utilities.formattedName import com.orange.ouds.core.component.OudsDivider @@ -52,18 +53,20 @@ private fun DividerDemoBottomSheetContent(state: DividerDemoState) { val colors = OudsDivider.Color.entries CustomizationDropdownMenu( label = stringResource(id = R.string.app_components_common_color_label), - itemLabels = colors.map { it.formattedName }, + items = colors.map { color -> + CustomizationDropdownMenuItem( + label = color.formattedName, + leadingIcon = { + Box( + modifier = Modifier + .fillMaxSize() + .background(color.value) + ) + } + ) + }, selectedItemIndex = colors.indexOf(color), - onSelectionChange = { color = colors[it] }, - itemLeadingIcons = colors.map { color -> - { - Box( - modifier = Modifier - .fillMaxSize() - .background(color.value) - ) - } - } + onSelectionChange = { color = colors[it] } ) } } diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoScreen.kt b/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoScreen.kt new file mode 100644 index 000000000..46f44a7b0 --- /dev/null +++ b/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoScreen.kt @@ -0,0 +1,172 @@ +/* + * Software Name: OUDS Android + * SPDX-FileCopyrightText: Copyright (c) Orange SA + * SPDX-License-Identifier: MIT + * + * This software is distributed under the MIT license, + * the text of which is available at https://opensource.org/license/MIT/ + * or see the "LICENSE" file for more details. + * + * Software description: Android library of reusable graphical components + */ + +package com.orange.ouds.app.ui.components.tag + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.orange.ouds.app.R +import com.orange.ouds.app.ui.components.Component +import com.orange.ouds.app.ui.components.labelArgument +import com.orange.ouds.app.ui.components.painterArgument +import com.orange.ouds.app.ui.utilities.Code +import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenu +import com.orange.ouds.app.ui.utilities.composable.CustomizationDropdownMenuItem +import com.orange.ouds.app.ui.utilities.composable.CustomizationFilterChips +import com.orange.ouds.app.ui.utilities.composable.CustomizationSwitchItem +import com.orange.ouds.app.ui.utilities.composable.CustomizationTextField +import com.orange.ouds.app.ui.utilities.composable.DemoScreen +import com.orange.ouds.app.ui.utilities.formattedName +import com.orange.ouds.core.component.OudsTag +import com.orange.ouds.core.theme.OudsTheme +import com.orange.ouds.core.utilities.OudsPreview +import com.orange.ouds.theme.OudsVersion + +@Composable +fun TagDemoScreen() { + val state = rememberTagDemoState() + DemoScreen( + description = stringResource(id = Component.Tag.descriptionRes), + bottomSheetContent = { TagDemoBottomSheetContent(state = state) }, + codeSnippet = { tagDemoCodeSnippet(state = state) }, + demoContent = { TagDemoContent(state = state) }, + version = OudsVersion.Component.Tag + ) +} + +@Composable +private fun TagDemoBottomSheetContent(state: TagDemoState) { + with(state) { + CustomizationFilterChips( + label = stringResource(R.string.app_components_common_hierarchy_label), + chipLabels = OudsTag.Hierarchy.entries.map { it.name }, + selectedChipIndex = OudsTag.Hierarchy.entries.indexOf(hierarchy), + onSelectionChange = { id -> hierarchy = OudsTag.Hierarchy.entries[id] } + ) + val statuses = OudsTag.Status.entries + CustomizationDropdownMenu( + modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), + label = stringResource(id = R.string.app_components_common_status_label), + items = statuses.map { status -> + CustomizationDropdownMenuItem( + label = status.formattedName, + leadingIcon = { + Box( + modifier = Modifier + .fillMaxSize() + .background(status.backgroundColor(hierarchy)) + ) + }, + enabled = !(status == OudsTag.Status.Disabled && loading) + ) + }, + selectedItemIndex = statuses.indexOf(status), + onSelectionChange = { status = statuses[it] } + ) + CustomizationFilterChips( + modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), + label = stringResource(R.string.app_components_common_layout_label), + chipLabels = TagDemoState.Layout.entries.map { stringResource(it.labelRes) }, + selectedChipIndex = TagDemoState.Layout.entries.indexOf(layout), + onSelectionChange = { id -> layout = TagDemoState.Layout.entries[id] } + ) + CustomizationSwitchItem( + label = stringResource(R.string.app_components_tag_loading_label), + checked = loading, + onCheckedChange = { loading = it }, + enabled = loadingSwitchEnabled + ) + CustomizationFilterChips( + modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), + label = stringResource(R.string.app_components_tag_shape_label), + chipLabels = OudsTag.Shape.entries.map { it.name }, + selectedChipIndex = OudsTag.Shape.entries.indexOf(shape), + onSelectionChange = { id -> shape = OudsTag.Shape.entries[id] } + ) + CustomizationFilterChips( + modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), + label = stringResource(R.string.app_components_common_size_label), + chipLabels = OudsTag.Size.entries.map { it.name }, + selectedChipIndex = OudsTag.Size.entries.indexOf(size), + onSelectionChange = { id -> size = OudsTag.Size.entries[id] } + ) + CustomizationTextField( + modifier = Modifier.padding(top = OudsTheme.spaces.fixed.medium), + label = stringResource(R.string.app_components_common_label_label), + value = label, + onValueChange = { value -> label = value } + ) + } +} + +@Composable +private fun TagDemoContent(state: TagDemoState) { + with(state) { + val loading = if (loading) OudsTag.Loading(null) else null + if (layout == TagDemoState.Layout.TextAndIcon) { + OudsTag( + icon = OudsTag.Icon(painter = painterResource(R.drawable.ic_heart)), + label = label, + hierarchy = hierarchy, + status = status, + size = size, + shape = shape, + loading = loading, + ) + } else { + OudsTag( + hasBullet = layout == TagDemoState.Layout.TextAndBullet, + label = label, + hierarchy = hierarchy, + status = status, + size = size, + shape = shape, + loading = loading, + ) + } + } +} + +private fun Code.Builder.tagDemoCodeSnippet(state: TagDemoState) { + with(state) { + functionCall(OudsTag::class.simpleName.orEmpty()) { + when (layout) { + TagDemoState.Layout.TextAndBullet -> typedArgument("hasBullet", true) + TagDemoState.Layout.TextAndIcon -> { + constructorCallArgument("icon") { + painterArgument(R.drawable.ic_heart) + } + } + TagDemoState.Layout.TextOnly -> {} + } + labelArgument(label) + typedArgument("hierarchy", hierarchy) + typedArgument("shape", shape) + typedArgument("size", size) + typedArgument("status", status) + typedArgument("loading", loading) + } + } +} + +@PreviewLightDark +@Composable +private fun PreviewTagDemoScreen() = OudsPreview { + TagDemoScreen() +} diff --git a/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoState.kt b/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoState.kt new file mode 100644 index 000000000..0e3b84c01 --- /dev/null +++ b/app/src/main/java/com/orange/ouds/app/ui/components/tag/TagDemoState.kt @@ -0,0 +1,103 @@ +/* + * Software Name: OUDS Android + * SPDX-FileCopyrightText: Copyright (c) Orange SA + * SPDX-License-Identifier: MIT + * + * This software is distributed under the MIT license, + * the text of which is available at https://opensource.org/license/MIT/ + * or see the "LICENSE" file for more details. + * + * Software description: Android library of reusable graphical components + */ + +package com.orange.ouds.app.ui.components.tag + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import com.orange.ouds.app.R +import com.orange.ouds.core.component.OudsTag +import com.orange.ouds.core.component.OudsTagDefaults + + +@Composable +fun rememberTagDemoState( + label: String = stringResource(id = R.string.app_components_tag_label), + hierarchy: OudsTag.Hierarchy = OudsTagDefaults.Hierarchy, + layout: TagDemoState.Layout = TagDemoState.Layout.TextOnly, + shape: OudsTag.Shape = OudsTagDefaults.Shape, + size: OudsTag.Size = OudsTagDefaults.Size, + status: OudsTag.Status = OudsTagDefaults.Status, + loading: Boolean = false +) = rememberSaveable(label, hierarchy, layout, shape, size, status, loading, saver = TagDemoState.Saver) { + TagDemoState(label, hierarchy, layout, shape, size, status, loading) +} + +class TagDemoState( + label: String, + hierarchy: OudsTag.Hierarchy, + layout: Layout, + shape: OudsTag.Shape, + size: OudsTag.Size, + status: OudsTag.Status, + loading: Boolean +) { + + companion object { + + val Saver = listSaver( + save = { state -> + with(state) { + listOf( + label, + hierarchy, + layout, + shape, + size, + status, + loading + ) + } + }, + restore = { list: List -> + TagDemoState( + list[0] as String, + list[1] as OudsTag.Hierarchy, + list[2] as Layout, + list[3] as OudsTag.Shape, + list[4] as OudsTag.Size, + list[5] as OudsTag.Status, + list[6] as Boolean + ) + } + ) + } + + var label: String by mutableStateOf(label) + + var hierarchy: OudsTag.Hierarchy by mutableStateOf(hierarchy) + + var layout: Layout by mutableStateOf(layout) + + var shape: OudsTag.Shape by mutableStateOf(shape) + + var size: OudsTag.Size by mutableStateOf(size) + + var status: OudsTag.Status by mutableStateOf(status) + + var loading: Boolean by mutableStateOf(loading) + + val loadingSwitchEnabled: Boolean + get() = status != OudsTag.Status.Disabled + + enum class Layout(@StringRes val labelRes: Int) { + TextOnly(R.string.app_components_common_textOnlyLayout_label), + TextAndBullet(R.string.app_components_tag_textAndBulletLayout_label), + TextAndIcon(R.string.app_components_common_textAndIconLayout_label) + } +} diff --git a/app/src/main/java/com/orange/ouds/app/ui/utilities/composable/CustomizationElements.kt b/app/src/main/java/com/orange/ouds/app/ui/utilities/composable/CustomizationElements.kt index bd0156089..75a4bb781 100644 --- a/app/src/main/java/com/orange/ouds/app/ui/utilities/composable/CustomizationElements.kt +++ b/app/src/main/java/com/orange/ouds/app/ui/utilities/composable/CustomizationElements.kt @@ -143,11 +143,10 @@ fun CustomizationTextField( @Composable fun CustomizationDropdownMenu( label: String, - itemLabels: List, + items: List, selectedItemIndex: Int, onSelectionChange: (Int) -> Unit, modifier: Modifier = Modifier, - itemLeadingIcons: List<@Composable () -> Unit>? = null ) { Column(modifier = modifier.fillMaxWidth()) { Text(modifier = Modifier.padding(horizontal = OudsTheme.grids.margin), text = label, style = labelTextStyle) @@ -162,12 +161,12 @@ fun CustomizationDropdownMenu( modifier = Modifier .menuAnchor(MenuAnchorType.PrimaryNotEditable) .fillMaxWidth(), - value = itemLabels[selectedItemIndex], + value = items[selectedItemIndex].label, onValueChange = {}, readOnly = true, singleLine = true, textStyle = valueLabelTextStyle, - leadingIcon = itemLeadingIcons?.get(selectedItemIndex)?.let { { Box(modifier = leadingIconBoxModifier) { it() } } }, + leadingIcon = items[selectedItemIndex].leadingIcon?.let { { Box(modifier = leadingIconBoxModifier) { it() } } }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = ExposedDropdownMenuDefaults.textFieldColors(), ) @@ -175,17 +174,20 @@ fun CustomizationDropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { - itemLabels.forEachIndexed { index, itemLabel -> + items.forEachIndexed { index, item -> DropdownMenuItem( - text = { Text(text = itemLabel, style = valueLabelTextStyle) }, + text = { Text(text = item.label, style = valueLabelTextStyle) }, onClick = { onSelectionChange(index) expanded = false }, - leadingIcon = itemLeadingIcons?.get(index)?.let { { Box(modifier = leadingIconBoxModifier) { it() } } } + leadingIcon = item.leadingIcon?.let { { Box(modifier = leadingIconBoxModifier) { it() } } }, + enabled = item.enabled ) } } } } } + +data class CustomizationDropdownMenuItem(val label: String, val leadingIcon: (@Composable () -> Unit)? = null, val enabled: Boolean = true) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 95f437f39..b6df86ece 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -98,21 +98,22 @@ اللون خطأ - التخطيط - نص فقط - رمز + نص + التسلسل + أيقون رمز فقط - الأسلوب التسمية + التخطيط على خلفية ملونة - أيقون + الحجم + الحالة + الأسلوب + رمز + نص + نص فقط Component design version شارة الشارة هي عنصر صغير في واجهة المستخدم يُستخدم لتسليط الضوء على الحالة أو الإشعارات أو التصنيف داخل الواجهة. غالبًا ما يتم عرضها كعلامة أو مؤشر بلون خلفية مميز ونص. - الحالة - الحجم النوع قياسي عدد @@ -122,7 +123,6 @@ زر الأزرار تسمح للمستخدمين باتخاذ قرارات أو أداء إجراء. لها عدة أنماط لتلبية احتياجات مختلفة. - التسلسل مربع اختيار @@ -184,6 +184,13 @@ تبديل وصف المحتوى تبديل العنصر + + Tag + A tag is a small element that shows short info like a label, keyword, or category.It helps users quickly find, group, or understand content. + Shape + Text + bullet + Loading + أدوات نظام التصميم diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 290f8e061..757a0f5a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,21 +109,22 @@ Color Error - Layout - Text only - Text + icon + Hierarchy + Icon Icon only - Style Label + Layout On colored background - Icon + Size + Status + Style + Text only + Text + icon Component design version Badge The badge is a small UI element used to highlight status, notifications, or categorization within an interface. It is often displayed as a label or indicator with a distinct background color and text. - Status - Size Type Standard Count @@ -133,7 +134,6 @@ Button Buttons allow users to make choices or perform an action. They have multiple styles for various needs. - Hierarchy Checkbox @@ -195,6 +195,13 @@ Switch content description Switch item + + Tag + A tag is a small element that shows short info like a label, keyword, or category.It helps users quickly find, group, or understand content. + Shape + Text + bullet + Loading + Design System Toolbox diff --git a/core/src/main/java/com/orange/ouds/core/component/OudsBadge.kt b/core/src/main/java/com/orange/ouds/core/component/OudsBadge.kt index 4862b91ab..420cc2645 100644 --- a/core/src/main/java/com/orange/ouds/core/component/OudsBadge.kt +++ b/core/src/main/java/com/orange/ouds/core/component/OudsBadge.kt @@ -323,7 +323,7 @@ object OudsBadge { * Creates an instance of [OudsBadge.Icon]. * * @param painter Painter of the icon. - * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the chip also contains label. + * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the badge also contains label. */ constructor(painter: Painter, contentDescription: String) : this(painter as Any, contentDescription) @@ -331,7 +331,7 @@ object OudsBadge { * Creates an instance of [OudsBadge.Icon]. * * @param imageVector Image vector of the icon. - * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the chip also contains label. + * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the badge also contains label. */ constructor(imageVector: ImageVector, contentDescription: String) : this(imageVector as Any, contentDescription) @@ -339,7 +339,7 @@ object OudsBadge { * Creates an instance of [OudsBadge.Icon]. * * @param bitmap Image bitmap of the icon. - * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the chip also contains label. + * @param contentDescription The content description associated with this [OudsBadge.Icon]. This value is ignored if the badge also contains label. */ constructor(bitmap: ImageBitmap, contentDescription: String) : this(bitmap as Any, contentDescription) diff --git a/core/src/main/java/com/orange/ouds/core/component/OudsTag.kt b/core/src/main/java/com/orange/ouds/core/component/OudsTag.kt new file mode 100644 index 000000000..4a052fe7b --- /dev/null +++ b/core/src/main/java/com/orange/ouds/core/component/OudsTag.kt @@ -0,0 +1,630 @@ +/* + * Software Name: OUDS Android + * SPDX-FileCopyrightText: Copyright (c) Orange SA + * SPDX-License-Identifier: MIT + * + * This software is distributed under the MIT license, + * the text of which is available at https://opensource.org/license/MIT/ + * or see the "LICENSE" file for more details. + * + * Software description: Android library of reusable graphical components + */ + +package com.orange.ouds.core.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.hideFromAccessibility +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.orange.ouds.core.component.content.OudsComponentContent +import com.orange.ouds.core.component.content.OudsComponentIcon +import com.orange.ouds.core.theme.OudsTheme +import com.orange.ouds.core.theme.value +import com.orange.ouds.core.utilities.CheckedContent +import com.orange.ouds.core.utilities.OudsPreview +import com.orange.ouds.core.utilities.PreviewEnumEntries +import com.orange.ouds.foundation.utilities.BasicPreviewParameterProvider + +// TODO: Update documentation URL once it is available +/** + * A tag is a small element that shows short info like a label, keyword, or category. + * It helps users quickly find, group, or understand content. + * + * Tags have seven status depending on the context of the information they represent. Each state is designed + * to convey a specific meaning and ensure clarity in communication. + * + * This tag API allows to: + * - displays only text. Used for simple labels, categories, or keywords without additional visual elements. + * - displays a small indicator (bullet) alongside the text. Used to show status, presence, or activity next to the label. + * + * > Design guidelines: [unified-design-system.orange.com](https://unified-design-system.orange.com) + * + * > Design version: 1.1.0 + * + * @param label The label displayed in the tag. + * @param modifier [Modifier] applied to the tag. + * @param hasBullet Controls the display of a bullet before the tag label. + * @param hierarchy The importance of the tag. Its background color and its content color are based on this hierarchy combined with the [status] of the tag. + * @param status The status of the tag. Its background color and its content color are based on this status combined with the [hierarchy] of the tag. + * A tag with loading spinner cannot have an [OudsTag.Status.Disabled] status. This will throw an [IllegalStateException]. + * @param shape The shape of the tag. This allows to play with its corners appearance. + * @param size The size of the tag. + * @param loading An optional loading spinner (or progress indicator) displayed before the [label]. Used to indicate that a process or action related to the + * tag is in progress. + * A tag with an [OudsTag.Status.Disabled] status cannot have a loading spinner. This will throw an [IllegalStateException]. + */ +@Composable +fun OudsTag( + label: String, + modifier: Modifier = Modifier, + hasBullet: Boolean = false, + hierarchy: OudsTag.Hierarchy = OudsTagDefaults.Hierarchy, + status: OudsTag.Status = OudsTagDefaults.Status, + shape: OudsTag.Shape = OudsTagDefaults.Shape, + size: OudsTag.Size = OudsTagDefaults.Size, + loading: OudsTag.Loading? = null +) { + OudsTag( + hasBullet = hasBullet, + nullableIcon = null, + label = label, + modifier = modifier, + hierarchy = hierarchy, + status = status, + shape = shape, + size = size, + loading = loading + ) +} + +// TODO: Update documentation URL once it is available +/** + * A tag is a small element that shows short info like a label, keyword, or category. + * It helps users quickly find, group, or understand content. + * + * Tags have seven status depending on the context of the information they represent. Each state is designed + * to convey a specific meaning and ensure clarity in communication. + * + * This version displays an icon to visually reinforce the meaning of the tag, such as status, type, or action. + * + * > Design guidelines: [unified-design-system.orange.com](https://unified-design-system.orange.com) + * + * > Design version: 1.1.0 + * + * @param icon The icon displayed before the label. + * @param label The label displayed in the tag. + * @param modifier [Modifier] applied to the tag. + * @param hierarchy The importance of the tag. Its background color and its content color are based on this hierarchy combined with the [status] of the tag. + * @param status The status of the tag. Its background color and its content color are based on this status combined with the [hierarchy] of the tag. + * A tag with loading spinner cannot have an [OudsTag.Status.Disabled] status. This will throw an [IllegalStateException]. + * @param shape The shape of the tag. This allows to play with its corners appearance. + * @param size The size of the tag. + * @param loading An optional loading spinner (or progress indicator) displayed before the [label]. Used to indicate that a process or action related to the + * tag is in progress. + * A tag with an [OudsTag.Status.Disabled] status cannot have a loading spinner. This will throw an [IllegalStateException]. + */ +@Composable +fun OudsTag( + icon: OudsTag.Icon, + label: String, + modifier: Modifier = Modifier, + hierarchy: OudsTag.Hierarchy = OudsTagDefaults.Hierarchy, + status: OudsTag.Status = OudsTagDefaults.Status, + shape: OudsTag.Shape = OudsTagDefaults.Shape, + size: OudsTag.Size = OudsTagDefaults.Size, + loading: OudsTag.Loading? = null +) { + OudsTag( + hasBullet = false, + nullableIcon = icon, + label = label, + modifier = modifier, + hierarchy = hierarchy, + status = status, + shape = shape, + size = size, + loading = loading + ) +} + +@Composable +private fun OudsTag( + hasBullet: Boolean, + nullableIcon: OudsTag.Icon?, + label: String, + modifier: Modifier, + hierarchy: OudsTag.Hierarchy, + status: OudsTag.Status, + shape: OudsTag.Shape, + size: OudsTag.Size, + loading: OudsTag.Loading? +) { + val hasAsset = hasBullet || nullableIcon != null || loading != null + val isForbidden = status == OudsTag.Status.Disabled && loading != null + + val tagShape = shape(shape) + CheckedContent( + expression = !isForbidden, + exceptionMessage = { "An OudsTag with OudsTag.Status.Disabled status cannot have a loading spinner. This is not allowed." }, + previewMessagePaddingValues = contentPadding(size, false), + shape = tagShape + ) { + // This outer box is necessary otherwise the user can change the size of the tag through the modifier + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier + .sizeIn(minWidth = minWidth(size), minHeight = minHeight(size)) + .clip(shape = tagShape) + .background(status.backgroundColor(hierarchy = hierarchy)) + .padding(paddingValues = contentPadding(size = size, hasAsset = hasAsset)), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(betweenAssetAndLabelSpace(size = size), Alignment.CenterHorizontally), + ) { + val contentColor = contentColor(status = status, hierarchy = hierarchy) + + if (hasAsset) { + Box(modifier = Modifier.size(assetSize(size))) { + if (loading != null) { + LoadingIndicator(status = status, hierarchy = hierarchy, size = size, progress = loading.progress) + } else { + val asset = if (hasBullet) OudsTag.Icon(painter = painterResource(id = OudsTheme.drawableResources.tagBullet)) else nullableIcon + val assetPadding = if (hasBullet) bulletPadding(size = size) else iconPadding(size = size) + + asset?.Content( + modifier = Modifier.padding(all = assetPadding), + extraParameters = OudsTag.Icon.ExtraParameters(tint = contentColor) + ) + } + } + } + Text( + text = label, + color = contentColor, + style = textStyle(size) + ) + } + } + } +} + +@Composable +private fun shape(shape: OudsTag.Shape): RoundedCornerShape { + return with(OudsTheme.componentsTokens.tag) { + RoundedCornerShape( + when (shape) { + OudsTag.Shape.Square -> OudsTheme.borders.radius.none + OudsTag.Shape.Rounded -> borderRadius.value + } + ) + } +} + +@Composable +private fun minWidth(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> sizeMinWidthDefault + OudsTag.Size.Small -> sizeMinWidthSmall + }.dp + } +} + +@Composable +private fun minHeight(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> sizeMinHeightDefault + OudsTag.Size.Small -> sizeMinHeightSmall + }.dp + } +} + +@Composable +private fun assetSize(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> sizeAssetDefault + OudsTag.Size.Small -> sizeAssetSmall + }.value + } +} + +@Composable +private fun textStyle(size: OudsTag.Size): TextStyle { + return when (size) { + OudsTag.Size.Default -> OudsTheme.typography.label.strong.medium + OudsTag.Size.Small -> OudsTheme.typography.label.strong.small + }.run { + copy(lineHeightStyle = lineHeightStyle?.copy(alignment = LineHeightStyle.Alignment.Center)) + } +} + +@Composable +private fun betweenAssetAndLabelSpace(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> spaceColumnGapDefault + OudsTag.Size.Small -> spaceColumnGapSmall + }.value + } +} + +@Composable +private fun contentColor(status: OudsTag.Status, hierarchy: OudsTag.Hierarchy): Color { + val disabledContentColor = OudsTheme.colorScheme.content.onAction.disabled + return with(OudsTheme.colorScheme.content.onStatus) { + when (hierarchy) { + OudsTag.Hierarchy.Emphasized -> when (status) { + OudsTag.Status.Neutral -> neutral.emphasized + OudsTag.Status.Accent -> accent.emphasized + OudsTag.Status.Positive -> positive.emphasized + OudsTag.Status.Warning -> warning.emphasized + OudsTag.Status.Negative -> negative.emphasized + OudsTag.Status.Info -> info.emphasized + OudsTag.Status.Disabled -> disabledContentColor + } + OudsTag.Hierarchy.Muted -> when (status) { + OudsTag.Status.Neutral -> neutral.muted + OudsTag.Status.Accent -> accent.muted + OudsTag.Status.Positive -> positive.muted + OudsTag.Status.Warning -> warning.muted + OudsTag.Status.Negative -> negative.muted + OudsTag.Status.Info -> info.muted + OudsTag.Status.Disabled -> disabledContentColor + } + } + } +} + +@Composable +private fun contentPadding(size: OudsTag.Size, hasAsset: Boolean): PaddingValues { + val verticalPadding: Dp + val startPadding: Dp + val endPadding: Dp + with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> { + verticalPadding = spacePaddingBlockDefault.value + startPadding = if (hasAsset) spacePaddingInlineAssetDefault.value else spacePaddingInlineDefault.value + endPadding = spacePaddingInlineDefault.value + } + OudsTag.Size.Small -> { + verticalPadding = spacePaddingBlockSmall.value + startPadding = if (hasAsset) spacePaddingInlineAssetSmall.value else spacePaddingInlineSmall.value + endPadding = spacePaddingInlineSmall.value + } + } + } + + return PaddingValues(top = verticalPadding, bottom = verticalPadding, start = startPadding, end = endPadding) +} + +@Composable +private fun iconPadding(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> spaceInsetIconDefault + OudsTag.Size.Small -> spaceInsetIconSmall + }.value + } +} + +@Composable +private fun bulletPadding(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> spaceInsetBulletDefault.dp + OudsTag.Size.Small -> spaceInsetBulletSmall.value + } + } +} + +@Composable +private fun loaderPadding(size: OudsTag.Size): Dp { + return with(OudsTheme.componentsTokens.tag) { + when (size) { + OudsTag.Size.Default -> spaceInsetLoaderDefault + OudsTag.Size.Small -> spaceInsetLoaderSmall + }.value + } +} + +@Composable +private fun LoadingIndicator(status: OudsTag.Status, hierarchy: OudsTag.Hierarchy, size: OudsTag.Size, progress: Float?) { + val modifier = Modifier + .padding(all = loaderPadding(size = size)) + .fillMaxSize() + .semantics { hideFromAccessibility() } + val color = contentColor(status = status, hierarchy = hierarchy) + val strokeWidth = when (size) { + OudsTag.Size.Default -> 2.4.dp + OudsTag.Size.Small -> 2.dp + } + val trackColor = Color.Transparent + val strokeCap = StrokeCap.Butt + if (progress != null) { + CircularProgressIndicator( + progress = { progress }, + modifier = modifier, + color = color, + strokeWidth = strokeWidth, + trackColor = trackColor, + strokeCap = strokeCap + ) + } else { + CircularProgressIndicator( + modifier = modifier, + color = color, + strokeWidth = strokeWidth, + trackColor = trackColor, + strokeCap = strokeCap + ) + } +} + +/** + * Default values for [OudsTag]. + */ +object OudsTagDefaults { + + /** + * Default hierarchy of an [OudsTag]. + */ + val Hierarchy = OudsTag.Hierarchy.Emphasized + + /** + * Default shape of an [OudsTag]. + */ + val Shape = OudsTag.Shape.Rounded + + /** + * Default size of an [OudsTag]. + */ + val Size = OudsTag.Size.Default + + /** + * Default status of an [OudsTag]. + */ + val Status = OudsTag.Status.Neutral + +} + +/** + * Contains classes to build an [OudsTag]. + */ +object OudsTag { + + enum class Hierarchy { + + /** + * A tag with a solid, high-contrast background. + * Used to draw strong attention to important labels or categories. Emphasized tags stand out prominently against the interface and + * are ideal for primary or high-priority information. + */ + Emphasized, + + /** + * A tag with a subtle, light, or semi-transparent background. + * Used for secondary or less prominent information. Muted tags blend more with the background, providing a softer visual emphasis + * compared to emphasized tags. + */ + Muted + } + + /** + * An icon in an [OudsTag]. + * This icon is non-clickable. No content description is needed because a tag always contains a label. + */ + class Icon private constructor( + graphicsObject: Any + ) : OudsComponentIcon(ExtraParameters::class.java, graphicsObject, "") { + + @ConsistentCopyVisibility + data class ExtraParameters internal constructor( + internal val tint: Color + ) : OudsComponentContent.ExtraParameters() + + /** + * Creates an instance of [OudsTag.Icon]. + * + * @param painter Painter of the icon. + */ + constructor(painter: Painter) : this(painter as Any) + + /** + * Creates an instance of [OudsTag.Icon]. + * + * @param imageVector Image vector of the icon. + */ + constructor(imageVector: ImageVector) : this(imageVector as Any) + + /** + * Creates an instance of [OudsTag.Icon]. + * + * @param bitmap Image bitmap of the icon. + */ + constructor(bitmap: ImageBitmap) : this(bitmap as Any) + + override val tint: Color? + @Composable + get() = extraParameters.tint + } + + enum class Shape { + + /** + * A tag with sharp, square corners. + * Squared tags provide a more formal, structured, or technical feel. They are often used in business contexts to label promotions, offers, or important notices. + */ + Square, + + /** + * A tag with fully rounded corners, creating a pill-shaped appearance. + * Rounded tags offer a softer and more approachable look, suitable for most modern interfaces. + */ + Rounded + } + + enum class Size { + + /** The standard tag size, suitable for most use cases and offering good readability. */ + Default, + + /** A compact tag with reduced height and font size. Used when saving space is important or when grouping elements visually. */ + Small + } + + /** + * The status of an [OudsTag]. This status determines the background and content colors of the tag. + */ + enum class Status { + + /** Default or inactive state. Used for standard labels, categories, or when no specific status needs to be communicated. */ + Neutral, + + /** + * Used to draw attention to new features, recommendations, or content suggestions. + * Invites users to explore and engage with new offerings, creating an exciting and engaging experience. + */ + Accent, + + /** Indicates success, confirmation, or a positive status. Commonly used to highlight completed actions or approved items. */ + Positive, + + /** Signals caution or a potentially risky situation. Used to draw attention to items requiring user awareness or intervention. */ + Warning, + + /** Represents errors, critical issues, or urgent attention needed. Used to highlight problems or failed actions. */ + Negative, + + /** Conveys informational messages or supplementary details. Used for neutral, helpful, or contextual information. */ + Info, + + /** Shows that the tag is inactive and cannot be interacted with. Appears faded or greyed out. */ + Disabled; + + /** + * The tag background color associated with this status. + */ + @Composable + fun backgroundColor(hierarchy: Hierarchy): Color { + val disabledBackgroundColor = OudsTheme.colorScheme.action.disabled + return when (hierarchy) { + Hierarchy.Emphasized -> when (this) { + Neutral -> OudsTheme.colorScheme.surface.status.neutral.emphasized + Accent -> OudsTheme.colorScheme.surface.status.accent.emphasized + Positive -> OudsTheme.colorScheme.surface.status.positive.emphasized + Warning -> OudsTheme.colorScheme.surface.status.warning.emphasized + Negative -> OudsTheme.colorScheme.surface.status.negative.emphasized + Info -> OudsTheme.colorScheme.surface.status.info.emphasized + Disabled -> disabledBackgroundColor + } + Hierarchy.Muted -> when (this) { + Neutral -> OudsTheme.colorScheme.surface.status.neutral.muted + Accent -> OudsTheme.colorScheme.surface.status.accent.muted + Positive -> OudsTheme.colorScheme.surface.status.positive.muted + Warning -> OudsTheme.colorScheme.surface.status.warning.muted + Negative -> OudsTheme.colorScheme.surface.status.negative.muted + Info -> OudsTheme.colorScheme.surface.status.info.muted + Disabled -> disabledBackgroundColor + } + } + } + } + + /** + * Displays a spinner in the input or tag area to indicate that tags are being loaded or processed. + * + * @param progress The loading progress, where 0.0 represents no progress and 1.0 represents full progress. + * Values outside of this range are coerced into the range. + * Set this value to `null` to display a circular indeterminate progress indicator. + */ + data class Loading(val progress: Float?) +} + +@PreviewLightDark +@Composable +@Suppress("PreviewShouldNotBeCalledRecursively") +private fun PreviewOudsTag(@PreviewParameter(OudsTagPreviewParameterProvider::class) parameter: OudsTagPreviewParameter) { + PreviewOudsTag(darkThemeEnabled = isSystemInDarkTheme(), parameter = parameter) +} + +@Composable +internal fun PreviewOudsTag(darkThemeEnabled: Boolean, parameter: OudsTagPreviewParameter) = OudsPreview(darkThemeEnabled = darkThemeEnabled) { + val label = "Label" + with(parameter) { + PreviewEnumEntries { size, status -> + if (icon != null) { + OudsTag( + icon = OudsTag.Icon(imageVector = icon), + label = label, + hierarchy = hierarchy, + status = status, + size = size, + shape = shape, + loading = loading, + ) + } else { + OudsTag( + hasBullet = hasBullet, + label = label, + hierarchy = hierarchy, + status = status, + size = size, + shape = shape, + loading = loading, + ) + } + } + } +} + +internal data class OudsTagPreviewParameter( + val icon: ImageVector? = null, + val hasBullet: Boolean = false, + val hierarchy: OudsTag.Hierarchy = OudsTagDefaults.Hierarchy, + val shape: OudsTag.Shape = OudsTagDefaults.Shape, + val loading: OudsTag.Loading? = null +) + +internal class OudsTagPreviewParameterProvider : BasicPreviewParameterProvider(*previewParameterValues.toTypedArray()) + +private val previewParameterValues: List + get() = listOf( + OudsTagPreviewParameter(null), + OudsTagPreviewParameter(null, true, hierarchy = OudsTag.Hierarchy.Muted), + OudsTagPreviewParameter(Icons.Outlined.FavoriteBorder, shape = OudsTag.Shape.Square), + OudsTagPreviewParameter(loading = OudsTag.Loading(0.6f)) + ) diff --git a/core/src/main/java/com/orange/ouds/core/utilities/CheckedContent.kt b/core/src/main/java/com/orange/ouds/core/utilities/CheckedContent.kt index 14ffc772b..87ec49cf3 100644 --- a/core/src/main/java/com/orange/ouds/core/utilities/CheckedContent.kt +++ b/core/src/main/java/com/orange/ouds/core/utilities/CheckedContent.kt @@ -14,6 +14,7 @@ package com.orange.ouds.core.utilities import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,11 +22,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.orange.ouds.core.theme.LocalColorMode import com.orange.ouds.core.theme.OudsTheme import com.orange.ouds.core.theme.dashedBorder @@ -35,6 +39,8 @@ internal fun CheckedContent( expression: Boolean, exceptionMessage: () -> String, previewMessage: () -> String = { "⛔" }, + previewMessagePaddingValues: PaddingValues = PaddingValues(all = OudsTheme.spaces.fixed.small), + shape: Shape = RectangleShape, content: @Composable () -> Unit ) { // Throw an exception at runtime if expression is false @@ -50,16 +56,17 @@ internal fun CheckedContent( val backgroundColor = if (LocalColorMode.current != null) Color.Black.copy(alpha = 0.68f) else Color.Transparent Box( modifier = Modifier - .dashedBorder(width = 1.dp, color = color, intervals = listOf(10.dp, 5.dp)) + .dashedBorder(width = 1.dp, color = color, shape = shape, intervals = listOf(10.dp, 5.dp)) .background(backgroundColor), contentAlignment = Alignment.Center ) { Box(modifier = Modifier.alpha(0f)) { content() } // Add content but hide it in order to make room for the text Text( - modifier = Modifier.padding(OudsTheme.spaces.fixed.small), + modifier = Modifier.padding(previewMessagePaddingValues), text = previewMessage(), color = color, textAlign = TextAlign.Center, + fontSize = 14.sp, style = TextStyle(fontFamily = FontFamily.Monospace) ) } diff --git a/core/src/test/java/com/orange/ouds/core/component/OudsTagTest.kt b/core/src/test/java/com/orange/ouds/core/component/OudsTagTest.kt new file mode 100644 index 000000000..3c1208a2a --- /dev/null +++ b/core/src/test/java/com/orange/ouds/core/component/OudsTagTest.kt @@ -0,0 +1,36 @@ +/* + * Software Name: OUDS Android + * SPDX-FileCopyrightText: Copyright (c) Orange SA + * SPDX-License-Identifier: MIT + * + * This software is distributed under the MIT license, + * the text of which is available at https://opensource.org/license/MIT/ + * or see the "LICENSE" file for more details. + * + * Software description: Android library of reusable graphical components + */ + +package com.orange.ouds.core.component + +import androidx.compose.runtime.Composable +import com.orange.ouds.OudsSnapshotTest +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +internal class OudsTagTest(private val parameter: OudsTagPreviewParameter) : OudsSnapshotTest() { + + companion object { + @JvmStatic + @Parameterized.Parameters + internal fun data() = OudsTagPreviewParameterProvider().values.toList() + } + + @Composable + override fun Snapshot(darkThemeEnabled: Boolean, highContrastModeEnabled: Boolean) { + PreviewOudsTag( + darkThemeEnabled = darkThemeEnabled, + parameter = parameter + ) + } +} diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[0].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[0].png new file mode 100644 index 000000000..5cf22a24e Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[0].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[1].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[1].png new file mode 100644 index 000000000..c5c5df37e Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[1].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[2].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[2].png new file mode 100644 index 000000000..c4ceae0d7 Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[2].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[3].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[3].png new file mode 100644 index 000000000..84604490f Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeDarkThemeSnapshot[3].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[0].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[0].png new file mode 100644 index 000000000..f23016697 Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[0].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[1].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[1].png new file mode 100644 index 000000000..7c2e88168 Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[1].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[2].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[2].png new file mode 100644 index 000000000..96ca8e659 Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[2].png differ diff --git a/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[3].png b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[3].png new file mode 100644 index 000000000..3cc09be9e Binary files /dev/null and b/core/src/test/snapshots/images/com.orange.ouds.core.component_OudsTagTest_takeLightThemeSnapshot[3].png differ diff --git a/theme-contract/src/main/java/com/orange/ouds/theme/OudsDrawableResources.kt b/theme-contract/src/main/java/com/orange/ouds/theme/OudsDrawableResources.kt index 1be8dd4ba..1d6bb0515 100644 --- a/theme-contract/src/main/java/com/orange/ouds/theme/OudsDrawableResources.kt +++ b/theme-contract/src/main/java/com/orange/ouds/theme/OudsDrawableResources.kt @@ -27,6 +27,8 @@ open class OudsDrawableResources { @DrawableRes get() = R.drawable.ic_radio_button_selected open val switchSelected: Int @DrawableRes get() = R.drawable.ic_switch_selected + open val tagBullet: Int + @DrawableRes get() = R.drawable.ic_tag_bullet open val tick: Int @DrawableRes get() = R.drawable.ic_tick } \ No newline at end of file diff --git a/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsComponentsTokens.kt b/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsComponentsTokens.kt index f8612d798..fcb632f3c 100644 --- a/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsComponentsTokens.kt +++ b/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsComponentsTokens.kt @@ -24,4 +24,5 @@ interface OudsComponentsTokens { val linkMonochrome: OudsLinkMonoTokens val radioButton: OudsRadioButtonTokens val switch: OudsSwitchTokens + val tag: OudsTagTokens } \ No newline at end of file diff --git a/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsTagTokens.kt b/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsTagTokens.kt index d480e19db..b9428efba 100644 --- a/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsTagTokens.kt +++ b/theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsTagTokens.kt @@ -18,7 +18,6 @@ package com.orange.ouds.theme.tokens.components import com.orange.ouds.theme.tokens.OudsBorderKeyToken import com.orange.ouds.theme.tokens.OudsSizeKeyToken import com.orange.ouds.theme.tokens.OudsSpaceKeyToken -import com.orange.ouds.tokens.raw.DimensionRawTokens interface OudsTagTokens { val borderRadius: OudsBorderKeyToken.Radius @@ -43,4 +42,4 @@ interface OudsTagTokens { val spacePaddingInlineAssetSmall: OudsSpaceKeyToken.PaddingInline val spacePaddingInlineDefault: OudsSpaceKeyToken.PaddingInline val spacePaddingInlineSmall: OudsSpaceKeyToken.PaddingInline -} +} \ No newline at end of file diff --git a/theme-contract/src/main/res/drawable/ic_tag_bullet.xml b/theme-contract/src/main/res/drawable/ic_tag_bullet.xml new file mode 100644 index 000000000..52f8e8a5a --- /dev/null +++ b/theme-contract/src/main/res/drawable/ic_tag_bullet.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/theme-orange/src/main/java/com/orange/ouds/theme/orange/tokens/components/OrangeComponentsTokens.kt b/theme-orange/src/main/java/com/orange/ouds/theme/orange/tokens/components/OrangeComponentsTokens.kt index ecf92bd60..74e2bcb14 100644 --- a/theme-orange/src/main/java/com/orange/ouds/theme/orange/tokens/components/OrangeComponentsTokens.kt +++ b/theme-orange/src/main/java/com/orange/ouds/theme/orange/tokens/components/OrangeComponentsTokens.kt @@ -24,6 +24,7 @@ import com.orange.ouds.theme.tokens.components.OudsLinkMonoTokens import com.orange.ouds.theme.tokens.components.OudsLinkTokens import com.orange.ouds.theme.tokens.components.OudsRadioButtonTokens import com.orange.ouds.theme.tokens.components.OudsSwitchTokens +import com.orange.ouds.theme.tokens.components.OudsTagTokens data class OrangeComponentsTokens( override val badge: OudsBadgeTokens = OrangeBadgeTokens(), @@ -36,5 +37,6 @@ data class OrangeComponentsTokens( override val link: OudsLinkTokens = OrangeLinkTokens(), override val linkMonochrome: OudsLinkMonoTokens = OrangeLinkMonoTokens(), override val radioButton: OudsRadioButtonTokens = OrangeRadioButtonTokens(), - override val switch: OudsSwitchTokens = OrangeSwitchTokens() + override val switch: OudsSwitchTokens = OrangeSwitchTokens(), + override val tag: OudsTagTokens = OrangeTagTokens() ) : OudsComponentsTokens \ No newline at end of file diff --git a/theme-sosh/src/main/java/com/orange/ouds/theme/sosh/tokens/components/SoshComponentsTokens.kt b/theme-sosh/src/main/java/com/orange/ouds/theme/sosh/tokens/components/SoshComponentsTokens.kt index 6571c7f5d..f6212508a 100644 --- a/theme-sosh/src/main/java/com/orange/ouds/theme/sosh/tokens/components/SoshComponentsTokens.kt +++ b/theme-sosh/src/main/java/com/orange/ouds/theme/sosh/tokens/components/SoshComponentsTokens.kt @@ -24,6 +24,7 @@ import com.orange.ouds.theme.tokens.components.OudsLinkMonoTokens import com.orange.ouds.theme.tokens.components.OudsLinkTokens import com.orange.ouds.theme.tokens.components.OudsRadioButtonTokens import com.orange.ouds.theme.tokens.components.OudsSwitchTokens +import com.orange.ouds.theme.tokens.components.OudsTagTokens data class SoshComponentsTokens( override val badge: OudsBadgeTokens = SoshBadgeTokens(), @@ -36,5 +37,6 @@ data class SoshComponentsTokens( override val link: OudsLinkTokens = SoshLinkTokens(), override val linkMonochrome: OudsLinkMonoTokens = SoshLinkMonoTokens(), override val radioButton: OudsRadioButtonTokens = SoshRadioButtonTokens(), - override val switch: OudsSwitchTokens = SoshSwitchTokens() + override val switch: OudsSwitchTokens = SoshSwitchTokens(), + override val tag: OudsTagTokens = SoshTagTokens() ) : OudsComponentsTokens \ No newline at end of file