diff --git a/.agents b/.agents new file mode 120000 index 0000000000..5a74acc44d --- /dev/null +++ b/.agents @@ -0,0 +1 @@ +skills \ No newline at end of file diff --git a/.claude b/.claude new file mode 120000 index 0000000000..5a74acc44d --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +skills \ No newline at end of file diff --git a/.opencode b/.opencode new file mode 120000 index 0000000000..5a74acc44d --- /dev/null +++ b/.opencode @@ -0,0 +1 @@ +skills \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 8b175a2874..daca313ea6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -206,6 +206,39 @@ Always consider that changes affect multiple brands. Test components with: See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines. +## AI Skills + +Agent skills are located in the `skills/` directory at the repository root. The `.agents/`, `.claude/`, and `.opencode/` directories are symlinks to `skills/`, ensuring compatibility across all major AI agent tools. + +### Available skills + +| Skill | When to load | +|---|---| +| `understanding-ouds-android-vocabulary` | User asks to explain, define, or understand OUDS-specific terminology and concepts (tokens, Tokenator, OudsThemeContract, tinted, OudsError, etc.) or relationships between OUDS architecture elements | +| `using-ouds-android` | User needs to write code, create components, set up OUDS, configure themes, access tokens, or use any Ouds* component in Kotlin/Compose | + +### Skill structure + +``` +skills/ +├── understanding-ouds-android-vocabulary/ +│ └── SKILL.md ← vocabulary definitions and token hierarchy +└── using-ouds-android/ + ├── SKILL.md ← setup, themes, token access, common patterns, checklist, troubleshooting + └── references/ + ├── components-index.md ← complete component cross-reference + ├── action-components.md ← OudsButton, OudsFloatingActionButton, OudsNavigationButton, OudsSmallButton + ├── alert-components.md ← OudsAlertMessage, OudsInlineAlert + ├── content-components.md ← OudsBulletList + ├── control-components.md ← OudsCheckbox, OudsCheckboxItem, OudsRadioButton, OudsRadioButtonItem, OudsSwitch, OudsSwitchItem, OudsFilterChip, OudsSuggestionChip + ├── indicator-components.md ← OudsBadge, OudsCircularProgressIndicator, OudsLinearProgressIndicator, OudsTag + ├── input-components.md ← OudsTextInput, OudsTextArea, OudsPasswordInput, OudsPinCodeInput + ├── layout-components.md ← OudsBottomSheetScaffold, OudsColoredBox, OudsDivider, OudsModalBottomSheet + ├── navigation-components.md ← OudsLink, OudsNavigationBar, OudsTopAppBar + ├── component-tokens.md ← advanced component-level tokens + └── tokens.md ← semantic tokens (color, typography, spacing, etc.) +``` + ## Resources - **Documentation**: https://android.unified-design-system.orange.com/ diff --git a/skills/understanding-ouds-android-vocabulary/SKILL.md b/skills/understanding-ouds-android-vocabulary/SKILL.md new file mode 100644 index 0000000000..38805fdabf --- /dev/null +++ b/skills/understanding-ouds-android-vocabulary/SKILL.md @@ -0,0 +1,65 @@ +--- +name: understanding-ouds-android-vocabulary +description: Use this skill whenever the user asks about OUDS-specific terminology, concepts, or vocabulary. This includes questions about tokens (raw, semantic, component), Tokenator, OudsThemeContract, OudsTheme, OudsColoredBox, tinted parameters, OudsError, or any OUDS-specific classes and patterns. ALWAYS trigger when the user asks to explain, define, clarify, understand the difference, or asks 'what is', 'how does X work', 'why do we use', 'what's the purpose of', or any conceptual question about OUDS concepts, even if they don't use these exact phrases. Also trigger when they ask about relationships between OUDS architecture elements (e.g., how tokens relate to each other, theme hierarchy, how Tokenator generates code, where tokens are defined). Trigger even when they seem confused about OUDS terminology without explicitly asking for definitions, or when they use incorrect terminology that suggests they need clarification. +license: MIT +--- + +# OUDS Android Vocabulary + +| Term | Definition | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Tokenator** | Internal tool that converts Figma-exported JSON token specs into Kotlin source files and submits them via pull requests. | +| **token** | Named variable holding a design value (color, size, spacing, border…); most tokens are produced by Tokenator | +| **raw token** | Token whose value is a primitive Kotlin/Compose type (`Color`, `Dp`, `Int`…); grouped in the `:global-raw-tokens` module (e.g. `OudsColorRawTokens`, `OudsBorderRawTokens`) | +| **semantic token** | Token that references a raw token and carries semantic meaning (e.g. `colorScheme.action.enabled`); used directly inside components via `OudsTheme.*` | +| **component token** | Token scoped to a specific component, referencing semantic tokens for per-component styling overrides (e.g. `OudsButtonTokens`, `OudsTagTokens`); exposed to consumers via `@OptIn(RestrictedOudsApi::class) OudsTheme.components` | +| **OudsThemeContract** | Kotlin interface that every theme must implement; centralises all semantic token groups (`colorTokens`, `borderTokens`, `fontTokens`, `spaceTokens`, `componentsTokens`, etc.) and drawable resources | +| **theme** | Cohesive set of tokens and assets (fonts, drawables) controlling the look and feel of an app; available themes: `OrangeTheme`, `OrangeCompactTheme`, `SoshTheme`, `WireframeTheme` | +| **OudsTheme** | The Jetpack Compose entry-point composable that wraps your UI with a given theme; it also exposes static accessors (`OudsTheme.colorScheme`, `OudsTheme.spaces`, `OudsTheme.borders`, etc.) for reading token values inside composables | +| **component** | Jetpack Compose composable shipped by OUDS, always prefixed with `Ouds` (e.g. `OudsButton`, `OudsTag`, `OudsCheckboxItem`); token-driven, accessible, multi-brand | +| **OudsColoredBox** | Special OUDS container composable that creates a semantically colored surface; child OUDS components automatically switch to their monochrome variant to maximise contrast | +| **OudsButtonIcon** | Wrapper class used to pass an icon to `OudsButton` or `OudsSmallButton`; accepts `ImageVector`, `Painter`, or `ImageBitmap`, plus a `tinted` flag | +| **OudsControlItemIcon** | Wrapper class used to pass an optional icon to item-type controls (`OudsCheckboxItem`, `OudsRadioButtonItem`, `OudsSwitchItem`); accepts `ImageVector`, `Painter`, or `ImageBitmap`, plus a `tinted` flag | +| **OudsError** | Data class wrapping an error message (plain `String` or `AnnotatedString`) to display in input components (`OudsTextInput`, `OudsTextArea`, `OudsPasswordInput`, `OudsPinCodeInput`, `OudsCheckboxItem`, etc.) | +| **tinted** | Boolean flag on icon wrapper classes (`OudsButtonIcon`, `OudsControlItemIcon`, `OudsLinkIcon`, etc.) — when `true` (default) the icon color is driven by tokens; when `false` the painter's own colors are preserved (useful for brand/multi-color icons) | + +## Token access inside composables + +Tokens are accessed via the `OudsTheme` static object inside any composable wrapped by `OudsTheme { }`: + +| Accessor | Content | +|-------------------------|-------------------------------------------------------------------------------------------------| +| `OudsTheme.colorScheme` | Color semantic tokens (content, background, border, action, surface…) | +| `OudsTheme.borders` | Border radius, style and width tokens | +| `OudsTheme.spaces` | Spacing tokens (`fixed.*`, `scaled.*`) | +| `OudsTheme.sizes` | Size tokens | +| `OudsTheme.typography` | Typography / font tokens | +| `OudsTheme.elevations` | Elevation / shadow tokens | +| `OudsTheme.grids` | Grid tokens | +| `OudsTheme.opacities` | Opacity tokens | +| `OudsTheme.effects` | Visual effect tokens | +| `OudsTheme.components` | Component-level tokens for advanced customization (requires `@OptIn(RestrictedOudsApi::class)`) | + +## Token hierarchy + +``` +Figma design tokens + │ + Tokenator (generates Kotlin) + │ + ├── :global-raw-tokens ← raw values (OudsColorRawTokens, OudsDimensionRawTokens…) + │ + └── :theme-contract ← semantic interfaces + component token interfaces + │ + └── :theme-orange / :theme-sosh / :theme-wireframe / :theme-orange-compact + └── concrete token values per brand + │ + └── :core ← Ouds* composables read tokens via OudsTheme.* +``` + +## When to load which skill + +| Task | Skill to load | +|---------------------------------------------------------------------|------------------------------------------------------| +| Write or review Kotlin/Compose code using OUDS components or tokens | `using-ouds-android` | +| Ask about OUDS-specific terminology | `understanding-ouds-android-vocabulary` (this skill) | diff --git a/skills/using-ouds-android/SKILL.md b/skills/using-ouds-android/SKILL.md new file mode 100644 index 0000000000..d0a733cb29 --- /dev/null +++ b/skills/using-ouds-android/SKILL.md @@ -0,0 +1,441 @@ +--- +name: using-ouds-android +description: Use this skill whenever the user needs to write code using OUDS Android components, set up the library, configure themes, access design tokens, debug OUDS code, or fix OUDS-related issues in Kotlin/Compose. This includes adding dependencies, wrapping UI with OudsTheme, creating any Ouds* component (OudsButton, OudsTextInput, OudsNavigationBar, OudsTag, OudsCheckbox, etc.), accessing tokens via OudsTheme.colorScheme/spaces/typography/borders, configuring fonts, handling tinted/untinted icons, using OudsColoredBox, showing error messages with OudsError, switching themes dynamically, troubleshooting component behavior, styling with component tokens, or migrating from Material 3 to OUDS. ALWAYS trigger when the user asks to 'create', 'show me how', 'write a composable', 'set up', 'access', 'fix', 'debug', 'implement', 'migrate', 'add', 'use', 'build', or 'style' anything related to OUDS components or tokens, even if they don't explicitly mention 'OUDS framework' or 'design system'. Also trigger when they want examples, code samples, or ask how to do something UI-related in the context of an OUDS-based Android app. +license: MIT +--- + +# OUDS Android Framework Usage + +## 1. Gradle setup + +Add the OUDS dependencies to your module's `build.gradle.kts`. Choose the theme modules you need: + +```kotlin +dependencies { + // Core components — always required + implementation("com.orange.ouds.android:core:") + + // Choose one or more theme modules: + implementation("com.orange.ouds.android:theme-orange:") // Orange brand + implementation("com.orange.ouds.android:theme-orange-compact:") // Orange Compact variant + implementation("com.orange.ouds.android:theme-sosh:") // Sosh brand + implementation("com.orange.ouds.android:theme-wireframe:") // Wireframe (dev/prototyping) +} +``` + +Latest version available on Maven Central under group `com.orange.ouds.android`. + +--- + +## 2. OudsTheme setup + +Wrap your root composable with `OudsTheme`, passing a theme object: + +```kotlin +import com.orange.ouds.core.theme.OudsTheme +import com.orange.ouds.theme.orange.OrangeTheme +import com.orange.ouds.theme.orange.OrangeFontFamily +import com.orange.ouds.theme.orange.OrangeHelveticaNeueLatin + +@Composable +fun App() { + OudsTheme( + theme = OrangeTheme( + orangeFontFamily = OrangeFontFamily( + latin = OrangeHelveticaNeueLatin.Bundled( + R.font.helvetica_neue_latin_roman, + R.font.helvetica_neue_latin_medium, + R.font.helvetica_neue_latin_bold + ) + ) + ) + ) { + // Your app UI here + } +} +``` + +### Available themes + +| Theme class | Brand | Notes | +|----------------------|----------------|--------------------------------------------------------| +| `OrangeTheme` | Orange | Requires Helvetica Neue font (bundled or downloadable) | +| `OrangeCompactTheme` | Orange Compact | Compact size variant of Orange | +| `SoshTheme` | Sosh | — | +| `WireframeTheme` | Wireframe | For development and prototyping only | + +### OrangeTheme — font options + +**Bundled font (recommended)** (copy `.ttf` files to `res/font/`): + +```kotlin +OrangeTheme( + orangeFontFamily = OrangeFontFamily( + latin = OrangeHelveticaNeueLatin.Bundled( + R.font.helvetica_neue_latin_roman, + R.font.helvetica_neue_latin_medium, + R.font.helvetica_neue_latin_bold + ) + ) +) +``` + +**Downloadable font** (via Android Downloadable Fonts — requires `INTERNET` permission and a `` in the manifest): + +```kotlin +OrangeTheme( + orangeFontFamily = OrangeFontFamily( + latin = OrangeHelveticaNeueLatin.Downloadable + ) +) +// Call once at startup: +OrangeFontFamily.preloadDownloadableFontFamilies(context, listOf(OrangeHelveticaNeueLatin.Downloadable)) { + // Update UI state when ready +} +``` + +### OrangeTheme — optional rounded corner settings + +```kotlin +OrangeTheme( + orangeFontFamily = ..., +roundedCornerButtons = true, +roundedCornerTextInputs = true, +roundedCornerAlertMessages = true, +roundedCornerProgressIndicators = true +) +``` + +--- + +## 3. Accessing tokens inside composables + +Tokens are accessed via the `OudsTheme` static object inside any composable wrapped by `OudsTheme { }`: + +```kotlin +import com.orange.ouds.core.theme.OudsTheme + +@Composable +fun MyView() { + Box( + modifier = Modifier + .background(OudsTheme.colorScheme.background.primary) + .padding(OudsTheme.spaces.fixed.medium) + ) { + Text( + text = stringResource(R.string.my_text), + color = OudsTheme.colorScheme.content.default, + style = OudsTheme.typography.body.medium.default + ) + } +} +``` + +### Token namespaces + +| Accessor | Content | +|-------------------------|----------------------------------------------------------------------------------------------------| +| `OudsTheme.colorScheme` | Color tokens (`.content.*`, `.background.*`, `.border.*`, `.action.*`, `.surface.*`, `.overlay.*`) | +| `OudsTheme.borders` | Border radius (`.radius.*`), style (`.style.*`), width (`.width.*`) | +| `OudsTheme.spaces` | Spacing tokens (`.fixed.*`, `.scaled.*`) | +| `OudsTheme.sizes` | Size tokens | +| `OudsTheme.typography` | Typography / font tokens | +| `OudsTheme.elevations` | Elevation / shadow tokens | +| `OudsTheme.grids` | Grid tokens | +| `OudsTheme.opacities` | Opacity tokens | +| `OudsTheme.effects` | Visual effect tokens | +| `OudsTheme.components` | Component-level tokens for advanced customization (requires `@OptIn(RestrictedOudsApi::class)`) | + +--- + +## 4. OudsColoredBox — colored surfaces + +`OudsColoredBox` creates a semantically colored surface. Some OUDS child components inside automatically switch to their **monochrome** variant for maximum +contrast (such as `OudsButton` or `OudsLink`): + +```kotlin +OudsColoredBox(color = OudsColoredBoxColor.StatusInfoEmphasized) { + // OudsButton inside uses monochrome colors automatically + OudsButton(label = stringResource(R.string.action), onClick = { }) + Text( + text = stringResource(R.string.description), + color = OudsTheme.colorScheme.content.default // automatically inverted + ) +} +``` + +--- + +## 5. Common patterns + +### Tinted vs. untinted icons + +By default, icons passed to OUDS components are **tinted** (color driven by tokens). Pass `tinted = false` to preserve the painter's own colors ( +brand/multi-color icons): + +```kotlin +// Tinted icon (default) +OudsButtonIcon(imageVector = Icons.Filled.FavoriteBorder, contentDescription = "Favorite") + +// Untinted icon — painter colors preserved +OudsButtonIcon(painter = myMultiColorPainter, contentDescription = "Brand", tinted = false) +``` + +The same `tinted` flag is available on `OudsControlItemIcon`, `OudsLinkIcon`, `OudsTagAsset.Icon`, `OudsAlertIcon`, `OudsBadgeIcon`, etc. + +### Error and helper text + +Input components accept both plain strings and rich `AnnotatedString` for error and helper text: + +```kotlin +// Plain error +error = OudsError(message = "This field cannot be empty.") + +// Rich annotated error +error = OudsError( + annotatedMessage = buildOudsAnnotatedErrorMessage { + append("This field ") + withStrong { append("cannot") } + append(" be empty.") + } +) + +// Plain helper text +helperText = "Minimum 8 characters." + +// Rich annotated helper text +helperText = buildOudsAnnotatedHelperText { + append("Password must be ") + withStrong { append("at least 8 characters") } + append(" long.") +} +``` + +### Hardcoded strings — forbidden + +Never use hardcoded strings in OUDS components or any composable: + +```kotlin +// Wrong +OudsButton(label = "Submit", onClick = { }) + +// Correct +OudsButton(label = stringResource(R.string.submit), onClick = { }) +``` + +--- + +## 6. Checklist before writing components + +- `OudsTheme { }` wraps the UI at the root +- All user-visible strings use `stringResource(R.string.*)` +- Icons that should preserve their original colors have `tinted = false` +- Content descriptions are provided for all icon-only elements +- Components inside `OudsColoredBox` do **not** need manual color adjustment +- `OudsButtonAppearance.Negative` must **not** be used inside `OudsColoredBox` +- A disabled component must **not** have a loader simultaneously + +--- + +## 7. Common mistakes and troubleshooting + +### Missing OudsTheme wrapper + +**Problem:** Components don't display correctly, colors are wrong, or app crashes with "CompositionLocal LocalOudsTheme not present" + +**Solution:** Ensure your UI is wrapped with `OudsTheme { }` at the root: + +```kotlin +@Composable +fun App() { + OudsTheme(theme = OrangeTheme(...)) { + // Your composables here + } +} +``` + +### Hardcoded strings + +**Problem:** Strings appear directly in code instead of using resources + +**Solution:** Always use `stringResource(R.string.*)`: + +```kotlin +// Wrong +OudsButton(label = "Submit", onClick = { }) + +// Correct +OudsButton(label = stringResource(R.string.submit), onClick = { }) +``` + +### OudsButtonAppearance.Negative inside OudsColoredBox + +**Problem:** App crashes or displays incorrectly when using `OudsButtonAppearance.Negative` inside `OudsColoredBox` + +**Solution:** The `Negative` appearance is forbidden inside `OudsColoredBox`. Use `Default`, `Strong`, `Brand`, or `Minimal` instead. The button will +automatically adopt its monochrome variant inside `OudsColoredBox`. + +### Disabled component with loader + +**Problem:** A component is both disabled and showing a loader simultaneously + +**Solution:** A disabled component must not have a loader. Choose one state: + +```kotlin +// Wrong +OudsButton( + label = stringResource(R.string.action), + enabled = false, + loader = OudsButtonLoader(progress = null), + onClick = { } +) + +// Correct - either disabled OR loading +OudsButton( + label = stringResource(R.string.action), + loader = OudsButtonLoader(progress = null), + onClick = { } +) +``` + +### Icon not tinted correctly + +**Problem:** Icon appears in wrong color or doesn't match theme + +**Solution:** By default, icons are tinted (color driven by tokens). For brand/multi-color icons, set `tinted = false`: + +```kotlin +// Tinted icon (default) - color from tokens +OudsButtonIcon(imageVector = Icons.Filled.Star, contentDescription = "") + +// Untinted icon - preserves painter's colors +OudsButtonIcon(painter = myBrandIcon, contentDescription = "", tinted = false) +``` + +### Missing content description + +**Problem:** Accessibility warnings or icon-only components without descriptions + +**Solution:** Always provide `contentDescription` for icon-only elements: + +```kotlin +// Wrong +OudsButton( + icon = OudsButtonIcon(imageVector = Icons.Filled.Star, contentDescription = ""), + onClick = { } +) + +// Correct +OudsButton( + icon = OudsButtonIcon( + imageVector = Icons.Filled.Star, + contentDescription = stringResource(R.string.favorite_desc) + ), + onClick = { } +) +``` + +### Font not loaded for OrangeTheme + +**Problem:** Text doesn't display in Helvetica Neue, falls back to system font + +**Solution:** For `OrangeTheme`, you must provide the Helvetica Neue font either as bundled (`.ttf` files in `res/font/`) or downloadable: + +```kotlin +// Bundled font (recommended) +OrangeTheme( + orangeFontFamily = OrangeFontFamily( + latin = OrangeHelveticaNeueLatin.Bundled( + R.font.helvetica_neue_latin_roman, + R.font.helvetica_neue_latin_medium, + R.font.helvetica_neue_latin_bold + ) + ) +) + +// Downloadable font (requires INTERNET permission) +OrangeTheme( + orangeFontFamily = OrangeFontFamily( + latin = OrangeHelveticaNeueLatin.Downloadable + ) +) +``` + +### Token access outside OudsTheme + +**Problem:** `OudsTheme.colorScheme` or other token accessors return unexpected values or crash + +**Solution:** Token accessors only work inside composables wrapped by `OudsTheme { }`. Move token access inside the theme wrapper: + +```kotlin +// Wrong - outside OudsTheme +val color = OudsTheme.colorScheme.background.primary +OudsTheme(theme = OrangeTheme(...)) { + Box(modifier = Modifier.background(color)) +} + +// Correct - inside OudsTheme +OudsTheme(theme = OrangeTheme(...)) { + val color = OudsTheme.colorScheme.background.primary + Box(modifier = Modifier.background(color)) +} +``` + +### Error message not displaying + +**Problem:** Error passed to input component but not showing + +**Solution:** Ensure you're using `OudsError` wrapper with either `message` or `annotatedMessage`: + +```kotlin +// Correct - plain error +error = OudsError(message = stringResource(R.string.error_required)) + +// Correct - rich annotated error +error = OudsError( + annotatedMessage = buildOudsAnnotatedErrorMessage { + append(stringResource(R.string.error_prefix)) + withStrong { append(stringResource(R.string.error_highlight)) } + } +) +``` + +--- + +## 8. Reference documentation + +### Components reference + +Component documentation is organized by category. When the user asks about specific components, consult the relevant reference file: + +- **Action components** (buttons, FAB): [`references/action-components.md`](references/action-components.md) + - OudsButton, OudsFloatingActionButton, OudsNavigationButton, OudsSmallButton + +- **Alert components** (alerts, messages): [`references/alert-components.md`](references/alert-components.md) + - OudsAlertMessage, OudsInlineAlert + +- **Content components** (lists): [`references/content-components.md`](references/content-components.md) + - OudsBulletList + +- **Control components** (checkboxes, switches, chips): [`references/control-components.md`](references/control-components.md) + - OudsCheckbox, OudsCheckboxItem, OudsRadioButton, OudsRadioButtonItem, OudsSwitch, OudsSwitchItem, OudsFilterChip, OudsSuggestionChip + +- **Indicator components** (badges, progress, tags): [`references/indicator-components.md`](references/indicator-components.md) + - OudsBadge, OudsCircularProgressIndicator, OudsLinearProgressIndicator, OudsTag + +- **Input components** (text fields): [`references/input-components.md`](references/input-components.md) + - OudsTextInput, OudsTextArea, OudsPasswordInput, OudsPinCodeInput + +- **Layout components** (containers, dividers): [`references/layout-components.md`](references/layout-components.md) + - OudsBottomSheetScaffold, OudsColoredBox, OudsDivider, OudsModalBottomSheet + +- **Navigation components** (links, bars): [`references/navigation-components.md`](references/navigation-components.md) + - OudsLink, OudsNavigationBar, OudsTopAppBar + +**Complete component index:** See [`references/components-index.md`](references/components-index.md) for a full cross-reference of all components. + +### Tokens reference + +- **Semantic tokens:** See [`references/tokens.md`](references/tokens.md) for color scheme, typography, spacing, sizes, borders, elevations, opacities, effects, + and grids +- **Component tokens:** See [`references/component-tokens.md`](references/component-tokens.md) for advanced component-level tokens (`@RestrictedOudsApi`) diff --git a/skills/using-ouds-android/references/action-components.md b/skills/using-ouds-android/references/action-components.md new file mode 100644 index 0000000000..16eae8bd7d --- /dev/null +++ b/skills/using-ouds-android/references/action-components.md @@ -0,0 +1,200 @@ +# OUDS Android — Action Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsButton](#button) — Default and small buttons +- [OudsFloatingActionButton](#floatingactionbutton) — Floating action button (FAB) +- [OudsNavigationButton](#navigationbutton) — Navigation button with chevron +- [OudsSmallButton](#smallbutton) — Small size button variant + +--- + +## Button + +**Layouts:** text only · icon only · text + icon +**Sizes:** default (`OudsButton`) · small (`OudsSmallButton`) +**Appearances:** `OudsButtonAppearance` — `Default`, `Strong`, `Brand`, `Minimal`, `Negative` +**Note:** `Negative` appearance is forbidden inside `OudsColoredBox`. +Inside `OudsColoredBox`, the button automatically adopts its monochrome variant. + +```kotlin +// Text only +OudsButton( + label = stringResource(R.string.action), + onClick = { } +) + +// Icon only — contentDescription required +OudsButton( + icon = OudsButtonIcon( + imageVector = Icons.Filled.FavoriteBorder, + contentDescription = stringResource(R.string.favorite_desc) + ), + onClick = { } +) + +// Text + icon +OudsButton( + icon = OudsButtonIcon(imageVector = Icons.Filled.FavoriteBorder, contentDescription = ""), + label = stringResource(R.string.action), + onClick = { } +) + +// Untinted icon (multi-color / brand icon) +OudsButton( + icon = OudsButtonIcon(painter = myPainter, contentDescription = "", tinted = false), + onClick = { } +) + +// With loading state +OudsButton( + label = stringResource(R.string.action), + loader = OudsButtonLoader(progress = null), // indeterminate + onClick = { } +) + +// On colored background — colors adjusted automatically +OudsColoredBox(color = OudsColoredBoxColor.StatusInfoEmphasized) { + OudsButton(label = stringResource(R.string.action), onClick = { }) +} +``` + +--- + +## SmallButton + +Same API as `OudsButton` but uses the small size variant. + +```kotlin +OudsSmallButton(label = stringResource(R.string.action), onClick = { }) + +OudsSmallButton( + icon = OudsButtonIcon(imageVector = Icons.Filled.FavoriteBorder, contentDescription = ""), + label = stringResource(R.string.action), + onClick = { } +) +``` + +--- + +## FloatingActionButton + +**Sizes:** `OudsFloatingActionButton` (default) · `OudsSmallFloatingActionButton` · `OudsLargeFloatingActionButton` · `OudsExtendedFloatingActionButton` (with text) +**Appearances:** `OudsFloatingActionButtonAppearance` — `Primary`, `Secondary` + +```kotlin +// Icon only (default size) +OudsFloatingActionButton( + icon = OudsFloatingActionButtonIcon( + imageVector = Icons.Filled.Add, + contentDescription = stringResource(R.string.add) + ), + onClick = { } +) + +// Small size +OudsSmallFloatingActionButton( + icon = OudsFloatingActionButtonIcon( + imageVector = Icons.Filled.Edit, + contentDescription = stringResource(R.string.edit) + ), + onClick = { } +) + +// Large size +OudsLargeFloatingActionButton( + icon = OudsFloatingActionButtonIcon( + imageVector = Icons.Filled.FavoriteBorder, + contentDescription = stringResource(R.string.favorite) + ), + onClick = { } +) + +// Extended (with text) +OudsExtendedFloatingActionButton( + text = stringResource(R.string.create), + icon = OudsFloatingActionButtonIcon(imageVector = Icons.Filled.Add, contentDescription = ""), + onClick = { } +) + +// With secondary appearance +OudsFloatingActionButton( + icon = OudsFloatingActionButtonIcon( + imageVector = Icons.Filled.Settings, + contentDescription = stringResource(R.string.settings) + ), + appearance = OudsFloatingActionButtonAppearance.Secondary, + onClick = { } +) + +// With untinted icon (multi-color) +OudsFloatingActionButton( + icon = OudsFloatingActionButtonIcon( + painter = myBrandPainter, + contentDescription = stringResource(R.string.brand_action), + tinted = false + ), + onClick = { } +) +``` + +--- + +## NavigationButton + +**Chevrons:** `OudsNavigationButtonChevron` — `Next`, `Previous` +**Appearances:** `OudsNavigationButtonAppearance` — `Default`, `Strong`, `Brand`, `Minimal` +**Note:** `Brand` appearance is forbidden inside `OudsColoredBox`. +Inside `OudsColoredBox`, the button automatically adopts its monochrome variant. + +```kotlin +// Icon only (chevron) +OudsNavigationButton( + chevron = OudsNavigationButtonChevron.Next, + onClick = { } +) + +// With label +OudsNavigationButton( + label = stringResource(R.string.next), + chevron = OudsNavigationButtonChevron.Next, + onClick = { } +) + +// Previous chevron +OudsNavigationButton( + label = stringResource(R.string.previous), + chevron = OudsNavigationButtonChevron.Previous, + onClick = { } +) + +// With appearance +OudsNavigationButton( + label = stringResource(R.string.next), + chevron = OudsNavigationButtonChevron.Next, + appearance = OudsNavigationButtonAppearance.Strong, + onClick = { } +) + +// With loader +OudsNavigationButton( + label = stringResource(R.string.next), + chevron = OudsNavigationButtonChevron.Next, + loader = OudsButtonLoader(progress = null), // indeterminate + onClick = { } +) + +// On colored background — colors adjusted automatically +OudsColoredBox(color = OudsColoredBoxColor.StatusInfoEmphasized) { + OudsNavigationButton( + label = stringResource(R.string.next), + chevron = OudsNavigationButtonChevron.Next, + onClick = { } + ) +} +``` diff --git a/skills/using-ouds-android/references/alert-components.md b/skills/using-ouds-android/references/alert-components.md new file mode 100644 index 0000000000..6c56fd75f9 --- /dev/null +++ b/skills/using-ouds-android/references/alert-components.md @@ -0,0 +1,99 @@ +# OUDS Android — Alert Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsAlertMessage](#alertmessage) — Full-featured alert with actions +- [OudsInlineAlert](#inlinealert) — Compact inline alert + +--- + +## AlertMessage + +**Statuses:** `OudsAlertMessageStatus` — `Neutral`, `Accent(icon?)`, `Positive`, `Warning`, `Negative`, `Info` +**Action link positions:** `OudsAlertMessageActionLinkPosition` — `Bottom` (default), `TopEnd` + +```kotlin +// Minimal +OudsAlertMessage(label = stringResource(R.string.title)) + +// With functional status (no icon param) +OudsAlertMessage( + label = stringResource(R.string.title), + description = stringResource(R.string.description), + status = OudsAlertMessageStatus.Positive, + onClose = { /* dismiss */ } +) + +// With non-functional status and custom icon +OudsAlertMessage( + label = stringResource(R.string.title), + description = stringResource(R.string.description), + status = OudsAlertMessageStatus.Accent(OudsAlertIcon(imageVector = Icons.Filled.FavoriteBorder)), + onClose = { /* dismiss */ }, + actionLink = OudsAlertMessageActionLink( + label = stringResource(R.string.learn_more), + onClick = { /* navigate */ } + ), + bulletList = listOf( + stringResource(R.string.point_1), + stringResource(R.string.point_2) + ) +) + +// With untinted icon +OudsAlertMessage( + label = stringResource(R.string.title), + status = OudsAlertMessageStatus.Accent(OudsAlertIcon(painter = myPainter, tinted = false)), + onClose = { } +) + +// Action link at top end +OudsAlertMessage( + label = stringResource(R.string.title), + status = OudsAlertMessageStatus.Positive, + onClose = { }, + actionLink = OudsAlertMessageActionLink( + label = stringResource(R.string.details), + onClick = { }, + position = OudsAlertMessageActionLinkPosition.TopEnd + ) +) +``` + +--- + +## InlineAlert + +**Statuses:** `OudsInlineAlertStatus` — `Neutral`, `Accent(icon?)`, `Positive`, `Warning`, `Negative`, `Info` +Functional statuses (`Positive`, `Warning`, `Negative`, `Info`) display a default icon automatically; no icon param. + +```kotlin +// Functional status — icon automatic +OudsInlineAlert( + label = stringResource(R.string.label), + status = OudsInlineAlertStatus.Positive +) + +// Non-functional with default icon +OudsInlineAlert( + label = stringResource(R.string.label), + status = OudsInlineAlertStatus.Accent(OudsAlertIcon.Default) +) + +// Non-functional with custom icon +OudsInlineAlert( + label = stringResource(R.string.label), + status = OudsInlineAlertStatus.Accent(OudsAlertIcon(imageVector = Icons.Filled.FavoriteBorder)) +) + +// Non-functional with untinted icon +OudsInlineAlert( + label = stringResource(R.string.label), + status = OudsInlineAlertStatus.Accent(OudsAlertIcon(painter = myPainter, tinted = false)) +) +``` diff --git a/skills/using-ouds-android/references/component-tokens.md b/skills/using-ouds-android/references/component-tokens.md new file mode 100644 index 0000000000..e707987781 --- /dev/null +++ b/skills/using-ouds-android/references/component-tokens.md @@ -0,0 +1,617 @@ +# OUDS Android — Component Tokens Reference + +**Restricted API** (`@RestrictedOudsApi`) — For advanced component customization. + +Component tokens provide granular control over the appearance of OUDS components. These tokens are designed for building custom components or extending existing OUDS components with precise design specifications. + +> **Important:** Most developers should use [semantic tokens](tokens.md) and standard OUDS components. Only use component tokens when you have specific customization needs that cannot be met with the standard API. + +--- + +## When to Use Component Tokens + +**Use component tokens when:** +- Building custom components that need to align with OUDS design language +- Extending existing OUDS components with additional functionality +- Implementing precise design specifications that require component-level token values +- Creating reusable component patterns within your design system + +**Do NOT use component tokens when:** +- Standard OUDS components (e.g., `OudsButton`, `OudsChip`, `OudsTextInput`) meet your needs +- Building typical UI layouts — use [semantic tokens](tokens.md) instead +- You're unsure whether component tokens are needed — start with semantic tokens first + +--- + +## Accessing Component Tokens + +Component tokens are accessed via `OudsTheme.components` and require opt-in to the restricted API: + +```kotlin +import com.orange.ouds.core.theme.OudsTheme +import com.orange.ouds.theme.tokens.utils.RestrictedOudsApi + +@OptIn(RestrictedOudsApi::class) +@Composable +fun MyCustomComponent() { + val buttonPadding = OudsTheme.components.button.space.paddingInline.medium + val buttonRadius = OudsTheme.components.button.border.radius.default + + // Use component tokens for precise customization +} +``` + +--- + +## Available Component Token Categories + +### Action Components + +```kotlin +// Button tokens +OudsTheme.components.button.border.radius.default +OudsTheme.components.button.border.radius.rounded +OudsTheme.components.button.border.width +OudsTheme.components.button.color.background.* +OudsTheme.components.button.color.border.* +OudsTheme.components.button.color.content.* +OudsTheme.components.button.elevation.* +OudsTheme.components.button.opacity.* +OudsTheme.components.button.size.height.* +OudsTheme.components.button.size.icon.* +OudsTheme.components.button.size.minWidth.* +OudsTheme.components.button.space.columnGap.* +OudsTheme.components.button.space.paddingInline.* +OudsTheme.components.button.typography.* + +// Monochrome button variant +OudsTheme.components.buttonMonochrome.color.background.* +OudsTheme.components.buttonMonochrome.color.border.* +OudsTheme.components.buttonMonochrome.color.content.* + +// Link tokens +OudsTheme.components.link.color.content.* +OudsTheme.components.link.size.icon.* +OudsTheme.components.link.space.columnGap.* +OudsTheme.components.link.typography.* + +// Monochrome link variant +OudsTheme.components.linkMonochrome.color.content.* +``` + +### Indicator Components + +```kotlin +// Badge tokens +OudsTheme.components.badge.border.radius +OudsTheme.components.badge.color.background.* +OudsTheme.components.badge.color.content.* +OudsTheme.components.badge.size.height.* +OudsTheme.components.badge.size.icon.* +OudsTheme.components.badge.size.minWidth.* +OudsTheme.components.badge.size.width.* +OudsTheme.components.badge.space.paddingInline.* +OudsTheme.components.badge.typography.* + +// Tag tokens +OudsTheme.components.tag.border.radius.default +OudsTheme.components.tag.border.radius.rounded +OudsTheme.components.tag.border.width +OudsTheme.components.tag.color.background.* +OudsTheme.components.tag.color.border.* +OudsTheme.components.tag.color.content.* +OudsTheme.components.tag.size.height.* +OudsTheme.components.tag.size.icon.* +OudsTheme.components.tag.space.columnGap.* +OudsTheme.components.tag.space.paddingInline.* +OudsTheme.components.tag.typography.* + +// Progress indicator tokens +OudsTheme.components.progressIndicator.border.radius.default +OudsTheme.components.progressIndicator.border.radius.rounded +OudsTheme.components.progressIndicator.color.background.* +OudsTheme.components.progressIndicator.color.content.* +OudsTheme.components.progressIndicator.size.height.* +OudsTheme.components.progressIndicator.size.width.* +OudsTheme.components.progressIndicator.space.rowGap.* +``` + +### Alert Components + +```kotlin +// Alert message tokens +OudsTheme.components.alert.border.radius.default +OudsTheme.components.alert.border.radius.rounded +OudsTheme.components.alert.border.width +OudsTheme.components.alert.color.background.* +OudsTheme.components.alert.color.border.* +OudsTheme.components.alert.color.content.* +OudsTheme.components.alert.size.icon +OudsTheme.components.alert.size.minHeight +OudsTheme.components.alert.size.minHeightBottomActionPlacement +OudsTheme.components.alert.size.minWidth +OudsTheme.components.alert.space.columnGap +OudsTheme.components.alert.space.columnGapAction +OudsTheme.components.alert.space.paddingBlock +OudsTheme.components.alert.space.paddingInline +OudsTheme.components.alert.space.rowGap +OudsTheme.components.alert.space.rowGapAction +OudsTheme.components.alert.space.rowGapBullet +``` + +### Control Components + +```kotlin +// Checkbox tokens +OudsTheme.components.checkbox.border.radius +OudsTheme.components.checkbox.border.width.* +OudsTheme.components.checkbox.color.background.* +OudsTheme.components.checkbox.color.border.* +OudsTheme.components.checkbox.color.content.* +OudsTheme.components.checkbox.elevation.* +OudsTheme.components.checkbox.size.* + +// Radio button tokens +OudsTheme.components.radioButton.border.width.* +OudsTheme.components.radioButton.color.background.* +OudsTheme.components.radioButton.color.border.* +OudsTheme.components.radioButton.color.content.* +OudsTheme.components.radioButton.elevation.* +OudsTheme.components.radioButton.size.* + +// Switch tokens +OudsTheme.components.switch.border.width.* +OudsTheme.components.switch.color.background.* +OudsTheme.components.switch.color.border.* +OudsTheme.components.switch.color.content.* +OudsTheme.components.switch.elevation.* +OudsTheme.components.switch.size.* + +// Control item tokens (CheckboxItem, RadioButtonItem, SwitchItem) +OudsTheme.components.controlItem.color.background.* +OudsTheme.components.controlItem.color.content.* +OudsTheme.components.controlItem.size.icon +OudsTheme.components.controlItem.space.columnGap +OudsTheme.components.controlItem.space.paddingBlock +OudsTheme.components.controlItem.space.paddingInline +OudsTheme.components.controlItem.space.rowGap +OudsTheme.components.controlItem.typography.* + +// Chip tokens +OudsTheme.components.chip.border.radius +OudsTheme.components.chip.border.width +OudsTheme.components.chip.color.background.* +OudsTheme.components.chip.color.border.* +OudsTheme.components.chip.color.content.* +OudsTheme.components.chip.size.height.* +OudsTheme.components.chip.size.icon.* +OudsTheme.components.chip.space.columnGap.* +OudsTheme.components.chip.space.paddingInline.* +OudsTheme.components.chip.typography.* +``` + +### Input Components + +```kotlin +// Text input tokens +OudsTheme.components.textInput.border.radius.default +OudsTheme.components.textInput.border.radius.rounded +OudsTheme.components.textInput.border.width +OudsTheme.components.textInput.color.background.* +OudsTheme.components.textInput.color.border.* +OudsTheme.components.textInput.color.content.* +OudsTheme.components.textInput.opacity.* +OudsTheme.components.textInput.size.height +OudsTheme.components.textInput.size.icon +OudsTheme.components.textInput.space.columnGap +OudsTheme.components.textInput.space.paddingBlock +OudsTheme.components.textInput.space.paddingInline +OudsTheme.components.textInput.space.rowGap +OudsTheme.components.textInput.typography.* + +// Text area tokens +OudsTheme.components.textArea.border.radius.default +OudsTheme.components.textArea.border.radius.rounded +OudsTheme.components.textArea.border.width +OudsTheme.components.textArea.color.background.* +OudsTheme.components.textArea.color.border.* +OudsTheme.components.textArea.color.content.* +OudsTheme.components.textArea.opacity.* +OudsTheme.components.textArea.size.minHeight +OudsTheme.components.textArea.space.paddingBlock +OudsTheme.components.textArea.space.paddingInline +OudsTheme.components.textArea.space.rowGap +OudsTheme.components.textArea.typography.* + +// PIN code input tokens +OudsTheme.components.pinCodeInput.border.radius.default +OudsTheme.components.pinCodeInput.border.radius.rounded +OudsTheme.components.pinCodeInput.border.width +OudsTheme.components.pinCodeInput.color.background.* +OudsTheme.components.pinCodeInput.color.border.* +OudsTheme.components.pinCodeInput.color.content.* +OudsTheme.components.pinCodeInput.opacity.* +OudsTheme.components.pinCodeInput.size.digitWidth +OudsTheme.components.pinCodeInput.size.height +OudsTheme.components.pinCodeInput.space.columnGap +OudsTheme.components.pinCodeInput.space.rowGap +OudsTheme.components.pinCodeInput.typography.* + +// Input tag tokens +OudsTheme.components.inputTag.color.background.* +OudsTheme.components.inputTag.color.border.* +OudsTheme.components.inputTag.color.content.* +``` + +### Layout Components + +```kotlin +// Divider tokens +OudsTheme.components.divider.color.background +OudsTheme.components.divider.size.height + +// Top app bar tokens +OudsTheme.components.bar.topAppBar.color.background +OudsTheme.components.bar.topAppBar.color.content +OudsTheme.components.bar.topAppBar.elevation.* +OudsTheme.components.bar.topAppBar.size.actionIcon +OudsTheme.components.bar.topAppBar.size.height.* +OudsTheme.components.bar.topAppBar.size.navigationIcon +OudsTheme.components.bar.topAppBar.space.columnGap +OudsTheme.components.bar.topAppBar.space.paddingBlock +OudsTheme.components.bar.topAppBar.space.paddingInline +OudsTheme.components.bar.topAppBar.typography.* + +// Navigation bar tokens +OudsTheme.components.bar.navigationBar.color.background +OudsTheme.components.bar.navigationBar.color.content.* +OudsTheme.components.bar.navigationBar.elevation +OudsTheme.components.bar.navigationBar.size.height +OudsTheme.components.bar.navigationBar.size.icon +OudsTheme.components.bar.navigationBar.space.rowGap +OudsTheme.components.bar.navigationBar.typography.* +``` + +### Content Components + +```kotlin +// Bullet list tokens +OudsTheme.components.bulletList.color.content.* +OudsTheme.components.bulletList.size.bullet.* +OudsTheme.components.bulletList.size.icon +OudsTheme.components.bulletList.space.columnGap +OudsTheme.components.bulletList.space.indent +OudsTheme.components.bulletList.space.rowGap + +// Icon tokens +OudsTheme.components.icon.color.content +``` + +--- + +## Component Token Examples + +### Example 1: Custom Button with Component Tokens + +```kotlin +@OptIn(RestrictedOudsApi::class) +@Composable +fun CustomBrandButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + onClick = onClick, + modifier = modifier, + shape = RoundedCornerShape(OudsTheme.components.button.border.radius.rounded), + colors = ButtonDefaults.buttonColors( + containerColor = OudsTheme.components.button.color.background.brand.enabled, + contentColor = OudsTheme.components.button.color.content.brand.enabled + ), + elevation = ButtonDefaults.buttonElevation( + defaultElevation = OudsTheme.components.button.elevation.default + ), + contentPadding = PaddingValues( + horizontal = OudsTheme.components.button.space.paddingInline.medium, + vertical = 0.dp + ) + ) { + Text( + text = text, + style = OudsTheme.components.button.typography.medium.strong + ) + } +} +``` + +### Example 2: Custom Chip with Component Tokens + +```kotlin +@OptIn(RestrictedOudsApi::class) +@Composable +fun CustomChip( + text: String, + icon: ImageVector? = null, + selected: Boolean = false, + onClick: () -> Unit +) { + val backgroundColor = if (selected) { + OudsTheme.components.chip.color.background.outlined.selected + } else { + OudsTheme.components.chip.color.background.outlined.enabled + } + + val contentColor = if (selected) { + OudsTheme.components.chip.color.content.outlined.selected + } else { + OudsTheme.components.chip.color.content.outlined.enabled + } + + Surface( + onClick = onClick, + shape = RoundedCornerShape(OudsTheme.components.chip.border.radius), + color = backgroundColor, + border = BorderStroke( + width = OudsTheme.components.chip.border.width, + color = OudsTheme.components.chip.color.border.outlined.enabled + ) + ) { + Row( + modifier = Modifier + .height(OudsTheme.components.chip.size.height.medium) + .padding(horizontal = OudsTheme.components.chip.space.paddingInline.medium), + horizontalArrangement = Arrangement.spacedBy(OudsTheme.components.chip.space.columnGap.icon), + verticalAlignment = Alignment.CenterVertically + ) { + icon?.let { + Icon( + imageVector = it, + contentDescription = null, + modifier = Modifier.size(OudsTheme.components.chip.size.icon.outlined.medium), + tint = contentColor + ) + } + Text( + text = text, + style = OudsTheme.components.chip.typography.medium.default, + color = contentColor + ) + } + } +} +``` + +### Example 3: Custom Text Input with Component Tokens + +```kotlin +@OptIn(RestrictedOudsApi::class, ExperimentalMaterial3Api::class) +@Composable +fun CustomTextInput( + value: String, + onValueChange: (String) -> Unit, + label: String, + modifier: Modifier = Modifier, + isError: Boolean = false +) { + val borderColor = if (isError) { + OudsTheme.components.textInput.color.border.error + } else { + OudsTheme.components.textInput.color.border.default + } + + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + label = { + Text( + text = label, + style = OudsTheme.components.textInput.typography.label + ) + }, + textStyle = OudsTheme.components.textInput.typography.content, + shape = RoundedCornerShape(OudsTheme.components.textInput.border.radius.default), + colors = TextFieldDefaults.colors( + focusedContainerColor = OudsTheme.components.textInput.color.background.default, + unfocusedContainerColor = OudsTheme.components.textInput.color.background.default, + focusedIndicatorColor = OudsTheme.components.textInput.color.border.focus, + unfocusedIndicatorColor = borderColor, + errorIndicatorColor = OudsTheme.components.textInput.color.border.error, + focusedTextColor = OudsTheme.components.textInput.color.content.default, + unfocusedTextColor = OudsTheme.components.textInput.color.content.default + ), + isError = isError + ) +} +``` + +### Example 4: Custom Alert Banner with Component Tokens + +```kotlin +@OptIn(RestrictedOudsApi::class) +@Composable +fun CustomAlertBanner( + title: String, + message: String, + icon: ImageVector, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(OudsTheme.components.alert.border.radius.default), + color = OudsTheme.components.alert.color.background.info.emphasized, + border = BorderStroke( + width = OudsTheme.components.alert.border.width, + color = OudsTheme.components.alert.color.border.info.emphasized + ) + ) { + Row( + modifier = Modifier + .padding( + horizontal = OudsTheme.components.alert.space.paddingInline, + vertical = OudsTheme.components.alert.space.paddingBlock + ), + horizontalArrangement = Arrangement.spacedBy(OudsTheme.components.alert.space.columnGap) + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(OudsTheme.components.alert.size.icon), + tint = OudsTheme.components.alert.color.content.info.emphasized + ) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(OudsTheme.components.alert.space.rowGap) + ) { + Text( + text = title, + color = OudsTheme.components.alert.color.content.info.emphasized + ) + Text( + text = message, + color = OudsTheme.components.alert.color.content.info.emphasized + ) + } + IconButton(onClick = onDismiss) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close", + tint = OudsTheme.components.alert.color.content.info.emphasized + ) + } + } + } +} +``` + +--- + +## Best Practices + +### 1. Prefer Semantic Tokens When Possible + +Before using component tokens, check if semantic tokens can achieve your goal: + +```kotlin +// ❌ Avoid if unnecessary +@OptIn(RestrictedOudsApi::class) +val spacing = OudsTheme.components.button.space.paddingInline.medium + +// ✅ Prefer semantic tokens +val spacing = OudsTheme.spaces.fixed.medium +``` + +### 2. Use Component Tokens for Component-Specific Values + +Component tokens are ideal when you need values specific to a component's design: + +```kotlin +@OptIn(RestrictedOudsApi::class) +// ✅ Good use case: component-specific height +val chipHeight = OudsTheme.components.chip.size.height.medium + +// ✅ Good use case: component-specific color states +val chipBackground = OudsTheme.components.chip.color.background.outlined.selected +``` + +### 3. Document Your Custom Components + +When creating custom components with component tokens, document your decisions: + +```kotlin +/** + * Custom branded chip component. + * + * Uses component tokens for: + * - Precise height matching OUDS chip specs + * - Brand-specific color states + * - Consistent padding and spacing + * + * @param text Chip label text + * @param selected Whether the chip is selected + * @param onClick Callback when chip is clicked + */ +@OptIn(RestrictedOudsApi::class) +@Composable +fun BrandChip( + text: String, + selected: Boolean, + onClick: () -> Unit +) { + // Implementation +} +``` + +### 4. Keep Component Token Usage Localized + +Limit component token access to component files: + +```kotlin +// ✅ Good: Component token usage in a dedicated component file +// file: CustomChip.kt +@OptIn(RestrictedOudsApi::class) +@Composable +fun CustomChip(...) { + val chipHeight = OudsTheme.components.chip.size.height.medium + // ... +} + +// ❌ Avoid: Spreading component tokens across screen/page files +// file: HomeScreen.kt +@OptIn(RestrictedOudsApi::class) +@Composable +fun HomeScreen() { + val chipHeight = OudsTheme.components.chip.size.height.medium // Prefer CustomChip component + // ... +} +``` + +--- + +## Migration from Semantic to Component Tokens + +If you find yourself needing more control than semantic tokens provide, migrate gradually: + +```kotlin +// Step 1: Start with semantic tokens +@Composable +fun MyButton(text: String, onClick: () -> Unit) { + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = OudsTheme.colorScheme.action.enabled + ) + ) { + Text(text, style = OudsTheme.typography.label.large.strong) + } +} + +// Step 2: Identify specific needs (e.g., precise padding) +@OptIn(RestrictedOudsApi::class) +@Composable +fun MyButton(text: String, onClick: () -> Unit) { + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = OudsTheme.colorScheme.action.enabled + ), + contentPadding = PaddingValues( + horizontal = OudsTheme.components.button.space.paddingInline.medium // Component token + ) + ) { + Text(text, style = OudsTheme.typography.label.large.strong) // Semantic token + } +} +``` + +--- + +## References + +- **Semantic Tokens:** See [`tokens.md`](tokens.md) for standard design tokens +- **Components:** See [`components.md`](components.md) for standard OUDS components +- **Documentation:** https://android.unified-design-system.orange.com/ +- **Repository:** https://github.com/Orange-OpenSource/ouds-android +- **Design System:** https://unified-design-system.orange.com/ diff --git a/skills/using-ouds-android/references/components-index.md b/skills/using-ouds-android/references/components-index.md new file mode 100644 index 0000000000..11cb6e4d79 --- /dev/null +++ b/skills/using-ouds-android/references/components-index.md @@ -0,0 +1,71 @@ +# OUDS Android — Components Reference Index + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents by Category + +### Action Components +[→ See full documentation](action-components.md) + +- [OudsButton](action-components.md#button) — Default and small buttons +- [OudsFloatingActionButton](action-components.md#floatingactionbutton) — Floating action button (FAB) +- [OudsNavigationButton](action-components.md#navigationbutton) — Navigation button with chevron +- [OudsSmallButton](action-components.md#smallbutton) — Small size button variant + +### Alert Components +[→ See full documentation](alert-components.md) + +- [OudsAlertMessage](alert-components.md#alertmessage) — Full-featured alert with actions +- [OudsInlineAlert](alert-components.md#inlinealert) — Compact inline alert + +### Content Components +[→ See full documentation](content-components.md) + +- [OudsBulletList](content-components.md#bulletlist) — Ordered, unordered, and bare lists + +### Control Components +[→ See full documentation](control-components.md) + +- [OudsCheckbox](control-components.md#checkbox) — Standalone checkbox +- [OudsCheckboxItem](control-components.md#checkboxitem) — Checkbox with label and description +- [OudsRadioButton](control-components.md#radiobutton) — Standalone radio button +- [OudsRadioButtonItem](control-components.md#radiobuttonitem) — Radio button with label and description +- [OudsSwitch](control-components.md#switch) — Standalone switch +- [OudsSwitchItem](control-components.md#switchitem) — Toggle switch with label and description + - **Chip** + - [OudsFilterChip](control-components.md#filterchip) — Selectable filter chip + - [OudsSuggestionChip](control-components.md#suggestionchip) — Suggestion and action chip + +### Indicator Components +[→ See full documentation](indicator-components.md) + +- [OudsBadge](indicator-components.md#badge) — Count and status badges +- [OudsCircularProgressIndicator](indicator-components.md#circularprogressindicator) — Circular loading indicator +- [OudsLinearProgressIndicator](indicator-components.md#linearprogressindicator) — Linear loading indicator +- [OudsTag](indicator-components.md#tag) — Status and category tags + +### Input Components +[→ See full documentation](input-components.md) + +- [OudsTextInput](input-components.md#textinput) — Single-line text field +- [OudsTextArea](input-components.md#textarea) — Multi-line text field +- [OudsPasswordInput](input-components.md#passwordinput) — Password field with visibility toggle +- [OudsPinCodeInput](input-components.md#pincodeinput) — PIN code input (4 or 6 digits) + +### Layout Components +[→ See full documentation](layout-components.md) + +- [OudsBottomSheetScaffold](layout-components.md#bottomsheetscaffold) — Standard bottom sheet scaffold +- [OudsColoredBox](layout-components.md#coloredbox) — Colored surface container +- [OudsDivider](layout-components.md#divider) — Horizontal and vertical dividers +- [OudsModalBottomSheet](layout-components.md#modalbottomsheet) — Modal bottom sheet + +### Navigation Components +[→ See full documentation](navigation-components.md) + +- [OudsLink](navigation-components.md#link) — Text link with optional icon/chevron +- [OudsNavigationBar](navigation-components.md#navigationbar) — Bottom navigation bar +- [OudsTopAppBar](navigation-components.md#topappbar) — Top app bar with variants diff --git a/skills/using-ouds-android/references/content-components.md b/skills/using-ouds-android/references/content-components.md new file mode 100644 index 0000000000..d06d444af8 --- /dev/null +++ b/skills/using-ouds-android/references/content-components.md @@ -0,0 +1,39 @@ +# OUDS Android — Content Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsBulletList](#bulletlist) — Ordered, unordered, and bare lists + +--- + +## BulletList + +**Types:** `OudsBulletListType` — `Unordered` (default, `brandColor: Boolean`), `Ordered`, `Bare` + +```kotlin +// Unordered (brand color) +OudsBulletList { + item(label = stringResource(R.string.item_1)) + item(label = stringResource(R.string.item_2), subListType = OudsBulletListType.Unordered(brandColor = false)) { + item(label = stringResource(R.string.sub_item_1)) + } +} + +// Ordered +OudsBulletList(type = OudsBulletListType.Ordered) { + item(label = stringResource(R.string.step_1)) + item(label = stringResource(R.string.step_2)) { + item(label = stringResource(R.string.sub_step_1)) + } +} + +// Bare (no bullet) +OudsBulletList(type = OudsBulletListType.Bare) { + item(label = stringResource(R.string.item_1)) +} +``` diff --git a/skills/using-ouds-android/references/control-components.md b/skills/using-ouds-android/references/control-components.md new file mode 100644 index 0000000000..8dbe1308c3 --- /dev/null +++ b/skills/using-ouds-android/references/control-components.md @@ -0,0 +1,311 @@ +# OUDS Android — Control Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsCheckbox](#checkbox) — Standalone checkbox +- [OudsCheckboxItem](#checkboxitem) — Checkbox with label and description +- [OudsRadioButton](#radiobutton) — Standalone radio button +- [OudsRadioButtonItem](#radiobuttonitem) — Radio button with label and description +- [OudsSwitch](#switch) — Standalone switch +- [OudsSwitchItem](#switchitem) — Toggle switch with label and description + - **Chip** + - [OudsFilterChip](#filterchip) — Selectable filter chip + - [OudsSuggestionChip](#suggestionchip) — Suggestion and action chip + +--- + +## CheckboxItem + +Signature: `OudsCheckboxItem(checked, label, onCheckedChange, modifier, description?, icon?, divider?, enabled?, error?)` +Tri-state variant: `OudsTriStateCheckboxItem(state: ToggleableState, label, onClick, …)` + +```kotlin +// Basic +var checked by remember { mutableStateOf(false) } +OudsCheckboxItem( + checked = checked, + label = stringResource(R.string.terms), + onCheckedChange = { checked = it } +) + +// With description and icon +OudsCheckboxItem( + checked = checked, + label = stringResource(R.string.terms), + description = stringResource(R.string.terms_desc), + icon = OudsControlItemIcon(imageVector = Icons.Filled.FavoriteBorder), + onCheckedChange = { checked = it } +) + +// With untinted icon +OudsCheckboxItem( + checked = checked, + label = stringResource(R.string.terms), + icon = OudsControlItemIcon(painter = myPainter, tinted = false), + onCheckedChange = { checked = it } +) + +// With error +OudsCheckboxItem( + checked = checked, + label = stringResource(R.string.terms), + onCheckedChange = { checked = it }, + error = OudsError(message = stringResource(R.string.error_required)) +) + +// Tri-state +var state by remember { mutableStateOf(ToggleableState.Off) } +OudsTriStateCheckboxItem( + state = state, + label = stringResource(R.string.select_all), + onClick = { + state = when (state) { + ToggleableState.On -> ToggleableState.Off + ToggleableState.Off -> ToggleableState.Indeterminate + ToggleableState.Indeterminate -> ToggleableState.On + } + } +) +``` + +--- + +## RadioButtonItem + +Signature: `OudsRadioButtonItem(selected, label, onClick, modifier, description?, icon?, divider?, enabled?, error?)` +**Always** wrap a group of radio items in `Modifier.selectableGroup()`. + +```kotlin +val options = listOf( + stringResource(R.string.option_a), + stringResource(R.string.option_b) +) +var selected by rememberSaveable { mutableStateOf(options.first()) } + +Column(modifier = Modifier.selectableGroup()) { + options.forEach { option -> + OudsRadioButtonItem( + selected = option == selected, + label = option, + onClick = { selected = option }, + divider = true + ) + } +} + +// With icon +OudsRadioButtonItem( + selected = selected == option, + label = option, + icon = OudsControlItemIcon(imageVector = Icons.Filled.FavoriteBorder), + onClick = { selected = option } +) + +// With error (typically on the last item) +OudsRadioButtonItem( + selected = selected == option, + label = option, + onClick = { selected = option }, + error = OudsError(message = stringResource(R.string.selection_required)) +) +``` + +--- + +## SwitchItem + +Signature: `OudsSwitchItem(checked, label, onCheckedChange, modifier, description?, icon?, divider?, enabled?, error?)` + +```kotlin +var checked by remember { mutableStateOf(true) } + +OudsSwitchItem( + checked = checked, + label = stringResource(R.string.notifications), + description = stringResource(R.string.notifications_desc), + icon = OudsControlItemIcon(imageVector = Icons.Filled.FavoriteBorder), + onCheckedChange = { checked = it } +) + +// With untinted icon +OudsSwitchItem( + checked = checked, + label = stringResource(R.string.notifications), + icon = OudsControlItemIcon(painter = myPainter, tinted = false), + onCheckedChange = { checked = it } +) + +// With error +OudsSwitchItem( + checked = checked, + label = stringResource(R.string.notifications), + onCheckedChange = { checked = it }, + error = OudsError(message = stringResource(R.string.notifications_required)) +) +``` + +--- + +## Checkbox + +**Standalone checkbox** without label — use when checkbox is nested within another component with an alternative label. +**See also:** [OudsCheckboxItem](#checkboxitem) for checkbox with label and description. + +```kotlin +var checked by remember { mutableStateOf(false) } + +// Basic checkbox +OudsCheckbox( + checked = checked, + onCheckedChange = { checked = it } +) + +// Disabled +OudsCheckbox( + checked = checked, + onCheckedChange = { checked = it }, + enabled = false +) + +// Tri-state checkbox +var state by remember { mutableStateOf(ToggleableState.Off) } +OudsTriStateCheckbox( + state = state, + onClick = { + state = when (state) { + ToggleableState.On -> ToggleableState.Off + ToggleableState.Off -> ToggleableState.Indeterminate + ToggleableState.Indeterminate -> ToggleableState.On + } + } +) +``` + +--- + +## RadioButton + +**Standalone radio button** without label — use when radio button is nested within another component with an alternative label. +**See also:** [OudsRadioButtonItem](#radiobuttonitem) for radio button with label and description. +**Always** wrap a group of radio buttons in `Modifier.selectableGroup()`. + +```kotlin +val options = listOf("Option A", "Option B", "Option C") +var selected by remember { mutableStateOf(options[0]) } + +Column(modifier = Modifier.selectableGroup()) { + options.forEach { option -> + OudsRadioButton( + selected = option == selected, + onClick = { selected = option } + ) + } +} + +// Disabled +OudsRadioButton( + selected = true, + onClick = null, + enabled = false +) +``` + +--- + +## Switch + +**Standalone switch** without label — use when switch is nested within another component with an alternative label. +**See also:** [OudsSwitchItem](#switchitem) for switch with label and description. + +```kotlin +var checked by remember { mutableStateOf(true) } + +// Basic switch +OudsSwitch( + checked = checked, + onCheckedChange = { checked = it } +) + +// Disabled +OudsSwitch( + checked = checked, + onCheckedChange = { checked = it }, + enabled = false +) + +// With icon (when checked) +OudsSwitch( + checked = checked, + onCheckedChange = { checked = it }, + thumbContent = { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + } +) +``` + +--- + +## FilterChip + +**Selectable chip** used for filtering content. + +```kotlin +// Text only +OudsFilterChip(text = stringResource(R.string.label), onClick = { }) + +// With icon +OudsFilterChip( + icon = OudsChipIcon(imageVector = Icons.Filled.FavoriteBorder), + text = stringResource(R.string.label), + onClick = { } +) + +// Icon only +OudsFilterChip( + icon = OudsChipIcon(imageVector = Icons.Filled.FavoriteBorder), + contentDescription = stringResource(R.string.label_desc), + onClick = { } +) + +// Selected state +var selected by remember { mutableStateOf(false) } +OudsFilterChip( + text = stringResource(R.string.label), + selected = selected, + onClick = { selected = !selected } +) +``` + +--- + +## SuggestionChip + +**Action chip** used for suggestions and quick actions. + +```kotlin +// Text only +OudsSuggestionChip(text = stringResource(R.string.label), onClick = { }) + +// With icon +OudsSuggestionChip( + icon = OudsChipIcon(imageVector = Icons.Filled.FavoriteBorder), + text = stringResource(R.string.label), + onClick = { } +) + +// Icon only +OudsSuggestionChip( + icon = OudsChipIcon(imageVector = Icons.Filled.Add), + contentDescription = stringResource(R.string.add_desc), + onClick = { } +) +``` diff --git a/skills/using-ouds-android/references/indicator-components.md b/skills/using-ouds-android/references/indicator-components.md new file mode 100644 index 0000000000..0acd72aff3 --- /dev/null +++ b/skills/using-ouds-android/references/indicator-components.md @@ -0,0 +1,200 @@ +# OUDS Android — Indicator Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsBadge](#badge) — Count and status badges +- [OudsCircularProgressIndicator](#circularprogressindicator) — Circular loading indicator +- [OudsLinearProgressIndicator](#linearprogressindicator) — Linear loading indicator +- [OudsTag](#tag) — Status and category tags + +--- + +## Tag + +**Statuses:** `OudsTagStatus` — `Neutral`, `Accent`, `Positive`, `Warning`, `Negative`, `Info` +**Assets:** `OudsTagAsset.Bullet` · `OudsTagAsset.Icon(…)` · `OudsTagAsset.Icon.Default` (functional icon per status) +**Appearances:** `OudsTagAppearance` — `Emphasized` (default), `Muted` +**Sizes:** `OudsTagSize` — `Default`, `Small` + +```kotlin +// Text only +OudsTag(label = stringResource(R.string.label)) + +// With bullet +OudsTag( + label = stringResource(R.string.label), + status = OudsTagStatus.Positive(asset = OudsTagAsset.Bullet) +) + +// With default functional icon (icon per status) +OudsTag( + label = stringResource(R.string.label), + status = OudsTagStatus.Positive(asset = OudsTagAsset.Icon.Default) +) + +// With custom icon (Neutral or Accent only) +OudsTag( + label = stringResource(R.string.label), + status = OudsTagStatus.Neutral(asset = OudsTagAsset.Icon(imageVector = Icons.Filled.FavoriteBorder)) +) + +// With untinted icon +OudsTag( + label = stringResource(R.string.label), + status = OudsTagStatus.Neutral(asset = OudsTagAsset.Icon(painter = myPainter, tinted = false)) +) + +// Small size +OudsTag(label = stringResource(R.string.label), size = OudsTagSize.Small) + +// With loader (indeterminate) +OudsTag(label = stringResource(R.string.label), loader = OudsTagLoader(progress = null)) +``` + +--- + +## Badge + +**Statuses (plain/count):** `OudsBadgeStatus` — `Neutral`, `Accent`, `Positive`, `Warning`, `Negative`, `Info` +**Statuses (icon):** `OudsIconBadgeStatus` — `Neutral(icon?)`, `Accent(icon?)`, `Positive`, `Warning`, `Negative`, `Info` +**Sizes:** `OudsBadgeSize` — `ExtraSmall`, `Small`, `Medium`, `Large` +**Note:** Always provide a `contentDescription` via `Modifier.semantics { contentDescription = "…" }`. + +```kotlin +// Standard dot badge +OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.info_desc) }, + status = OudsBadgeStatus.Info, + size = OudsBadgeSize.Small +) + +// Badge with count +val count = 10 +OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.unread_count, count) }, + status = OudsBadgeStatus.Accent, + count = count +) + +// Badge with default functional icon +OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.info_desc) }, + status = OudsIconBadgeStatus.Info, + size = OudsBadgeSize.Large +) + +// Badge with custom icon +OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.favorite_desc) }, + status = OudsIconBadgeStatus.Accent(OudsBadgeIcon(imageVector = Icons.Filled.FavoriteBorder)), + size = OudsBadgeSize.Large +) + +// Badge with untinted custom icon +OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.brand_desc) }, + status = OudsIconBadgeStatus.Neutral(OudsBadgeIcon(painter = myPainter, tinted = false)), + size = OudsBadgeSize.Large +) + +// Typical use: badged navigation item +BadgedBox( + badge = { + OudsBadge( + modifier = Modifier.semantics { contentDescription = stringResource(R.string.unread_count, 8) }, + count = 8, + status = OudsBadgeStatus.Accent + ) + } +) { + Icon(imageVector = Icons.Filled.Notifications, contentDescription = null) +} +``` + +--- + +## CircularProgressIndicator + +**Statuses:** `OudsProgressIndicatorStatus` — `Accent` (default), `Neutral` +**Variants:** Determinate (with progress value) · Indeterminate (loading animation) +**Track:** Optional background track for better visibility + +```kotlin +// Indeterminate (loading) +OudsCircularProgressIndicator() + +// Determinate (with progress) +OudsCircularProgressIndicator(progress = { 0.75f }) + +// With status +OudsCircularProgressIndicator( + progress = { 0.5f }, + status = OudsProgressIndicatorStatus.Neutral +) + +// Without track (minimal) +OudsCircularProgressIndicator( + progress = { 0.75f }, + track = false +) + +// Custom size +OudsCircularProgressIndicator( + modifier = Modifier.size(64.dp), + progress = { 0.75f } +) +``` + +--- + +## LinearProgressIndicator + +**Statuses:** `OudsProgressIndicatorStatus` — `Accent` (default), `Neutral` +**Variants:** Determinate (with progress value) · Indeterminate (loading animation) +**Track:** Optional background track for better visibility +**Stop Indicator:** Optional end marker for accessibility (required if contrast < 3:1) + +```kotlin +// Indeterminate (loading) +OudsLinearProgressIndicator( + helperText = stringResource(R.string.loading) +) + +// Determinate (with progress) +OudsLinearProgressIndicator( + progress = { 0.75f }, + helperText = stringResource(R.string.loading_percent, 75) +) + +// With status +OudsLinearProgressIndicator( + progress = { 0.5f }, + status = OudsProgressIndicatorStatus.Neutral, + helperText = stringResource(R.string.uploading) +) + +// Without track (minimal) +OudsLinearProgressIndicator( + progress = { 0.75f }, + track = false +) + +// With stop indicator (for accessibility) +OudsLinearProgressIndicator( + progress = { 0.75f }, + stopIndicator = true, + helperText = stringResource(R.string.processing) +) + +// Full width with helper text +OudsLinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = { 0.75f }, + helperText = "Uploading file: document.pdf" +) +``` diff --git a/skills/using-ouds-android/references/input-components.md b/skills/using-ouds-android/references/input-components.md new file mode 100644 index 0000000000..6429319e6e --- /dev/null +++ b/skills/using-ouds-android/references/input-components.md @@ -0,0 +1,153 @@ +# OUDS Android — Input Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsTextInput](#textinput) — Single-line text field +- [OudsTextArea](#textarea) — Multi-line text field +- [OudsPasswordInput](#passwordinput) — Password field with visibility toggle +- [OudsPinCodeInput](#pincodeinput) — PIN code input (4 or 6 digits) + +--- + +## TextInput + +Two API variants for OudsTextInput: **state-based** (`textFieldState`) and **value-based** (`value` + `onValueChange`). +Prefer the state-based API for new code. + +```kotlin +// State-based — basic +OudsTextInput( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.label) +) + +// State-based — full featured +OudsTextInput( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.label), + placeholder = stringResource(R.string.placeholder), + leadingIcon = OudsTextInputLeadingIcon(imageVector = Icons.Filled.Search, contentDescription = ""), + prefix = stringResource(R.string.prefix), + suffix = stringResource(R.string.suffix), + helperText = stringResource(R.string.helper), + helperLink = OudsTextInputHelperLink(text = stringResource(R.string.more), onClick = { }) +) + +// With trailing action button +OudsTextInput( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.date), + trailingIconButton = OudsTextInputTrailingIconButton( + imageVector = Icons.Filled.DateRange, + contentDescription = stringResource(R.string.open_calendar), + onClick = { } + ), + outlined = true +) + +// With error +OudsTextInput( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.label), + error = OudsError(message = stringResource(R.string.field_required)) +) + +// Value-based +var value by remember { mutableStateOf("") } +OudsTextInput( + value = value, + onValueChange = { value = it }, + label = stringResource(R.string.label) +) + +// Untinted leading icon +OudsTextInput( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.label), + leadingIcon = OudsTextInputLeadingIcon(painter = myPainter, contentDescription = "", tinted = false) +) +``` + +--- + +## TextArea + +Same two API variants as `OudsTextInput` (state-based / value-based). + +```kotlin +// State-based +OudsTextArea( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.feedback), + placeholder = stringResource(R.string.feedback_placeholder), + helperText = stringResource(R.string.feedback_helper) +) + +// With error +OudsTextArea( + textFieldState = rememberTextFieldState(), + label = stringResource(R.string.comment), + outlined = true, + error = OudsError(message = stringResource(R.string.min_chars_error)) +) + +// Value-based +var value by remember { mutableStateOf("") } +OudsTextArea( + value = value, + onValueChange = { value = it }, + label = stringResource(R.string.description) +) +``` + +--- + +## PasswordInput + +Uses `OudsPasswordInputState` to manage visibility toggle. Create the state with `rememberOudsPasswordInputState()`. + +```kotlin +OudsPasswordInput( + state = rememberOudsPasswordInputState(), + label = stringResource(R.string.password), + lockIcon = true, + helperText = stringResource(R.string.password_helper) +) + +// With error +OudsPasswordInput( + state = rememberOudsPasswordInputState(), + label = stringResource(R.string.password), + error = OudsError(message = stringResource(R.string.password_error)) +) +``` + +--- + +## PinCodeInput + +**Lengths:** `OudsPinCodeInputLength` — `Four`, `Six` + +```kotlin +var value by remember { mutableStateOf("") } + +OudsPinCodeInput( + value = value, + onValueChange = { value = it }, + length = OudsPinCodeInputLength.Four, + helperText = stringResource(R.string.pin_helper) +) + +// With error +OudsPinCodeInput( + value = value, + onValueChange = { value = it }, + length = OudsPinCodeInputLength.Four, + error = OudsError(message = stringResource(R.string.pin_error)) +) +``` diff --git a/skills/using-ouds-android/references/layout-components.md b/skills/using-ouds-android/references/layout-components.md new file mode 100644 index 0000000000..b70fb144da --- /dev/null +++ b/skills/using-ouds-android/references/layout-components.md @@ -0,0 +1,164 @@ +# OUDS Android — Layout Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsBottomSheetScaffold](#bottomsheetscaffold) — Standard bottom sheet scaffold +- [OudsColoredBox](#coloredbox) — Colored surface container +- [OudsDivider](#divider) — Horizontal and vertical dividers +- [OudsModalBottomSheet](#modalbottomsheet) — Modal bottom sheet + +--- + +## ColoredBox + +Creates a colored surface where child OUDS components automatically switch to their monochrome variant. + +**Colors:** 24 values organized by category: +- **Background** (5): `BackgroundInverseHigh`, `BackgroundInverseLow`, `BackgroundPrimary`, `BackgroundSecondary`, `BackgroundTertiary` +- **Brand** (3): `BrandPrimary`, `BrandSecondary`, `BrandTertiary` +- **Overlay** (3): `OverlayDropdown`, `OverlayModal`, `OverlayTooltip` +- **Status** (8): `StatusAccentEmphasized`, `StatusAccentMuted`, `StatusInfoEmphasized`, `StatusInfoMuted`, `StatusNegativeEmphasized`, `StatusNegativeMuted`, `StatusPositiveEmphasized`, `StatusPositiveMuted`, `StatusWarningEmphasized`, `StatusWarningMuted` +- **Surface** (5): `SurfaceInverseHigh`, `SurfaceInverseLow`, `SurfacePrimary`, `SurfaceSecondary`, `SurfaceTertiary` + +> **Note:** Not all colors are supported by all themes. Check `color.isSupported` before using a color in production code. + +```kotlin +// Basic usage +OudsColoredBox(color = OudsColoredBoxColor.StatusInfoEmphasized) { + // Child OUDS components adopt monochrome colors automatically + OudsButton(label = stringResource(R.string.action), onClick = { }) + Text( + text = stringResource(R.string.description), + color = OudsTheme.colorScheme.content.default + ) +} + +// Check if color is supported by current theme +val color = OudsColoredBoxColor.BrandPrimary +if (color.isSupported) { + OudsColoredBox(color = color) { + // Content + } +} + +// Different color categories +OudsColoredBox(color = OudsColoredBoxColor.BrandPrimary) { /* Brand colors */ } +OudsColoredBox(color = OudsColoredBoxColor.StatusPositiveEmphasized) { /* Status colors */ } +OudsColoredBox(color = OudsColoredBoxColor.SurfacePrimary) { /* Surface colors */ } +``` + +--- + +## Divider + +```kotlin +// Horizontal +OudsHorizontalDivider(modifier = Modifier.fillMaxWidth()) + +// Vertical +OudsVerticalDivider(modifier = Modifier.height(50.dp)) +``` + +--- + +## BottomSheetScaffold + +**Standard bottom sheet** that co-exists with main screen content, allowing simultaneous interaction. +**See also:** [OudsModalBottomSheet](#modalbottomsheet) for modal behavior that blocks main content. + +```kotlin +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MyScreen() { + val scaffoldState = rememberBottomSheetScaffoldState() + + OudsBottomSheetScaffold( + sheetContent = { + Column(modifier = Modifier.padding(16.dp)) { + Text(stringResource(R.string.sheet_title)) + Text(stringResource(R.string.sheet_content)) + } + }, + sheetPeekHeight = 128.dp, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + Text(stringResource(R.string.main_content)) + } + } + ) +} + +// Without drag handle +OudsBottomSheetScaffold( + sheetContent = { /* content */ }, + sheetDragHandle = false, + content = { /* main content */ } +) + +// With custom peek height +OudsBottomSheetScaffold( + sheetContent = { /* content */ }, + sheetPeekHeight = 200.dp, + content = { /* main content */ } +) +``` + +--- + +## ModalBottomSheet + +**Modal bottom sheet** that appears in front of app content and blocks interaction until dismissed. +**See also:** [OudsBottomSheetScaffold](#bottomsheetscaffold) for non-modal variant. + +```kotlin +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MyScreen() { + var showBottomSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState() + + Button(onClick = { showBottomSheet = true }) { + Text(stringResource(R.string.show_sheet)) + } + + if (showBottomSheet) { + OudsModalBottomSheet( + onDismissRequest = { showBottomSheet = false }, + sheetState = sheetState + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text(stringResource(R.string.sheet_title)) + Text(stringResource(R.string.sheet_content)) + Button(onClick = { showBottomSheet = false }) { + Text(stringResource(R.string.close)) + } + } + } + } +} + +// Without drag handle +OudsModalBottomSheet( + onDismissRequest = { /* dismiss */ }, + dragHandle = false +) { + // Content +} + +// With gestures disabled +OudsModalBottomSheet( + onDismissRequest = { /* dismiss */ }, + sheetGesturesEnabled = false +) { + // Content +} +``` diff --git a/skills/using-ouds-android/references/navigation-components.md b/skills/using-ouds-android/references/navigation-components.md new file mode 100644 index 0000000000..b660dd7854 --- /dev/null +++ b/skills/using-ouds-android/references/navigation-components.md @@ -0,0 +1,105 @@ +# OUDS Android — Navigation Components + +All components are in the `com.orange.ouds.core.component` package. +All user-visible strings must use `stringResource(R.string.*)` — never hardcode. + +> **Naming Convention:** All OUDS components follow the `Ouds*` prefix naming pattern (e.g., `OudsButton`, `OudsTag`, `OudsTextInput`). + +## Table of Contents + +- [OudsLink](#link) — Text link with optional icon/chevron +- [OudsNavigationBar](#navigationbar) — Bottom navigation bar +- [OudsTopAppBar](#topappbar) — Top app bar with variants + +--- + +## Link + +**Chevrons:** `OudsLinkChevron` — `Next`, `Back` + +```kotlin +// Text only +OudsLink( + label = stringResource(R.string.link_label), + onClick = { } +) + +// With icon +OudsLink( + label = stringResource(R.string.link_label), + icon = OudsLinkIcon(imageVector = Icons.Filled.FavoriteBorder), + onClick = { } +) + +// With chevron +OudsLink( + label = stringResource(R.string.link_label), + chevron = OudsLinkChevron.Next, + onClick = { } +) + +// With untinted icon +OudsLink( + label = stringResource(R.string.link_label), + icon = OudsLinkIcon(painter = myPainter, tinted = false), + onClick = { } +) +``` + +--- + +## NavigationBar + +```kotlin +var selectedIndex by rememberSaveable { mutableIntStateOf(0) } + +OudsNavigationBar( + items = listOf( + OudsNavigationBarItem( + selected = selectedIndex == 0, + onClick = { selectedIndex = 0 }, + icon = OudsNavigationBarItemIcon(imageVector = Icons.Default.Home), + label = stringResource(R.string.home) + ), + OudsNavigationBarItem( + selected = selectedIndex == 1, + onClick = { selectedIndex = 1 }, + icon = OudsNavigationBarItemIcon(imageVector = Icons.Default.Email), + label = stringResource(R.string.messages), + badge = OudsNavigationBarItemBadge( + contentDescription = stringResource(R.string.unread_count, 5), + count = 5 + ) + ) + ) +) +``` + +--- + +## TopAppBar + +Four variants: `OudsTopAppBar`, `OudsCenterAlignedTopAppBar`, `OudsMediumTopAppBar`, `OudsLargeTopAppBar`. +**Navigation icons:** `OudsTopAppBarNavigationIcon.Back { }` · `OudsTopAppBarNavigationIcon.Menu { }` +**Actions:** `OudsTopAppBarAction.Icon(…)` · `OudsTopAppBarAction.Avatar(…)` + +```kotlin +@OptIn(ExperimentalMaterial3Api::class) +OudsTopAppBar( + title = stringResource(R.string.screen_title), + navigationIcon = OudsTopAppBarNavigationIcon.Back { /* navigate back */ }, + actions = listOf( + OudsTopAppBarAction.Icon( + imageVector = Icons.Outlined.Settings, + contentDescription = stringResource(R.string.settings_desc) + ) { /* open settings */ } + ) +) + +// Large top app bar +@OptIn(ExperimentalMaterial3Api::class) +OudsLargeTopAppBar( + title = stringResource(R.string.screen_title), + navigationIcon = OudsTopAppBarNavigationIcon.Back { } +) +``` diff --git a/skills/using-ouds-android/references/tokens.md b/skills/using-ouds-android/references/tokens.md new file mode 100644 index 0000000000..44db57c506 --- /dev/null +++ b/skills/using-ouds-android/references/tokens.md @@ -0,0 +1,847 @@ +# OUDS Android — Semantic Tokens Reference + +All OUDS semantic tokens are accessed via `OudsTheme` in composable functions. +**Never hardcode values** — always use token references for consistency and theme support. + +> **Golden Rule:** Always use `OudsTheme.*` tokens instead of hardcoded values. +> ```kotlin +> // ✅ CORRECT +> Text( +> text = "Title", +> color = OudsTheme.colorScheme.content.default, +> style = OudsTheme.typography.heading.large +> ) +> Box( +> modifier = Modifier +> .padding(OudsTheme.spaces.fixed.medium) +> .border( +> width = OudsTheme.borders.width.default, +> color = OudsTheme.colorScheme.border.default +> ) +> ) +> +> // ❌ INCORRECT +> Text(text = "Title", color = Color.Black, fontSize = 16.sp) +> Box(modifier = Modifier.padding(16.dp)) +> ``` + +## Table of Contents + +- [Color Scheme](#color-scheme-oudsthemecolorscheme) +- [Typography](#typography-oudsthemetypography) +- [Spacing](#spacing-oudsthemespaces) +- [Sizes](#sizes-oudsthemesizes) +- [Borders](#borders-oudsthemeborders) +- [Elevations](#elevations-oudsthemeelevations) +- [Opacities](#opacities-oudsthemeopacities) +- [Effects](#effects-oudsthemeeffects) +- [Grids](#grids-oudsthemegrids) +- [Usage Examples](#usage-examples) + +> **Advanced Usage:** For component-level tokens (`@RestrictedOudsApi`), see [`component-tokens.md`](component-tokens.md). + +--- + +## Color Scheme (`OudsTheme.colorScheme`) + +**Design guidelines:** [Color tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-color) + +> Color tokens are **auto-adaptive**: they automatically adjust between Light and Dark modes. + +### Actions + +Interactive element colors for buttons, links, and actionable items. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.action.enabled` | Primary action color (enabled button, active link) | +| `OudsTheme.colorScheme.action.disabled` | Disabled action state | +| `OudsTheme.colorScheme.action.hover` | Hover state | +| `OudsTheme.colorScheme.action.pressed` | Pressed/tap state | +| `OudsTheme.colorScheme.action.focus` | Focus state | +| `OudsTheme.colorScheme.action.loading` | Loading state | +| `OudsTheme.colorScheme.action.highlighted` | Highlighted state | +| `OudsTheme.colorScheme.action.selected` | Selected state | +| `OudsTheme.colorScheme.action.visited` | Visited link state | +| `OudsTheme.colorScheme.action.readOnlyPrimary` | Read-only primary | +| `OudsTheme.colorScheme.action.readOnlySecondary` | Read-only secondary | +| `OudsTheme.colorScheme.action.negative.enabled` | Destructive action enabled | +| `OudsTheme.colorScheme.action.negative.hover` | Destructive action hover | +| `OudsTheme.colorScheme.action.negative.pressed` | Destructive action pressed | +| `OudsTheme.colorScheme.action.negative.loading` | Destructive action loading | +| `OudsTheme.colorScheme.action.negative.focus` | Destructive action focus | +| `OudsTheme.colorScheme.action.support.enabled` | Support/secondary action enabled | +| `OudsTheme.colorScheme.action.support.disabled` | Support/secondary action disabled | +| `OudsTheme.colorScheme.action.support.hover` | Support action hover | +| `OudsTheme.colorScheme.action.support.pressed` | Support action pressed | +| `OudsTheme.colorScheme.action.support.focus` | Support action focus | +| `OudsTheme.colorScheme.action.support.loading` | Support action loading | + +```kotlin +// Action colors in use +Button( + onClick = { }, + colors = ButtonDefaults.buttonColors( + containerColor = OudsTheme.colorScheme.action.enabled, + contentColor = OudsTheme.colorScheme.content.onAction.enabled, + disabledContainerColor = OudsTheme.colorScheme.action.disabled + ) +) { + Text("Action") +} +``` + +### Always + +Colors that remain constant regardless of Light/Dark mode. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.always.black` | Absolute black (mode-independent) | +| `OudsTheme.colorScheme.always.white` | Absolute white (mode-independent) | +| `OudsTheme.colorScheme.always.onBlack` | Content on absolute black | +| `OudsTheme.colorScheme.always.onWhite` | Content on absolute white | + +```kotlin +// Always colors for mode-independent elements +Surface(color = OudsTheme.colorScheme.always.black) { + Text( + text = "Always visible", + color = OudsTheme.colorScheme.always.onBlack + ) +} +``` + +### Background + +Page and screen background colors. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.background.primary` | Primary page background | +| `OudsTheme.colorScheme.background.secondary` | Secondary background (sections) | +| `OudsTheme.colorScheme.background.tertiary` | Tertiary background | +| `OudsTheme.colorScheme.background.inverseHigh` | High contrast inverse background | +| `OudsTheme.colorScheme.background.inverseLow` | Low contrast inverse background | + +### Border + +Border and divider colors. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.border.default` | Standard border | +| `OudsTheme.colorScheme.border.muted` | Subtle border | +| `OudsTheme.colorScheme.border.emphasized` | Strong border | +| `OudsTheme.colorScheme.border.minimal` | Minimal border | +| `OudsTheme.colorScheme.border.focus` | Focus ring (outer) | +| `OudsTheme.colorScheme.border.focusInset` | Focus ring (inner/inset) | +| `OudsTheme.colorScheme.border.brandPrimary` | Brand-colored border | +| `OudsTheme.colorScheme.border.brandSecondary` | Brand secondary border | +| `OudsTheme.colorScheme.border.brandTertiary` | Brand tertiary border | +| `OudsTheme.colorScheme.border.onBrand.primary` | Border on brand primary background | +| `OudsTheme.colorScheme.border.onBrand.secondary` | Border on brand secondary background | +| `OudsTheme.colorScheme.border.onBrand.tertiary` | Border on brand tertiary background | +| `OudsTheme.colorScheme.border.status.accent` | Accent status border | +| `OudsTheme.colorScheme.border.status.negative` | Error/negative border | +| `OudsTheme.colorScheme.border.status.positive` | Success/positive border | +| `OudsTheme.colorScheme.border.status.warning` | Warning border | +| `OudsTheme.colorScheme.border.status.info` | Info border | + +```kotlin +// Border colors +Box( + modifier = Modifier.border( + width = OudsTheme.borders.width.default, + color = OudsTheme.colorScheme.border.default, + shape = RoundedCornerShape(OudsTheme.borders.radius.medium) + ) +) +``` + +### Content + +Text, icon, and foreground element colors. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.content.default` | Primary text/icon color | +| `OudsTheme.colorScheme.content.muted` | Secondary text, hints, captions | +| `OudsTheme.colorScheme.content.disabled` | Disabled text/icon | +| `OudsTheme.colorScheme.content.inverse` | Text on inverse background | +| `OudsTheme.colorScheme.content.brandPrimary` | Brand primary colored text | +| `OudsTheme.colorScheme.content.brandSecondary` | Brand secondary colored text | +| `OudsTheme.colorScheme.content.brandTertiary` | Brand tertiary colored text | +| `OudsTheme.colorScheme.content.onAction.enabled` | Content on enabled action background | +| `OudsTheme.colorScheme.content.onAction.disabled` | Content on disabled action background | +| `OudsTheme.colorScheme.content.onAction.focus` | Content on focused action | +| `OudsTheme.colorScheme.content.onAction.highlighted` | Content on highlighted action | +| `OudsTheme.colorScheme.content.onAction.hover` | Content on hovered action | +| `OudsTheme.colorScheme.content.onAction.loading` | Content on loading action | +| `OudsTheme.colorScheme.content.onAction.pressed` | Content on pressed action | +| `OudsTheme.colorScheme.content.onAction.selected` | Content on selected action | +| `OudsTheme.colorScheme.content.onBrand.primary` | Text on brand primary background | +| `OudsTheme.colorScheme.content.onBrand.secondary` | Text on brand secondary background | +| `OudsTheme.colorScheme.content.onBrand.tertiary` | Text on brand tertiary background | +| `OudsTheme.colorScheme.content.onStatus.accent.emphasized` | Text on accent emphasized background | +| `OudsTheme.colorScheme.content.onStatus.accent.muted` | Text on accent muted background | +| `OudsTheme.colorScheme.content.onStatus.info.emphasized` | Text on info emphasized background | +| `OudsTheme.colorScheme.content.onStatus.info.muted` | Text on info muted background | +| `OudsTheme.colorScheme.content.onStatus.negative.emphasized` | Text on error emphasized background | +| `OudsTheme.colorScheme.content.onStatus.negative.muted` | Text on error muted background | +| `OudsTheme.colorScheme.content.onStatus.positive.emphasized` | Text on success emphasized background | +| `OudsTheme.colorScheme.content.onStatus.positive.muted` | Text on success muted background | +| `OudsTheme.colorScheme.content.onStatus.warning.emphasized` | Text on warning emphasized background | +| `OudsTheme.colorScheme.content.onStatus.warning.muted` | Text on warning muted background | +| `OudsTheme.colorScheme.content.status.accent` | Accent colored text | +| `OudsTheme.colorScheme.content.status.info` | Info colored text | +| `OudsTheme.colorScheme.content.status.negative` | Error colored text | +| `OudsTheme.colorScheme.content.status.positive` | Success colored text | +| `OudsTheme.colorScheme.content.status.warning` | Warning colored text | + +```kotlin +// Content colors for text hierarchy +Column { + Text( + text = stringResource(R.string.title), + color = OudsTheme.colorScheme.content.default, + style = OudsTheme.typography.heading.large + ) + Text( + text = stringResource(R.string.subtitle), + color = OudsTheme.colorScheme.content.muted, + style = OudsTheme.typography.body.medium.default + ) +} +``` + +### Opacity + +Transparency colors. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.opacity.transparent` | Fully transparent | +| `OudsTheme.colorScheme.opacity.lowest` | Very high transparency | +| `OudsTheme.colorScheme.opacity.lower` | High transparency | + +### Overlay + +Overlay, modal, and tooltip background colors. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.overlay.backdrop` | Modal backdrop/scrim | +| `OudsTheme.colorScheme.overlay.modalSheet` | Bottom sheet background | +| `OudsTheme.colorScheme.overlay.dropdown` | Dropdown background | +| `OudsTheme.colorScheme.overlay.tooltip` | Tooltip background | +| `OudsTheme.colorScheme.overlay.drag` | Drag overlay | + +### Surface + +Surface colors for cards, dialogs, and elevated components. + +| Token | Usage | +|-------|-------| +| `OudsTheme.colorScheme.surface.primary` | Primary surface (cards, modals) | +| `OudsTheme.colorScheme.surface.secondary` | Secondary surface | +| `OudsTheme.colorScheme.surface.tertiary` | Tertiary surface | +| `OudsTheme.colorScheme.surface.inverseHigh` | High contrast inverse surface | +| `OudsTheme.colorScheme.surface.inverseLow` | Low contrast inverse surface | +| `OudsTheme.colorScheme.surface.brand.primary` | Brand primary surface | +| `OudsTheme.colorScheme.surface.brand.secondary` | Brand secondary surface | +| `OudsTheme.colorScheme.surface.brand.tertiary` | Brand tertiary surface | +| `OudsTheme.colorScheme.surface.status.accent.emphasized` | Accent emphasized surface | +| `OudsTheme.colorScheme.surface.status.accent.muted` | Accent muted surface | +| `OudsTheme.colorScheme.surface.status.info.emphasized` | Info emphasized surface | +| `OudsTheme.colorScheme.surface.status.info.muted` | Info muted surface | +| `OudsTheme.colorScheme.surface.status.negative.emphasized` | Error emphasized surface | +| `OudsTheme.colorScheme.surface.status.negative.muted` | Error muted surface | +| `OudsTheme.colorScheme.surface.status.positive.emphasized` | Success emphasized surface | +| `OudsTheme.colorScheme.surface.status.positive.muted` | Success muted surface | +| `OudsTheme.colorScheme.surface.status.warning.emphasized` | Warning emphasized surface | +| `OudsTheme.colorScheme.surface.status.warning.muted` | Warning muted surface | + +--- + +## Typography (`OudsTheme.typography`) + +**Design guidelines:** [Typography tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-typography) + +> Text styles that automatically adapt to screen size (mobile/tablet). + +| Token | Recommended Usage | +|-------|-------------------| +| `OudsTheme.typography.display.large` | Hero title, splash screens | +| `OudsTheme.typography.display.medium` | Large display title | +| `OudsTheme.typography.display.small` | Medium display title | +| `OudsTheme.typography.heading.extraLarge` | H0 — Main page title | +| `OudsTheme.typography.heading.large` | H1 — Primary section title | +| `OudsTheme.typography.heading.medium` | H2 — Subsection title | +| `OudsTheme.typography.heading.small` | H3 — Group title | +| `OudsTheme.typography.body.large.default` | Large body text (regular weight) | +| `OudsTheme.typography.body.large.moderate` | Large body text (medium weight) | +| `OudsTheme.typography.body.large.strong` | Large body text (bold weight) | +| `OudsTheme.typography.body.medium.default` | Standard body text (regular) | +| `OudsTheme.typography.body.medium.moderate` | Standard body text (medium) | +| `OudsTheme.typography.body.medium.strong` | Standard body text (bold) | +| `OudsTheme.typography.body.small.default` | Compact body text (regular) | +| `OudsTheme.typography.body.small.moderate` | Compact body text (medium) | +| `OudsTheme.typography.body.small.strong` | Compact body text (bold) | +| `OudsTheme.typography.label.extraLarge.default` | XL label (regular) | +| `OudsTheme.typography.label.extraLarge.moderate` | XL label (medium) | +| `OudsTheme.typography.label.extraLarge.strong` | XL label (bold, for buttons) | +| `OudsTheme.typography.label.large.default` | Large label (regular) | +| `OudsTheme.typography.label.large.moderate` | Large label (medium) | +| `OudsTheme.typography.label.large.strong` | Large label (bold) | +| `OudsTheme.typography.label.medium.default` | Medium label (regular) | +| `OudsTheme.typography.label.medium.moderate` | Medium label (medium) | +| `OudsTheme.typography.label.medium.strong` | Medium label (bold) | +| `OudsTheme.typography.label.small.default` | Small label (regular) | +| `OudsTheme.typography.label.small.moderate` | Small label (medium) | +| `OudsTheme.typography.label.small.strong` | Small label (bold) | + +```kotlin +// Typography hierarchy +Column(verticalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.small)) { + Text( + text = stringResource(R.string.page_title), + style = OudsTheme.typography.heading.large + ) + Text( + text = stringResource(R.string.section_title), + style = OudsTheme.typography.heading.medium + ) + Text( + text = stringResource(R.string.body), + style = OudsTheme.typography.body.medium.default + ) + Text( + text = stringResource(R.string.caption), + style = OudsTheme.typography.label.small.default, + color = OudsTheme.colorScheme.content.muted + ) +} +``` + +--- + +## Spacing (`OudsTheme.spaces`) + +**Design guidelines:** [Space tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-space) + +### Fixed Spaces + +Fixed spacing values that remain constant regardless of screen size. + +| Token | Approximate Size | +|-------|-----------------| +| `OudsTheme.spaces.fixed.none` | 0 dp | +| `OudsTheme.spaces.fixed.threeExtraSmall` | ~2 dp | +| `OudsTheme.spaces.fixed.twoExtraSmall` | ~4 dp | +| `OudsTheme.spaces.fixed.extraSmall` | ~8 dp | +| `OudsTheme.spaces.fixed.small` | ~12 dp | +| `OudsTheme.spaces.fixed.medium` | ~16 dp | +| `OudsTheme.spaces.fixed.large` | ~24 dp | +| `OudsTheme.spaces.fixed.extraLarge` | ~32 dp | +| `OudsTheme.spaces.fixed.twoExtraLarge` | ~40 dp | +| `OudsTheme.spaces.fixed.threeExtraLarge` | ~48 dp | +| `OudsTheme.spaces.fixed.fourExtraLarge` | ~64 dp | +| `OudsTheme.spaces.fixed.fiveExtraLarge` | ~80 dp | + +### Scaled Spaces + +Adaptive spacing values that adjust based on screen size (mobile vs tablet). + +| Token | Usage | +|-------|-------| +| `OudsTheme.spaces.scaled.none` | 0 dp | +| `OudsTheme.spaces.scaled.threeExtraSmall` | Very compact spacing | +| `OudsTheme.spaces.scaled.twoExtraSmall` | Very compact spacing | +| `OudsTheme.spaces.scaled.extraSmall` | Compact spacing | +| `OudsTheme.spaces.scaled.small` | Small spacing | +| `OudsTheme.spaces.scaled.medium` | Medium spacing | +| `OudsTheme.spaces.scaled.large` | Large spacing | +| `OudsTheme.spaces.scaled.extraLarge` | Very large spacing | +| `OudsTheme.spaces.scaled.twoExtraLarge` | 2XL spacing | +| `OudsTheme.spaces.scaled.threeExtraLarge` | 3XL spacing | + +```kotlin +// Fixed spacing for consistent layouts +Card( + modifier = Modifier.padding(OudsTheme.spaces.fixed.medium) +) { + Column( + modifier = Modifier.padding(OudsTheme.spaces.fixed.large), + verticalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.small) + ) { + Text(stringResource(R.string.title)) + Text(stringResource(R.string.content)) + } +} + +// Scaled spacing for responsive layouts +Column( + modifier = Modifier.padding(OudsTheme.spaces.scaled.medium), + verticalArrangement = Arrangement.spacedBy(OudsTheme.spaces.scaled.small) +) { + // Content adapts spacing to screen size +} +``` + +--- + +## Sizes (`OudsTheme.sizes`) + +**Design guidelines:** [Size tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-size) + +### Icon Sizes — Decorative + +General-purpose icon sizes for standalone icons without textual context. + +| Token | Usage | +|-------|-------| +| `OudsTheme.sizes.icon.decorative.fourExtraSmall` | Tiny icon | +| `OudsTheme.sizes.icon.decorative.threeExtraSmall` | Very small icon | +| `OudsTheme.sizes.icon.decorative.twoExtraSmall` | Extra small icon | +| `OudsTheme.sizes.icon.decorative.extraSmall` | Small icon | +| `OudsTheme.sizes.icon.decorative.small` | Small-standard icon | +| `OudsTheme.sizes.icon.decorative.medium` | Standard icon (common use) | +| `OudsTheme.sizes.icon.decorative.large` | Large icon | +| `OudsTheme.sizes.icon.decorative.extraLarge` | Very large icon | +| `OudsTheme.sizes.icon.decorative.twoExtraLarge` | 2XL icon | + +### Icon Sizes — Contextual + +Icon sizes paired with text elements for proper alignment. + +```kotlin +// Icon with label +OudsTheme.sizes.icon.withLabel.small.sizeExtraSmall +OudsTheme.sizes.icon.withLabel.small.sizeSmall +OudsTheme.sizes.icon.withLabel.small.sizeMedium +OudsTheme.sizes.icon.withLabel.small.sizeLarge +OudsTheme.sizes.icon.withLabel.medium.sizeExtraSmall +OudsTheme.sizes.icon.withLabel.medium.sizeSmall +OudsTheme.sizes.icon.withLabel.medium.sizeMedium +OudsTheme.sizes.icon.withLabel.medium.sizeLarge +OudsTheme.sizes.icon.withLabel.large.sizeExtraSmall +OudsTheme.sizes.icon.withLabel.large.sizeSmall +OudsTheme.sizes.icon.withLabel.large.sizeMedium +OudsTheme.sizes.icon.withLabel.large.sizeLarge +OudsTheme.sizes.icon.withLabel.large.sizeExtraLarge +OudsTheme.sizes.icon.withLabel.extraLarge.sizeExtraSmall +OudsTheme.sizes.icon.withLabel.extraLarge.sizeSmall +OudsTheme.sizes.icon.withLabel.extraLarge.sizeMedium +OudsTheme.sizes.icon.withLabel.extraLarge.sizeLarge + +// Icon with body text +OudsTheme.sizes.icon.withBody.small.sizeSmall +OudsTheme.sizes.icon.withBody.small.sizeMedium +OudsTheme.sizes.icon.withBody.small.sizeLarge +OudsTheme.sizes.icon.withBody.medium.sizeSmall +OudsTheme.sizes.icon.withBody.medium.sizeMedium +OudsTheme.sizes.icon.withBody.medium.sizeLarge +OudsTheme.sizes.icon.withBody.large.sizeSmall +OudsTheme.sizes.icon.withBody.large.sizeMedium +OudsTheme.sizes.icon.withBody.large.sizeLarge + +// Icon with heading +OudsTheme.sizes.icon.withHeading.small.sizeSmall +OudsTheme.sizes.icon.withHeading.small.sizeMedium +OudsTheme.sizes.icon.withHeading.small.sizeLarge +OudsTheme.sizes.icon.withHeading.medium.sizeSmall +OudsTheme.sizes.icon.withHeading.medium.sizeMedium +OudsTheme.sizes.icon.withHeading.medium.sizeLarge +OudsTheme.sizes.icon.withHeading.large.sizeSmall +OudsTheme.sizes.icon.withHeading.large.sizeMedium +OudsTheme.sizes.icon.withHeading.large.sizeLarge +OudsTheme.sizes.icon.withHeading.extraLarge.sizeSmall +OudsTheme.sizes.icon.withHeading.extraLarge.sizeMedium +OudsTheme.sizes.icon.withHeading.extraLarge.sizeLarge +``` + +### Max Width Constraints + +Maximum width constraints for readable text blocks. + +```kotlin +// Body text +OudsTheme.sizes.maxWidth.body.small +OudsTheme.sizes.maxWidth.body.medium +OudsTheme.sizes.maxWidth.body.large + +// Display text +OudsTheme.sizes.maxWidth.display.small +OudsTheme.sizes.maxWidth.display.medium +OudsTheme.sizes.maxWidth.display.large + +// Heading text +OudsTheme.sizes.maxWidth.heading.small +OudsTheme.sizes.maxWidth.heading.medium +OudsTheme.sizes.maxWidth.heading.large +OudsTheme.sizes.maxWidth.heading.extraLarge + +// Label text +OudsTheme.sizes.maxWidth.label.small +OudsTheme.sizes.maxWidth.label.medium +OudsTheme.sizes.maxWidth.label.large +OudsTheme.sizes.maxWidth.label.extraLarge +``` + +### Minimum Interactive Area + +Minimum touch target size for accessibility. + +```kotlin +OudsTheme.sizes.minInteractiveArea // General minimum touch target +``` + +```kotlin +// Using icon sizes +Icon( + imageVector = Icons.Default.Settings, + contentDescription = stringResource(R.string.settings), + modifier = Modifier.size(OudsTheme.sizes.icon.decorative.medium) +) + +// Using max width for readable text +Text( + text = stringResource(R.string.long_content), + style = OudsTheme.typography.body.medium.default, + modifier = Modifier.widthIn(max = OudsTheme.sizes.maxWidth.body.medium) +) +``` + +--- + +## Borders (`OudsTheme.borders`) + +**Design guidelines:** [Border tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-border) + +### Width + +Border stroke widths. + +| Token | Usage | +|-------|-------| +| `OudsTheme.borders.width.none` | No border (0 dp) | +| `OudsTheme.borders.width.default` | Standard border width | +| `OudsTheme.borders.width.thin` | Thin border | +| `OudsTheme.borders.width.medium` | Medium border | +| `OudsTheme.borders.width.thick` | Thick border | +| `OudsTheme.borders.width.thicker` | Very thick border | +| `OudsTheme.borders.width.focus` | Focus ring (outer) | +| `OudsTheme.borders.width.focusInset` | Focus ring (inner/inset) | + +### Radius + +Corner rounding radii. + +| Token | Usage | +|-------|-------| +| `OudsTheme.borders.radius.none` | Square corners (0 dp) | +| `OudsTheme.borders.radius.default` | Standard rounding | +| `OudsTheme.borders.radius.small` | Slight rounding | +| `OudsTheme.borders.radius.medium` | Medium rounding (cards, chips) | +| `OudsTheme.borders.radius.large` | Large rounding | +| `OudsTheme.borders.radius.pill` | Pill/capsule shape (buttons, badges) | + +### Style + +Border stroke styles. + +| Token | Usage | +|-------|-------| +| `OudsTheme.borders.style.default` | Solid border | +| `OudsTheme.borders.style.drag` | Dashed border (drag & drop) | + +```kotlin +// Using border tokens +Box( + modifier = Modifier + .size(100.dp) + .border( + width = OudsTheme.borders.width.default, + color = OudsTheme.colorScheme.border.default, + shape = RoundedCornerShape(OudsTheme.borders.radius.medium) + ) +) + +// Card with border +Card( + shape = RoundedCornerShape(OudsTheme.borders.radius.large), + border = BorderStroke( + width = OudsTheme.borders.width.thin, + color = OudsTheme.colorScheme.border.muted + ) +) { + // Content +} +``` + +--- + +## Elevations (`OudsTheme.elevations`) + +**Design guidelines:** [Elevation tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-elevation) + +Shadow and z-axis elevation values. + +| Token | Usage | +|-------|-------| +| `OudsTheme.elevations.none` | No shadow (0 dp) | +| `OudsTheme.elevations.default` | Default elevation | +| `OudsTheme.elevations.raised` | Raised card elevation | +| `OudsTheme.elevations.sticky` | Sticky/floating bar elevation | +| `OudsTheme.elevations.drag` | Drag & drop elevation | +| `OudsTheme.elevations.emphasized` | Modal/dialog elevation | + +```kotlin +// Using elevations +Card( + elevation = CardDefaults.cardElevation( + defaultElevation = OudsTheme.elevations.raised + ) +) { + // Content +} + +Surface( + shadowElevation = OudsTheme.elevations.emphasized, + shape = RoundedCornerShape(OudsTheme.borders.radius.medium) +) { + // Modal content +} +``` + +--- + +## Opacities (`OudsTheme.opacities`) + +**Design guidelines:** [Opacity tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-opacity) + +Transparency alpha values (Float, 0.0 to 1.0). + +| Token | Value Range | Usage | +|-------|------------|-------| +| `OudsTheme.opacities.invisible` | 0.0 | Fully transparent | +| `OudsTheme.opacities.weakest` | Very low | Very subtle overlay | +| `OudsTheme.opacities.weaker` | Low | Subtle overlay | +| `OudsTheme.opacities.weak` | Medium-low | Light overlay | +| `OudsTheme.opacities.medium` | Medium | Standard overlay | +| `OudsTheme.opacities.strong` | Medium-high | Strong overlay | +| `OudsTheme.opacities.disabled` | Variable | Disabled state opacity | +| `OudsTheme.opacities.opaque` | 1.0 | Fully opaque | + +```kotlin +// Using opacity +Box( + modifier = Modifier + .fillMaxSize() + .background( + OudsTheme.colorScheme.always.black.copy( + alpha = OudsTheme.opacities.medium + ) + ) +) +``` + +--- + +## Effects (`OudsTheme.effects`) + +Visual effect values. + +| Token | Usage | +|-------|-------| +| `OudsTheme.effects.blurDrag` | Blur radius for drag effects (Int) | + +--- + +## Grids (`OudsTheme.grids`) + +**Design guidelines:** [Grid tokens documentation](https://r.orange.fr/r/S-ouds-doc-token-grid) + +Grid layout properties that adapt to screen size. + +| Token | Usage | +|-------|-------| +| `OudsTheme.grids.minWidth` | Minimum grid column width | +| `OudsTheme.grids.maxWidth` | Maximum grid column width | +| `OudsTheme.grids.margin` | Grid outer margin | +| `OudsTheme.grids.columnGap` | Gap between grid columns | + +```kotlin +// Using grid tokens +LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = OudsTheme.grids.minWidth), + contentPadding = PaddingValues(OudsTheme.grids.margin), + horizontalArrangement = Arrangement.spacedBy(OudsTheme.grids.columnGap) +) { + // Grid items +} +``` + +--- + +## Usage Examples + +### Complete Card Layout + +```kotlin +@Composable +fun ProductCard() { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(OudsTheme.spaces.fixed.medium), + shape = RoundedCornerShape(OudsTheme.borders.radius.large), + elevation = CardDefaults.cardElevation( + defaultElevation = OudsTheme.elevations.raised + ), + border = BorderStroke( + width = OudsTheme.borders.width.thin, + color = OudsTheme.colorScheme.border.muted + ) + ) { + Column( + modifier = Modifier.padding(OudsTheme.spaces.fixed.large), + verticalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.small) + ) { + Text( + text = stringResource(R.string.product_name), + style = OudsTheme.typography.heading.medium, + color = OudsTheme.colorScheme.content.default + ) + Text( + text = stringResource(R.string.product_description), + style = OudsTheme.typography.body.medium.default, + color = OudsTheme.colorScheme.content.muted, + modifier = Modifier.widthIn(max = OudsTheme.sizes.maxWidth.body.medium) + ) + Row( + horizontalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.small) + ) { + Icon( + imageVector = Icons.Default.Star, + contentDescription = null, + modifier = Modifier.size(OudsTheme.sizes.icon.decorative.small), + tint = OudsTheme.colorScheme.content.status.positive + ) + Text( + text = "4.5", + style = OudsTheme.typography.label.medium.strong + ) + } + } + } +} +``` + +### Styled Button + +```kotlin +@Composable +fun CustomStyledButton() { + Button( + onClick = { }, + colors = ButtonDefaults.buttonColors( + containerColor = OudsTheme.colorScheme.action.enabled, + contentColor = OudsTheme.colorScheme.content.onAction.enabled + ), + shape = RoundedCornerShape(OudsTheme.borders.radius.pill), + elevation = ButtonDefaults.buttonElevation( + defaultElevation = OudsTheme.elevations.raised + ), + contentPadding = PaddingValues( + horizontal = OudsTheme.spaces.fixed.large, + vertical = OudsTheme.spaces.fixed.medium + ) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.small), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.size(OudsTheme.sizes.icon.decorative.small) + ) + Text( + text = stringResource(R.string.add_to_cart), + style = OudsTheme.typography.label.large.strong + ) + } + } +} +``` + +### Text Hierarchy + +```kotlin +@Composable +fun ArticleContent() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(OudsTheme.spaces.fixed.large), + verticalArrangement = Arrangement.spacedBy(OudsTheme.spaces.fixed.medium) + ) { + Text( + text = stringResource(R.string.article_title), + style = OudsTheme.typography.heading.large, + color = OudsTheme.colorScheme.content.default + ) + Text( + text = stringResource(R.string.article_subtitle), + style = OudsTheme.typography.heading.small, + color = OudsTheme.colorScheme.content.muted + ) + HorizontalDivider( + thickness = OudsTheme.borders.width.thin, + color = OudsTheme.colorScheme.border.muted + ) + Text( + text = stringResource(R.string.article_body), + style = OudsTheme.typography.body.medium.default, + color = OudsTheme.colorScheme.content.default, + modifier = Modifier.widthIn(max = OudsTheme.sizes.maxWidth.body.medium) + ) + Text( + text = stringResource(R.string.article_caption), + style = OudsTheme.typography.label.small.default, + color = OudsTheme.colorScheme.content.muted + ) + } +} +``` + +--- + +## Token Generation + +All OUDS tokens are **generated by Tokenator**, a tool that converts Figma design tokens to Kotlin code. Generated files include comments like: + +```kotlin +// Orange brand tokens version 2.5.0 +// Generated by Tokenator +``` + +**Do not manually edit generated token files.** All token modifications must be made in Figma and regenerated through Tokenator. + +--- + +## References + +- **Documentation:** https://android.unified-design-system.orange.com/ +- **Repository:** https://github.com/Orange-OpenSource/ouds-android +- **Design System:** https://unified-design-system.orange.com/ +- **Color Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-color +- **Typography Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-typography +- **Space Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-space +- **Size Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-size +- **Border Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-border +- **Elevation Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-elevation +- **Opacity Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-opacity +- **Grid Guidelines:** https://r.orange.fr/r/S-ouds-doc-token-grid