diff --git a/mobile/kmp/gradle/libs.versions.toml b/mobile/kmp/gradle/libs.versions.toml
index 2d63e3dd1..40c85d346 100644
--- a/mobile/kmp/gradle/libs.versions.toml
+++ b/mobile/kmp/gradle/libs.versions.toml
@@ -1,13 +1,23 @@
[versions]
+calendar = "2.9.0"
kotlin = "2.2.0"
+ktor = "3.3.1"
compose = "1.10.0-rc02"
composeShadow = "2.0.4"
+coilCompose = "3.3.0"
+kotlinxDatetime = "0.6.1"
agp = "8.9.0"
androidx-activityCompose = "1.9.3"
[libraries]
+compose-calendar = { module = "com.kizitonwose.calendar:compose-multiplatform", version.ref = "calendar" }
+kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
compose-shadow = { module = "com.adamglin:compose-shadow", version.ref = "composeShadow" }
+coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
+coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coilCompose" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
+ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
diff --git a/mobile/kmp/sample/build.gradle.kts b/mobile/kmp/sample/build.gradle.kts
index 21086ddc7..da45f15ec 100644
--- a/mobile/kmp/sample/build.gradle.kts
+++ b/mobile/kmp/sample/build.gradle.kts
@@ -28,6 +28,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(project(":ui"))
+ implementation(project(":storybook"))
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
diff --git a/mobile/kmp/sample/src/commonMain/kotlin/com/atls/hyperion/sample/App.kt b/mobile/kmp/sample/src/commonMain/kotlin/com/atls/hyperion/sample/App.kt
index 5c31d68ad..cfcc0b92e 100644
--- a/mobile/kmp/sample/src/commonMain/kotlin/com/atls/hyperion/sample/App.kt
+++ b/mobile/kmp/sample/src/commonMain/kotlin/com/atls/hyperion/sample/App.kt
@@ -1,22 +1,41 @@
package com.atls.hyperion.sample
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import com.atls.hyperion.storybook.fragments.storybook.Storybook
+import com.atls.hyperion.ui.components.avatar.stories.AvatarStory
+import com.atls.hyperion.ui.components.button.stories.ButtonStory
+import com.atls.hyperion.ui.components.card.stories.CardStory
+import com.atls.hyperion.ui.components.checkbox.stories.CheckboxStory
+import com.atls.hyperion.ui.components.divider.stories.DividerStory
+import com.atls.hyperion.ui.components.input.stories.InputStory
+import com.atls.hyperion.ui.components.modal.bottom.stories.BottomDialogStory
+import com.atls.hyperion.ui.components.modal.popup.stories.PopupStory
+import com.atls.hyperion.ui.components.switch.stories.SwitchStory
+import com.atls.hyperion.ui.fragment.datepicker.stories.DatePickerStory
+import com.atls.hyperion.ui.fragment.datepicker.stories.DateRangePickerStory
+import com.atls.hyperion.ui.primitives.stories.LinkStory
+import com.atls.hyperion.ui.primitives.stories.TextStory
@Composable
fun App() {
MaterialTheme {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp)
- ) {
- //TODO use for demonstration and tests
- }
+ Storybook(
+ components = listOf(
+ AvatarStory(),
+ BottomDialogStory(),
+ ButtonStory(),
+ CheckboxStory(),
+ DatePickerStory(),
+ DateRangePickerStory(),
+ DividerStory(),
+ InputStory(),
+ PopupStory(),
+ SwitchStory(),
+ CardStory(),
+ TextStory(),
+ LinkStory()
+ )
+ )
}
}
diff --git a/mobile/kmp/settings.gradle.kts b/mobile/kmp/settings.gradle.kts
index f3b45bbcc..583813c1d 100644
--- a/mobile/kmp/settings.gradle.kts
+++ b/mobile/kmp/settings.gradle.kts
@@ -16,3 +16,5 @@ dependencyResolutionManagement {
rootProject.name = "hyperion"
include(":sample")
include(":ui")
+include(":storybook")
+include(":storybook-api")
diff --git a/mobile/kmp/storybook/.gitignore b/mobile/kmp/storybook/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/mobile/kmp/storybook/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/mobile/kmp/storybook/build.gradle.kts b/mobile/kmp/storybook/build.gradle.kts
new file mode 100644
index 000000000..f77186dec
--- /dev/null
+++ b/mobile/kmp/storybook/build.gradle.kts
@@ -0,0 +1,65 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidLibrary)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+}
+
+group = "com.atls.hyperion"
+version = "0.1.0"
+
+kotlin {
+ androidTarget {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_21)
+ }
+ }
+
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64()
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ baseName = "storybook"
+ isStatic = true
+ }
+ }
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation(compose.runtime)
+ implementation(compose.foundation)
+ implementation(compose.material)
+ implementation(compose.ui)
+ implementation(compose.components.resources)
+ }
+ }
+}
+
+compose.resources {
+ publicResClass = false
+ generateResClass = auto
+}
+
+android {
+ namespace = "com.atls.hyperion.storybook"
+
+ val compileSdkValue = System.getenv(Versions.COMPILE_SDK_KEY)?.toInt()
+ ?: (extra[Versions.COMPILE_SDK_KEY] as String).toInt()
+ val minSdkValue = System.getenv(Versions.MIN_SDK_KEY)?.toInt()
+ ?: (extra[Versions.MIN_SDK_KEY] as String).toInt()
+ val javaTargetValue = System.getenv(Versions.JAVA_TARGET_KEY)?.toInt()
+ ?: (extra[Versions.JAVA_TARGET_KEY] as String).toInt()
+
+ compileSdk = compileSdkValue
+ defaultConfig {
+ minSdk = minSdkValue
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.toVersion(javaTargetValue)
+ targetCompatibility = JavaVersion.toVersion(javaTargetValue)
+ }
+}
diff --git a/mobile/kmp/storybook/src/commonMain/composeResources/drawable/menu.xml b/mobile/kmp/storybook/src/commonMain/composeResources/drawable/menu.xml
new file mode 100644
index 000000000..9e7f0e919
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/composeResources/drawable/menu.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/mobile/kmp/storybook/src/commonMain/composeResources/values/strings.xml b/mobile/kmp/storybook/src/commonMain/composeResources/values/strings.xml
new file mode 100644
index 000000000..c2e3d112a
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/composeResources/values/strings.xml
@@ -0,0 +1,5 @@
+
+ Components
+ Select a component from the sidebar
+ Toggle Sidebar
+
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/config/Constants.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/config/Constants.kt
new file mode 100644
index 000000000..edc7c2c79
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/config/Constants.kt
@@ -0,0 +1,5 @@
+package com.atls.hyperion.storybook.config
+
+import androidx.compose.ui.unit.dp
+
+internal val sidebarWidth = 250.dp
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/Storybook.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/Storybook.kt
new file mode 100644
index 000000000..4963b0f49
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/Storybook.kt
@@ -0,0 +1,90 @@
+package com.atls.hyperion.storybook.fragments.storybook
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawingPadding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.fragments.storybook.ui.Sidebar
+import com.atls.hyperion.storybook.generated.resources.Res
+import com.atls.hyperion.storybook.generated.resources.menu
+import com.atls.hyperion.storybook.generated.resources.select_component_from_sidebar
+import com.atls.hyperion.storybook.generated.resources.toggle_sidebar
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.theme.Padding
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+fun Storybook(
+ components: List
+) {
+ var isSidebarVisible by remember { mutableStateOf(false) }
+ var selectedComponent by remember(components) {
+ mutableStateOf(components.firstOrNull())
+ }
+
+ Box {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .safeDrawingPadding()
+ ) {
+ IconButton(
+ onClick = { isSidebarVisible = !isSidebarVisible },
+ modifier = Modifier
+ .padding(Padding.small)
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.menu),
+ contentDescription = stringResource(Res.string.toggle_sidebar),
+ tint = MaterialTheme.colors.onSurface
+ )
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ if (selectedComponent != null) {
+ selectedComponent?.Content()
+ } else {
+ Text(
+ text = stringResource(Res.string.select_component_from_sidebar),
+ modifier = Modifier
+ .align(Alignment.Center),
+ )
+ }
+ }
+ }
+
+ AnimatedVisibility(
+ visible = isSidebarVisible,
+ enter = slideInHorizontally(),
+ exit = slideOutHorizontally()
+ ) {
+ Sidebar(
+ components = components,
+ selectedComponent = selectedComponent,
+ onComponentClick = { component ->
+ selectedComponent = component
+ isSidebarVisible = false
+ },
+ onClose = { isSidebarVisible = false }
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/ui/Sidebar.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/ui/Sidebar.kt
new file mode 100644
index 000000000..a754bde1a
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/fragments/storybook/ui/Sidebar.kt
@@ -0,0 +1,74 @@
+package com.atls.hyperion.storybook.fragments.storybook.ui
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Divider
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import com.atls.hyperion.storybook.config.sidebarWidth
+import com.atls.hyperion.storybook.generated.resources.Res
+import com.atls.hyperion.storybook.generated.resources.components
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.theme.FontSize
+import com.atls.hyperion.storybook.shared.ui.theme.Padding
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+fun Sidebar(
+ components: List,
+ selectedComponent: ComponentExample?,
+ onComponentClick: (ComponentExample) -> Unit,
+ onClose: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .clickable { onClose() }
+ ) {
+ Surface(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(sidebarWidth),
+ color = MaterialTheme.colors.surface
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(Padding.medium)
+ ) {
+ Text(
+ text = stringResource(Res.string.components),
+ fontSize = FontSize.large,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(vertical = Padding.large)
+ )
+ LazyColumn {
+ items(components) { component ->
+ Text(
+ text = component.name,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onComponentClick(component) }
+ .padding(vertical = Padding.small),
+ fontWeight = if (component == selectedComponent) FontWeight.Bold else FontWeight.Normal,
+ fontSize = FontSize.small
+ )
+ Divider()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/model/ComponentExample.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/model/ComponentExample.kt
new file mode 100644
index 000000000..9bd08f1de
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/model/ComponentExample.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.storybook.shared.model
+
+import androidx.compose.runtime.Composable
+
+interface ComponentExample {
+ val name: String
+ @Composable
+ fun Content()
+}
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/ComponentVariants.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/ComponentVariants.kt
new file mode 100644
index 000000000..dab73d455
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/ComponentVariants.kt
@@ -0,0 +1,68 @@
+package com.atls.hyperion.storybook.shared.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Divider
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import com.atls.hyperion.storybook.shared.ui.theme.FontSize
+import com.atls.hyperion.storybook.shared.ui.theme.Padding
+
+@Composable
+fun ComponentVariants(
+ name: String,
+ appearances: List A>>,
+ shapes: List S>>,
+ content: @Composable (A, S) -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState())
+ .padding(Padding.medium),
+ verticalArrangement = Arrangement.spacedBy(Padding.large)
+ ) {
+ Text(
+ text = name,
+ fontSize = FontSize.large,
+ fontWeight = FontWeight.Bold
+ )
+
+ appearances.forEach { (appearanceName, appearanceProvider) ->
+ Column(
+ verticalArrangement = Arrangement.spacedBy(Padding.medium),
+ ) {
+ Text(
+ text = appearanceName,
+ fontSize = FontSize.medium,
+ fontWeight = FontWeight.SemiBold
+ )
+
+ shapes.forEach { (shapeName, shapeProvider) ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = shapeName,
+ fontSize = FontSize.small,
+ modifier = Modifier.padding(top = Padding.tiny)
+ )
+ Spacer(Modifier.width(Padding.small))
+ content(appearanceProvider(), shapeProvider())
+ }
+ Divider()
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/FontSize.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/FontSize.kt
new file mode 100644
index 000000000..ce66f2e52
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/FontSize.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.storybook.shared.ui.theme
+
+import androidx.compose.ui.unit.sp
+
+object FontSize {
+ val large = 24.sp
+ val medium = 20.sp
+ val small = 12.sp
+}
diff --git a/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/Padding.kt b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/Padding.kt
new file mode 100644
index 000000000..4aaa5e0b5
--- /dev/null
+++ b/mobile/kmp/storybook/src/commonMain/kotlin/com/atls/hyperion/storybook/shared/ui/theme/Padding.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.storybook.shared.ui.theme
+
+import androidx.compose.ui.unit.dp
+
+object Padding {
+ val large = 24.dp
+ val medium = 16.dp
+ val small = 12.dp
+ val tiny = 8.dp
+}
diff --git a/mobile/kmp/ui/build.gradle.kts b/mobile/kmp/ui/build.gradle.kts
index 2407a1fe5..fcb039b21 100644
--- a/mobile/kmp/ui/build.gradle.kts
+++ b/mobile/kmp/ui/build.gradle.kts
@@ -16,7 +16,7 @@ kotlin {
jvmTarget.set(JvmTarget.JVM_21)
}
}
-
+
listOf(
iosX64(),
iosArm64(),
@@ -27,16 +27,34 @@ kotlin {
isStatic = true
}
}
-
+
sourceSets {
commonMain.dependencies {
+ api(project(":storybook"))
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
+ implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.compose.shadow)
+ implementation(libs.coil.compose)
+ implementation(libs.coil.network.ktor)
+ implementation(libs.compose.calendar)
+ api(libs.kotlinx.datetime)
+ }
+
+ androidMain {
+ dependencies {
+ implementation(libs.ktor.client.okhttp)
+ }
+ }
+
+ iosMain {
+ dependencies {
+ implementation(libs.ktor.client.darwin)
+ }
}
}
}
diff --git a/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_left.xml b/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_left.xml
new file mode 100644
index 000000000..62b41a877
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_left.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_right.xml b/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_right.xml
new file mode 100644
index 000000000..3979fa801
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/composeResources/drawable/chevron_right.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Component.kt
new file mode 100644
index 000000000..95de6f357
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Component.kt
@@ -0,0 +1,68 @@
+package com.atls.hyperion.ui.components.avatar
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import coil3.compose.AsyncImage
+import com.atls.hyperion.ui.components.avatar.styles.appearance.AvatarAppearance
+import com.atls.hyperion.ui.components.avatar.styles.shape.AvatarShape
+import com.atls.hyperion.ui.primitives.image.Image
+
+@Composable
+fun Avatar(
+ modifier: Modifier = Modifier,
+ appearance: AvatarAppearance,
+ shape: AvatarShape,
+ content: @Composable BoxScope.() -> Unit = {}
+) {
+ AvatarLayout(
+ modifier = modifier,
+ appearance = appearance,
+ shape = shape,
+ content = content
+ )
+}
+
+@Composable
+fun Avatar(
+ modifier: Modifier = Modifier,
+ image: Painter,
+ appearance: AvatarAppearance,
+ shape: AvatarShape,
+ contentScale: ContentScale = ContentScale.Fit
+) {
+ AvatarLayout(
+ modifier = modifier,
+ appearance = appearance,
+ shape = shape
+ ) {
+ Image(
+ image = image,
+ contentScale = contentScale
+ )
+ }
+}
+
+@Composable
+fun Avatar(
+ modifier: Modifier = Modifier,
+ imageUrl: String,
+ appearance: AvatarAppearance,
+ shape: AvatarShape
+) {
+ AvatarLayout(
+ modifier = modifier,
+ appearance = appearance,
+ shape = shape
+ ) {
+ AsyncImage(
+ modifier = Modifier
+ .matchParentSize(),
+ model = imageUrl,
+ contentScale = ContentScale.Crop,
+ contentDescription = null
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Layout.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Layout.kt
new file mode 100644
index 000000000..82985fcea
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/Layout.kt
@@ -0,0 +1,36 @@
+package com.atls.hyperion.ui.components.avatar
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import com.atls.hyperion.ui.components.avatar.styles.appearance.AvatarAppearance
+import com.atls.hyperion.ui.components.avatar.styles.shape.AvatarShape
+
+@Composable
+fun AvatarLayout(
+ modifier: Modifier = Modifier,
+ appearance: AvatarAppearance,
+ shape: AvatarShape,
+ content: @Composable BoxScope.() -> Unit
+) {
+ Box(
+ modifier = modifier
+ .size(shape.size)
+ .border(
+ shape.borderStroke,
+ appearance.borderColor
+ )
+ .clip(RoundedCornerShape(shape.cornerRadius))
+ .background(appearance.backgroundColor),
+ contentAlignment = Alignment.Center
+ ) {
+ content()
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/stories/Component.kt
new file mode 100644
index 000000000..d04c18301
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/stories/Component.kt
@@ -0,0 +1,43 @@
+package com.atls.hyperion.ui.components.avatar.stories
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.avatar.Avatar
+import com.atls.hyperion.ui.components.avatar.styles.appearance.AvatarAppearance
+import com.atls.hyperion.ui.components.avatar.styles.appearance.default
+import com.atls.hyperion.ui.components.avatar.styles.shape.AvatarShape
+import com.atls.hyperion.ui.components.avatar.styles.shape.largeCircle
+import com.atls.hyperion.ui.components.avatar.styles.shape.largeSquare
+import com.atls.hyperion.ui.components.avatar.styles.shape.normalCircle
+import com.atls.hyperion.ui.components.avatar.styles.shape.normalSquare
+import com.atls.hyperion.ui.components.avatar.styles.shape.smallCircle
+import com.atls.hyperion.ui.components.avatar.styles.shape.smallSquare
+
+class AvatarStory : ComponentExample {
+ override val name: String = "Avatar"
+
+ @Composable
+ override fun Content() {
+ ComponentVariants(
+ name = "Avatar",
+ appearances = listOf(
+ "Default" to { AvatarAppearance.default() }
+ ),
+ shapes = listOf(
+ "Small Circle" to { AvatarShape.smallCircle() },
+ "Normal Circle" to { AvatarShape.normalCircle() },
+ "Large Circle" to { AvatarShape.largeCircle() },
+ "Small Square" to { AvatarShape.smallSquare() },
+ "Normal Square" to { AvatarShape.normalSquare() },
+ "Large Square" to { AvatarShape.largeSquare() }
+ )
+ ) { appearance: AvatarAppearance, shape: AvatarShape ->
+ Avatar(
+ appearance = appearance,
+ shape = shape,
+ imageUrl = "https://media.istockphoto.com/id/1224772234/ru/фото/портрет-полосатой-кошки-с-зелеными-глазами-на-сером-фоне-кошка-смотрит-прямо-в-камеру.jpg?s=612x612&w=0&k=20&c=W5q6PFsJhSNgDuyHwi-xSSUaftlrfToSFix31JJ2eVU="
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Appearance.kt
new file mode 100644
index 000000000..243f0b678
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Appearance.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.avatar.styles.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class AvatarAppearance(
+ val backgroundColor: Color,
+ val borderColor: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Variants.kt
new file mode 100644
index 000000000..c5f3e7b82
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/appearance/Variants.kt
@@ -0,0 +1,11 @@
+package com.atls.hyperion.ui.components.avatar.styles.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+@Composable
+fun AvatarAppearance.Companion.default(): AvatarAppearance =
+ AvatarAppearance(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.transparent
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Shape.kt
new file mode 100644
index 000000000..5a067c91e
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Shape.kt
@@ -0,0 +1,12 @@
+package com.atls.hyperion.ui.components.avatar.styles.shape
+
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+
+data class AvatarShape(
+ val size: Dp,
+ val cornerRadius: Dp,
+ val borderStroke: Dp = BorderStroke.none
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Variants.kt
new file mode 100644
index 000000000..53c6f0e19
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/avatar/styles/shape/Variants.kt
@@ -0,0 +1,47 @@
+package com.atls.hyperion.ui.components.avatar.styles.shape
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.primitives.image.ImageSize
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+
+@Composable
+fun AvatarShape.Companion.smallCircle(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.medium,
+ cornerRadius = CornerRadius.full
+ )
+
+@Composable
+fun AvatarShape.Companion.normalCircle(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.large,
+ cornerRadius = CornerRadius.full
+ )
+
+@Composable
+fun AvatarShape.Companion.largeCircle(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.huge,
+ cornerRadius = CornerRadius.full
+ )
+
+@Composable
+fun AvatarShape.Companion.smallSquare(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.medium,
+ cornerRadius = CornerRadius.xs3
+ )
+
+@Composable
+fun AvatarShape.Companion.normalSquare(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.large,
+ cornerRadius = CornerRadius.xs3
+ )
+
+@Composable
+fun AvatarShape.Companion.largeSquare(): AvatarShape =
+ AvatarShape(
+ size = ImageSize.huge,
+ cornerRadius = CornerRadius.xs3
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Button.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Button.kt
new file mode 100644
index 000000000..748afb949
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Button.kt
@@ -0,0 +1,37 @@
+package com.atls.hyperion.ui.components.button
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.atls.hyperion.ui.components.button.locals.LocalState
+import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance
+import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape
+import com.atls.hyperion.ui.shared.addon.AddonSlotManager
+
+@Composable
+fun Button(
+ modifier: Modifier = Modifier,
+ text: String,
+ enabled: Boolean = true,
+ appearance: ButtonAppearance,
+ shape: ButtonShape,
+ addons: AddonSlotManager = AddonSlotManager(),
+ onClick: () -> Unit
+) {
+ ButtonLayout(
+ modifier = modifier,
+ enabled = enabled,
+ appearance = appearance,
+ shape = shape,
+ addons = addons,
+ onClick = onClick
+ ) {
+ Text(
+ text = text,
+ textAlign = TextAlign.Center,
+ color = appearance.fromState(LocalState.current).textColor,
+ style = shape.typography
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt
new file mode 100644
index 000000000..3077aaab7
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt
@@ -0,0 +1,108 @@
+package com.atls.hyperion.ui.components.button
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import com.atls.hyperion.ui.components.button.locals.LocalState
+import com.atls.hyperion.ui.components.button.state.ButtonState
+import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance
+import com.atls.hyperion.ui.components.button.styles.appearance.Colors
+import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape
+import com.atls.hyperion.ui.shared.addon.AddonPosition
+import com.atls.hyperion.ui.shared.addon.AddonSlotManager
+import com.atls.hyperion.ui.theme.tokens.layout.Elevation
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun ButtonLayout(
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ appearance: ButtonAppearance,
+ shape: ButtonShape,
+ addons: AddonSlotManager = AddonSlotManager(),
+ onClick: () -> Unit,
+ content: @Composable () -> Unit
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isPressed = interactionSource.collectIsPressedAsState().value
+
+ val state = when {
+ !enabled -> ButtonState.Disabled
+ isPressed -> ButtonState.Pressed
+ else -> ButtonState.Default
+ }
+ val colors = appearance.fromState(state)
+
+ CompositionLocalProvider(
+ LocalState provides state,
+ LocalMinimumInteractiveComponentEnforcement provides false
+ ) {
+ Button(
+ onClick = onClick,
+ shape = RoundedCornerShape(shape.cornerRadius),
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = when (colors) {
+ is Colors.Solid -> colors.backgroundColor
+ is Colors.Gradient -> ThemeColors.Palette.transparent
+ },
+ contentColor = colors.textColor,
+ disabledBackgroundColor = when (colors) {
+ is Colors.Solid -> colors.backgroundColor
+ is Colors.Gradient -> ThemeColors.Palette.transparent
+ },
+ disabledContentColor = colors.textColor
+ ),
+ border = BorderStroke(shape.borderStroke, colors.borderColor),
+ contentPadding = shape.paddings,
+ enabled = enabled,
+ elevation = ButtonDefaults.elevation(
+ defaultElevation = Elevation.zero,
+ pressedElevation = Elevation.zero,
+ disabledElevation = Elevation.zero
+ ),
+ interactionSource = interactionSource,
+ modifier = modifier
+ .shadow(
+ elevation = appearance.shadowElevation,
+ shape = RoundedCornerShape(shape.cornerRadius),
+ clip = false
+ )
+ .then(
+ if (colors is Colors.Gradient) {
+ Modifier.background(
+ colors.backgroundBrush,
+ RoundedCornerShape(shape.cornerRadius)
+ )
+ } else Modifier
+ )
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ addons.get(AddonPosition.Before).forEach {
+ it.Content()
+ it.Spacer()
+ }
+
+ content()
+
+ addons.get(AddonPosition.After).forEach {
+ it.Spacer()
+ it.Content()
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/locals/LocalState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/locals/LocalState.kt
new file mode 100644
index 000000000..daf383b04
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/locals/LocalState.kt
@@ -0,0 +1,8 @@
+package com.atls.hyperion.ui.components.button.locals
+
+import androidx.compose.runtime.compositionLocalOf
+import com.atls.hyperion.ui.components.button.state.ButtonState
+
+val LocalState = compositionLocalOf {
+ error("ButtonState not provided")
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt
new file mode 100644
index 000000000..2f1d9a198
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.components.button.state
+
+enum class ButtonState {
+ Default,
+ Disabled,
+ Pressed
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt
new file mode 100644
index 000000000..af661f4a5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt
@@ -0,0 +1,79 @@
+package com.atls.hyperion.ui.components.button.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.button.Button
+import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance
+import com.atls.hyperion.ui.components.button.styles.appearance.blue
+import com.atls.hyperion.ui.components.button.styles.appearance.lightBlue
+import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape
+import com.atls.hyperion.ui.components.button.styles.shape.huge
+import com.atls.hyperion.ui.components.button.styles.shape.large
+import com.atls.hyperion.ui.components.button.styles.shape.medium
+import com.atls.hyperion.ui.components.button.styles.shape.normal
+import com.atls.hyperion.ui.components.button.styles.shape.semiMedium
+import com.atls.hyperion.ui.components.button.styles.shape.small
+import com.atls.hyperion.ui.components.button.styles.shape.smallSizeMediumRadii
+import com.atls.hyperion.ui.primitives.HorizontalSpacer
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+class ButtonStory : ComponentExample {
+ override val name: String = "Button"
+
+ @Composable
+ override fun Content() {
+ var enabled by remember { mutableStateOf(true) }
+
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Space.g12)
+ ) {
+ Text(modifier = Modifier.weight(Weight.full), text = "Enabled")
+ HorizontalSpacer(Space.g12)
+ Switch(checked = enabled, onCheckedChange = { enabled = it })
+ }
+ VerticalSpacer(Space.g12)
+
+ ComponentVariants(
+ name = "Button",
+ appearances = listOf(
+ "Blue" to { ButtonAppearance.blue() },
+ "Light Blue" to { ButtonAppearance.lightBlue() }
+ ),
+ shapes = listOf(
+ "Huge" to { ButtonShape.huge() },
+ "Large" to { ButtonShape.large() },
+ "Semi Medium" to { ButtonShape.semiMedium() },
+ "Medium" to { ButtonShape.medium() },
+ "Normal" to { ButtonShape.normal() },
+ "Small" to { ButtonShape.small() },
+ "Small Medium Radii" to { ButtonShape.smallSizeMediumRadii() }
+ )
+ ) { appearance: ButtonAppearance, shape: ButtonShape ->
+ Button(
+ text = "Button",
+ appearance = appearance,
+ shape = shape,
+ enabled = enabled,
+ onClick = {}
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt
new file mode 100644
index 000000000..1c1b0db9c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt
@@ -0,0 +1,22 @@
+package com.atls.hyperion.ui.components.button.styles.appearance
+
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.components.button.state.ButtonState
+import com.atls.hyperion.ui.theme.tokens.layout.Elevation
+
+data class ButtonAppearance(
+ val default: Colors,
+ val pressed: Colors,
+ val disabled: Colors,
+ val shadowElevation: Dp = Elevation.zero
+) {
+ fun fromState(state: ButtonState): Colors {
+ return when (state) {
+ ButtonState.Default -> default
+ ButtonState.Disabled -> disabled
+ ButtonState.Pressed -> pressed
+ }
+ }
+
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt
new file mode 100644
index 000000000..f7a6998ed
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt
@@ -0,0 +1,22 @@
+package com.atls.hyperion.ui.components.button.styles.appearance
+
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+sealed class Colors(
+ val textColor: Color,
+ val borderColor: Color = ThemeColors.Palette.transparent
+) {
+ class Solid(
+ val backgroundColor: Color,
+ textColor: Color,
+ borderColor: Color = ThemeColors.Palette.transparent
+ ) : Colors(textColor, borderColor)
+
+ class Gradient(
+ val backgroundBrush: Brush,
+ textColor: Color,
+ borderColor: Color = ThemeColors.Palette.transparent
+ ) : Colors(textColor, borderColor)
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt
new file mode 100644
index 000000000..3ad7afe39
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt
@@ -0,0 +1,42 @@
+package com.atls.hyperion.ui.components.button.styles.appearance
+
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+import com.atls.hyperion.ui.components.button.styles.appearance.Colors as ButtonColors
+
+fun ButtonAppearance.Companion.blue(): ButtonAppearance =
+ ButtonAppearance(
+ default = ButtonColors.Solid(
+ backgroundColor = Colors.Button.Blue.Default.background,
+ textColor = Colors.Button.Blue.Default.font,
+ borderColor = Colors.Button.Blue.Default.border
+ ),
+ pressed = ButtonColors.Solid(
+ backgroundColor = Colors.Button.Blue.Pressed.background,
+ textColor = Colors.Button.Blue.Pressed.font,
+ borderColor = Colors.Button.Blue.Pressed.border
+ ),
+ disabled = ButtonColors.Solid(
+ backgroundColor = Colors.Button.Blue.Disabled.background,
+ textColor = Colors.Button.Blue.Disabled.font,
+ borderColor = Colors.Button.Blue.Disabled.border
+ )
+ )
+
+fun ButtonAppearance.Companion.lightBlue(): ButtonAppearance =
+ ButtonAppearance(
+ default = ButtonColors.Solid(
+ backgroundColor = Colors.Button.LightBlue.Default.background,
+ textColor = Colors.Button.LightBlue.Default.font,
+ borderColor = Colors.Button.LightBlue.Default.border
+ ),
+ pressed = ButtonColors.Solid(
+ backgroundColor = Colors.Button.LightBlue.Pressed.background,
+ textColor = Colors.Button.LightBlue.Pressed.font,
+ borderColor = Colors.Button.LightBlue.Pressed.border
+ ),
+ disabled = ButtonColors.Solid(
+ backgroundColor = Colors.Button.LightBlue.Disabled.background,
+ textColor = Colors.Button.LightBlue.Disabled.font,
+ borderColor = Colors.Button.LightBlue.Disabled.border
+ )
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt
new file mode 100644
index 000000000..5b4a745a3
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt
@@ -0,0 +1,14 @@
+package com.atls.hyperion.ui.components.button.styles.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+
+data class ButtonShape(
+ val cornerRadius: Dp,
+ val paddings: PaddingValues,
+ val typography: TextStyle,
+ val borderStroke: Dp
+) {
+ companion object Companion
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt
new file mode 100644
index 000000000..ffb849f56
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt
@@ -0,0 +1,73 @@
+package com.atls.hyperion.ui.components.button.styles.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+import com.atls.hyperion.ui.theme.typography.LineHeights
+
+@Composable
+fun ButtonShape.Companion.huge(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g24, vertical = Space.g16),
+ typography = TextStyle(fontSize = FontSize.xl3, lineHeight = LineHeights.xl4),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.large(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g24, vertical = Space.g14),
+ typography = TextStyle(fontSize = FontSize.xl2, lineHeight = LineHeights.xl3),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.semiMedium(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g20, vertical = Space.g12),
+ typography = TextStyle(fontSize = FontSize.lg, lineHeight = LineHeights.xl),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.medium(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g16, vertical = Space.g10),
+ typography = TextStyle(fontSize = FontSize.md, lineHeight = LineHeights.md),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.normal(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g24, vertical = Space.g8),
+ typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeights.sm),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.small(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl4,
+ paddings = PaddingValues(horizontal = Space.g16, vertical = Space.g6),
+ typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeights.xs),
+ borderStroke = BorderStroke.none
+ )
+
+@Composable
+fun ButtonShape.Companion.smallSizeMediumRadii(): ButtonShape =
+ ButtonShape(
+ cornerRadius = CornerRadius.xl2_5,
+ paddings = PaddingValues(horizontal = Space.g16, vertical = Space.g6),
+ typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeights.xs),
+ borderStroke = BorderStroke.none
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/Component.kt
new file mode 100644
index 000000000..593adc282
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/Component.kt
@@ -0,0 +1,42 @@
+package com.atls.hyperion.ui.components.card
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.card.style.appearance.CardAppearance
+import com.atls.hyperion.ui.components.card.style.shape.CardShape
+
+@Composable
+fun Card(
+ modifier: Modifier = Modifier,
+ shape: CardShape,
+ appearance: CardAppearance,
+ contentAlignment: Alignment = Alignment.TopCenter,
+ content: @Composable BoxScope.() -> Unit
+) {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(shape.cornerRadius),
+ color = appearance.backgroundColor,
+ elevation = appearance.elevation
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = shape.borderWidth,
+ color = appearance.borderColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ .padding(shape.padding),
+ contentAlignment = contentAlignment
+ ) {
+ content()
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/stories/Component.kt
new file mode 100644
index 000000000..32df3a532
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/stories/Component.kt
@@ -0,0 +1,36 @@
+package com.atls.hyperion.ui.components.card.stories
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.card.Card
+import com.atls.hyperion.ui.components.card.style.appearance.CardAppearance
+import com.atls.hyperion.ui.components.card.style.appearance.white
+import com.atls.hyperion.ui.components.card.style.shape.CardShape
+import com.atls.hyperion.ui.components.card.style.shape.medium
+
+class CardStory : ComponentExample {
+
+ override val name: String = "Card"
+
+ @Composable
+ override fun Content() {
+ ComponentVariants(
+ name = "Card",
+ appearances = listOf(
+ "White" to { CardAppearance.white() }
+ ),
+ shapes = listOf(
+ "Medium" to { CardShape.medium() }
+ )
+ ) { appearance: CardAppearance, shape: CardShape ->
+ Card(
+ appearance = appearance,
+ shape = shape
+ ) {
+ Text("Card Content")
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Appearance.kt
new file mode 100644
index 000000000..1f874708c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Appearance.kt
@@ -0,0 +1,14 @@
+package com.atls.hyperion.ui.components.card.style.appearance
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.theme.tokens.layout.Elevation
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+data class CardAppearance(
+ val backgroundColor: Color,
+ val borderColor: Color = ThemeColors.Palette.transparent,
+ val elevation: Dp = Elevation.zero
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Variants.kt
new file mode 100644
index 000000000..817ef379f
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/appearance/Variants.kt
@@ -0,0 +1,11 @@
+package com.atls.hyperion.ui.components.card.style.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+
+@Composable
+fun CardAppearance.Companion.white(): CardAppearance =
+ CardAppearance(
+ backgroundColor = Colors.Palette.white,
+ borderColor = Colors.Palette.gray
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Shape.kt
new file mode 100644
index 000000000..a1dc3011d
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Shape.kt
@@ -0,0 +1,12 @@
+package com.atls.hyperion.ui.components.card.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+
+data class CardShape(
+ val cornerRadius: Dp,
+ val padding: PaddingValues,
+ val borderWidth: Dp
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Variants.kt
new file mode 100644
index 000000000..7fa6617d5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/card/style/shape/Variants.kt
@@ -0,0 +1,15 @@
+package com.atls.hyperion.ui.components.card.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+@Composable
+fun CardShape.Companion.medium(): CardShape =
+ CardShape(
+ cornerRadius = CornerRadius.xl,
+ padding = PaddingValues(Space.g16),
+ borderWidth = BorderStroke.none
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/Component.kt
new file mode 100644
index 000000000..f0f1c2bf7
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/Component.kt
@@ -0,0 +1,71 @@
+package com.atls.hyperion.ui.components.checkbox
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import com.atls.hyperion.ui.components.checkbox.locals.LocalState
+import com.atls.hyperion.ui.components.checkbox.state.State
+import com.atls.hyperion.ui.components.checkbox.styles.appearance.CheckboxAppearance
+import com.atls.hyperion.ui.components.checkbox.styles.shape.CheckboxShape
+import com.atls.hyperion.ui.primitives.icon.Icon
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+@Composable
+fun Checkbox(
+ modifier: Modifier = Modifier,
+ state: State = State.Default,
+ appearance: CheckboxAppearance,
+ shape: CheckboxShape,
+ icon: Painter? = null,
+ onCheckedChange: ((Boolean) -> Unit)? = null
+) {
+ CompositionLocalProvider(LocalState provides state) {
+ val state = LocalState.current
+ val colors = appearance.fromState(state)
+
+ Box(
+ modifier = modifier
+ .size(shape.size)
+ .background(
+ color = colors.backgroundColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ .border(
+ width = shape.borderStroke,
+ color = colors.borderColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ .clickable {
+ onCheckedChange?.let { it(state != State.Checked) }
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ if (state == State.Checked && icon != null) {
+ Icon(
+ icon = icon,
+ color = colors.checkColor,
+ size = shape.size * Weight.large
+ )
+ } else if (state == State.Checked) {
+ Box(
+ modifier = Modifier
+ .padding(shape.padding)
+ .matchParentSize()
+ .background(
+ color = colors.checkColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/locals/State.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/locals/State.kt
new file mode 100644
index 000000000..f0ac4fc75
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/locals/State.kt
@@ -0,0 +1,6 @@
+package com.atls.hyperion.ui.components.checkbox.locals
+
+import androidx.compose.runtime.staticCompositionLocalOf
+import com.atls.hyperion.ui.components.checkbox.state.State
+
+val LocalState = staticCompositionLocalOf { State.Default }
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/state/State.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/state/State.kt
new file mode 100644
index 000000000..c21be7925
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/state/State.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.components.checkbox.state
+
+enum class State {
+ Default,
+ Checked,
+ Disabled
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/stories/Component.kt
new file mode 100644
index 000000000..53ceb1ba2
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/stories/Component.kt
@@ -0,0 +1,75 @@
+package com.atls.hyperion.ui.components.checkbox.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.checkbox.Checkbox
+import com.atls.hyperion.ui.components.checkbox.state.State
+import com.atls.hyperion.ui.components.checkbox.styles.appearance.CheckboxAppearance
+import com.atls.hyperion.ui.components.checkbox.styles.appearance.blue
+import com.atls.hyperion.ui.components.checkbox.styles.appearance.green
+import com.atls.hyperion.ui.components.checkbox.styles.appearance.red
+import com.atls.hyperion.ui.components.checkbox.styles.shape.CheckboxShape
+import com.atls.hyperion.ui.components.checkbox.styles.shape.large
+import com.atls.hyperion.ui.components.checkbox.styles.shape.medium
+import com.atls.hyperion.ui.components.checkbox.styles.shape.small
+import com.atls.hyperion.ui.primitives.HorizontalSpacer
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+class CheckboxStory : ComponentExample {
+ override val name: String = "Checkbox"
+
+ @Composable
+ override fun Content() {
+ var checked by remember { mutableStateOf(false) }
+
+ var enabled by remember { mutableStateOf(true) }
+
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Space.g12)
+ ) {
+ Text(modifier = Modifier.weight(Weight.full), text = "Enabled")
+ HorizontalSpacer(Space.g12)
+ Switch(checked = enabled, onCheckedChange = { enabled = it })
+ }
+ VerticalSpacer(Space.g12)
+
+ ComponentVariants(
+ name = "Checkbox",
+ appearances = listOf(
+ "Blue" to { CheckboxAppearance.blue() },
+ "Green" to { CheckboxAppearance.green() },
+ "Red" to { CheckboxAppearance.red() }
+ ),
+ shapes = listOf(
+ "Small" to { CheckboxShape.small() },
+ "Medium" to { CheckboxShape.medium() },
+ "Large" to { CheckboxShape.large() }
+ )
+ ) { appearance: CheckboxAppearance, shape: CheckboxShape ->
+ Checkbox(
+ state = if (checked) State.Checked else if (!enabled) State.Disabled else State.Default,
+ appearance = appearance,
+ shape = shape,
+ onCheckedChange = { checked = it }
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/Size.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/Size.kt
new file mode 100644
index 000000000..c16334ee5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/Size.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.checkbox.styles
+
+import androidx.compose.ui.unit.dp
+
+object CheckboxSize {
+ val large = 32.dp
+ val medium = 24.dp
+ val small = 16.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Appearance.kt
new file mode 100644
index 000000000..3155675d9
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Appearance.kt
@@ -0,0 +1,19 @@
+package com.atls.hyperion.ui.components.checkbox.styles.appearance
+
+import com.atls.hyperion.ui.components.checkbox.state.State
+
+data class CheckboxAppearance(
+ val default: Colors,
+ val checked: Colors,
+ val disabled: Colors
+) {
+ fun fromState(state: State): Colors {
+ return when (state) {
+ State.Default -> default
+ State.Checked -> checked
+ State.Disabled -> disabled
+ }
+ }
+
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Colors.kt
new file mode 100644
index 000000000..1c59d8ea2
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Colors.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.checkbox.styles.appearance
+
+import androidx.compose.ui.graphics.Color
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+data class Colors(
+ val backgroundColor: Color,
+ val borderColor: Color = ThemeColors.Palette.transparent,
+ val checkColor: Color
+)
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Variants.kt
new file mode 100644
index 000000000..1b8cca409
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/appearance/Variants.kt
@@ -0,0 +1,60 @@
+package com.atls.hyperion.ui.components.checkbox.styles.appearance
+
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+fun CheckboxAppearance.Companion.blue(): CheckboxAppearance =
+ CheckboxAppearance(
+ default = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.blue,
+ checkColor = ThemeColors.Palette.blue
+ ),
+ checked = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.blue,
+ checkColor = ThemeColors.Palette.blue
+ ),
+ disabled = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.blue,
+ checkColor = ThemeColors.Palette.blue
+ )
+ )
+
+fun CheckboxAppearance.Companion.green(): CheckboxAppearance =
+ CheckboxAppearance(
+ default = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.green,
+ checkColor = ThemeColors.Palette.green
+ ),
+ checked = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.green,
+ checkColor = ThemeColors.Palette.green
+ ),
+ disabled = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.green,
+ checkColor = ThemeColors.Palette.green
+ )
+ )
+
+fun CheckboxAppearance.Companion.red(): CheckboxAppearance =
+ CheckboxAppearance(
+ default = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.red,
+ checkColor = ThemeColors.Palette.red
+ ),
+ checked = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.red,
+ checkColor = ThemeColors.Palette.red
+ ),
+ disabled = Colors(
+ backgroundColor = ThemeColors.Palette.transparent,
+ borderColor = ThemeColors.Palette.red,
+ checkColor = ThemeColors.Palette.red
+ )
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Shape.kt
new file mode 100644
index 000000000..382b03759
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Shape.kt
@@ -0,0 +1,14 @@
+package com.atls.hyperion.ui.components.checkbox.styles.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+data class CheckboxShape(
+ val size: Dp,
+ val cornerRadius: Dp,
+ val borderStroke: Dp,
+ val padding: PaddingValues = PaddingValues(Space.g2)
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Variants.kt
new file mode 100644
index 000000000..9b7490026
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/checkbox/styles/shape/Variants.kt
@@ -0,0 +1,26 @@
+package com.atls.hyperion.ui.components.checkbox.styles.shape
+
+import com.atls.hyperion.ui.components.checkbox.styles.CheckboxSize
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+
+fun CheckboxShape.Companion.small(): CheckboxShape =
+ CheckboxShape(
+ size = CheckboxSize.small,
+ cornerRadius = CornerRadius.xs3,
+ borderStroke = BorderStroke.tiny
+ )
+
+fun CheckboxShape.Companion.medium(): CheckboxShape =
+ CheckboxShape(
+ size = CheckboxSize.medium,
+ cornerRadius = CornerRadius.xs3,
+ borderStroke = BorderStroke.tiny
+ )
+
+fun CheckboxShape.Companion.large(): CheckboxShape =
+ CheckboxShape(
+ size = CheckboxSize.large,
+ cornerRadius = CornerRadius.xs3,
+ borderStroke = BorderStroke.tiny
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/horizontal/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/horizontal/Component.kt
new file mode 100644
index 000000000..1a0574401
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/horizontal/Component.kt
@@ -0,0 +1,22 @@
+package com.atls.hyperion.ui.components.divider.horizontal
+
+import androidx.compose.material.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.divider.style.appearance.DividerAppearance
+import com.atls.hyperion.ui.components.divider.style.appearance.default
+import com.atls.hyperion.ui.components.divider.style.shape.DividerShape
+import com.atls.hyperion.ui.components.divider.style.shape.default
+
+@Composable
+fun HorizontalDivider(
+ modifier: Modifier = Modifier,
+ appearance: DividerAppearance = DividerAppearance.default(),
+ shape: DividerShape = DividerShape.default()
+) {
+ Divider(
+ modifier = modifier,
+ color = appearance.color,
+ thickness = shape.thickness
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/stories/DividerStory.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/stories/DividerStory.kt
new file mode 100644
index 000000000..fec542066
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/stories/DividerStory.kt
@@ -0,0 +1,59 @@
+package com.atls.hyperion.ui.components.divider.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.divider.horizontal.HorizontalDivider
+import com.atls.hyperion.ui.components.divider.style.appearance.DividerAppearance
+import com.atls.hyperion.ui.components.divider.style.appearance.default
+import com.atls.hyperion.ui.components.divider.style.shape.DividerShape
+import com.atls.hyperion.ui.components.divider.style.shape.default
+import com.atls.hyperion.ui.components.divider.vertical.VerticalDivider
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+class DividerStory : ComponentExample {
+ override val name: String = "Divider"
+
+ @Composable
+ override fun Content() {
+ Column(modifier = Modifier.padding(Space.g12)) {
+ ComponentVariants(
+ name = "Horizontal Divider",
+ appearances = listOf(
+ "Default" to { DividerAppearance.default() }
+ ),
+ shapes = listOf(
+ "Default" to { DividerShape.default() }
+ )
+ ) { appearance: DividerAppearance, shape: DividerShape ->
+ HorizontalDivider(
+ appearance = appearance,
+ shape = shape
+ )
+ }
+
+ VerticalSpacer(Space.g24)
+
+ ComponentVariants(
+ name = "Vertical Divider",
+ appearances = listOf(
+ "Default" to { DividerAppearance.default() }
+ ),
+ shapes = listOf(
+ "Default" to { DividerShape.default() }
+ )
+ ) { appearance: DividerAppearance, shape: DividerShape ->
+ VerticalDivider(
+ modifier = Modifier.height(Space.g24),
+ appearance = appearance,
+ shape = shape
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Appearance.kt
new file mode 100644
index 000000000..6269191be
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Appearance.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.divider.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class DividerAppearance(
+ val color: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Variants.kt
new file mode 100644
index 000000000..0f451f5b1
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/appearance/Variants.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.divider.style.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+
+@Composable
+fun DividerAppearance.Companion.default(): DividerAppearance =
+ DividerAppearance(
+ color = Colors.Palette.lightPurple
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/DividerShape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/DividerShape.kt
new file mode 100644
index 000000000..08e246158
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/DividerShape.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.divider.style.shape
+
+import androidx.compose.ui.unit.Dp
+
+data class DividerShape(
+ val thickness: Dp
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/Variants.kt
new file mode 100644
index 000000000..5ab7437d6
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/style/shape/Variants.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.divider.style.shape
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+
+@Composable
+fun DividerShape.Companion.default(): DividerShape =
+ DividerShape(
+ thickness = BorderStroke.tiny
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/vertical/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/vertical/Component.kt
new file mode 100644
index 000000000..21c7932e3
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/divider/vertical/Component.kt
@@ -0,0 +1,26 @@
+package com.atls.hyperion.ui.components.divider.vertical
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.divider.style.appearance.DividerAppearance
+import com.atls.hyperion.ui.components.divider.style.appearance.default
+import com.atls.hyperion.ui.components.divider.style.shape.DividerShape
+import com.atls.hyperion.ui.components.divider.style.shape.default
+
+@Composable
+fun VerticalDivider(
+ modifier: Modifier = Modifier,
+ appearance: DividerAppearance = DividerAppearance.default(),
+ shape: DividerShape = DividerShape.default()
+) {
+ Box(
+ modifier = modifier
+ .fillMaxHeight()
+ .width(shape.thickness)
+ .background(color = appearance.color)
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/Input.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/Input.kt
new file mode 100644
index 000000000..fb0fcaa94
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/Input.kt
@@ -0,0 +1,86 @@
+package com.atls.hyperion.ui.components.input
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.VisualTransformation
+import com.atls.hyperion.ui.components.input.container.InputContainer
+import com.atls.hyperion.ui.components.input.state.InputState
+import com.atls.hyperion.ui.components.input.style.appearance.InputAppearance
+import com.atls.hyperion.ui.components.input.style.shape.InputShape
+import com.atls.hyperion.ui.shared.addon.AddonSlotManager
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+@Composable
+fun Input(
+ modifier: Modifier = Modifier,
+ value: TextFieldValue,
+ onValueChange: (TextFieldValue) -> Unit,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ isError: Boolean = false,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
+ keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+ keyboardActions: KeyboardActions = KeyboardActions.Default,
+ appearance: InputAppearance,
+ shape: InputShape,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ addons: AddonSlotManager = AddonSlotManager(),
+ placeholder: @Composable (() -> Unit)? = null,
+) {
+ val isFocused = interactionSource.collectIsFocusedAsState().value
+ val isPressed = interactionSource.collectIsPressedAsState().value
+
+ val currentState = when {
+ !enabled -> InputState.Disabled
+ isError -> InputState.Error
+ isPressed -> InputState.Active
+ isFocused -> InputState.Focused
+ value.text.isNotEmpty() -> InputState.Filled
+ else -> InputState.Default
+ }
+
+ val colors = appearance.getColorsFromState(currentState)
+
+ InputContainer(
+ modifier = modifier,
+ appearance = appearance,
+ shape = shape,
+ state = currentState,
+ addons = addons
+ ) {
+ BasicTextField(
+ value = value,
+ onValueChange = onValueChange,
+ enabled = enabled,
+ readOnly = readOnly,
+ interactionSource = interactionSource,
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ cursorBrush = SolidColor(colors.cursorColor),
+ textStyle = shape.typography.copy(color = colors.textColor),
+ visualTransformation = visualTransformation,
+ decorationBox = { innerTextField ->
+ if (value.text.isEmpty() && placeholder != null) {
+ placeholder()
+ }
+ innerTextField()
+ },
+ modifier = Modifier
+ .background(ThemeColors.Palette.transparent)
+ .padding(shape.textPaddings)
+ .weight(Weight.full)
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/container/InputContainer.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/container/InputContainer.kt
new file mode 100644
index 000000000..e6de4f7ad
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/container/InputContainer.kt
@@ -0,0 +1,76 @@
+package com.atls.hyperion.ui.components.input.container
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.input.locals.LocalAppearance
+import com.atls.hyperion.ui.components.input.locals.LocalState
+import com.atls.hyperion.ui.components.input.state.InputState
+import com.atls.hyperion.ui.components.input.style.appearance.InputAppearance
+import com.atls.hyperion.ui.components.input.style.appearance.blue
+import com.atls.hyperion.ui.components.input.style.shape.InputShape
+import com.atls.hyperion.ui.components.input.style.shape.normal
+import com.atls.hyperion.ui.shared.addon.AddonPosition
+import com.atls.hyperion.ui.shared.addon.AddonSlotManager
+
+@Composable
+fun InputContainer(
+ modifier: Modifier = Modifier,
+ appearance: InputAppearance = InputAppearance.blue(),
+ shape: InputShape = InputShape.normal(),
+ state: InputState? = null,
+ addons: AddonSlotManager = AddonSlotManager(),
+ content: @Composable RowScope.(interactionSource: MutableInteractionSource) -> Unit
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused = interactionSource.collectIsFocusedAsState().value
+ val isPressed = interactionSource.collectIsPressedAsState().value
+
+ val resolvedState = state ?: when {
+ isPressed -> InputState.Active
+ isFocused -> InputState.Focused
+ else -> InputState.Default
+ }
+
+ val colors = appearance.getColorsFromState(resolvedState)
+
+ CompositionLocalProvider(
+ LocalState provides resolvedState,
+ LocalAppearance provides appearance
+ ) {
+ Row(
+ modifier = modifier
+ .border(
+ width = shape.borderStroke,
+ color = colors.borderColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ .background(colors.backgroundColor, RoundedCornerShape(shape.cornerRadius))
+ .padding(shape.paddings),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ addons.get(AddonPosition.Before).forEach {
+ it.Content()
+ it.Spacer()
+ }
+
+ content(interactionSource)
+
+ addons.get(AddonPosition.After).forEach {
+ it.Spacer()
+ it.Content()
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalAppearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalAppearance.kt
new file mode 100644
index 000000000..3203205af
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalAppearance.kt
@@ -0,0 +1,8 @@
+package com.atls.hyperion.ui.components.input.locals
+
+import androidx.compose.runtime.compositionLocalOf
+import com.atls.hyperion.ui.components.input.style.appearance.InputAppearance
+
+val LocalAppearance = compositionLocalOf {
+ error("InputAppearance not provided")
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalState.kt
new file mode 100644
index 000000000..70e3ac986
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/locals/LocalState.kt
@@ -0,0 +1,8 @@
+package com.atls.hyperion.ui.components.input.locals
+
+import androidx.compose.runtime.compositionLocalOf
+import com.atls.hyperion.ui.components.input.state.InputState
+
+val LocalState = compositionLocalOf {
+ error("InputState not provided")
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/placeholder/InputPlaceholder.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/placeholder/InputPlaceholder.kt
new file mode 100644
index 000000000..9bc572171
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/placeholder/InputPlaceholder.kt
@@ -0,0 +1,24 @@
+package com.atls.hyperion.ui.components.input.placeholder
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.input.style.appearance.InputAppearance
+import com.atls.hyperion.ui.components.input.style.appearance.blue
+import com.atls.hyperion.ui.components.input.style.shape.InputShape
+import com.atls.hyperion.ui.components.input.style.shape.normal
+
+@Composable
+fun InputPlaceholder(
+ modifier: Modifier = Modifier,
+ text: String,
+ appearance: InputAppearance = InputAppearance.blue(),
+ shape: InputShape = InputShape.normal()
+) {
+ Text(
+ modifier = modifier,
+ text = text,
+ color = appearance.default.textColor,
+ style = shape.typography
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/state/InputState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/state/InputState.kt
new file mode 100644
index 000000000..8d53590f6
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/state/InputState.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.input.state
+
+sealed interface InputState {
+ data object Default : InputState
+ data object Focused : InputState
+ data object Filled : InputState
+ data object Disabled : InputState
+ data object Error : InputState
+ data object Active : InputState
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/stories/Component.kt
new file mode 100644
index 000000000..e2ee2c9f0
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/stories/Component.kt
@@ -0,0 +1,94 @@
+package com.atls.hyperion.ui.components.input.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.TextFieldValue
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.input.Input
+import com.atls.hyperion.ui.components.input.placeholder.InputPlaceholder
+import com.atls.hyperion.ui.components.input.style.appearance.InputAppearance
+import com.atls.hyperion.ui.components.input.style.appearance.blue
+import com.atls.hyperion.ui.components.input.style.appearance.white
+import com.atls.hyperion.ui.components.input.style.shape.InputShape
+import com.atls.hyperion.ui.components.input.style.shape.large
+import com.atls.hyperion.ui.components.input.style.shape.normal
+import com.atls.hyperion.ui.primitives.HorizontalSpacer
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+class InputStory : ComponentExample {
+ override val name: String = "Input"
+
+ @Composable
+ override fun Content() {
+ var enabled by remember { mutableStateOf(true) }
+ var isError by remember { mutableStateOf(false) }
+ var textValue by remember { mutableStateOf(TextFieldValue("")) }
+
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Space.g12),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(modifier = Modifier.weight(Weight.full), text = "Enabled")
+ HorizontalSpacer(Space.g12)
+ Switch(checked = enabled, onCheckedChange = { enabled = it })
+ }
+ VerticalSpacer(Space.g8)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Space.g12),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(modifier = Modifier.weight(Weight.full), text = "Error")
+ HorizontalSpacer(Space.g12)
+ Switch(checked = isError, onCheckedChange = { isError = it })
+ }
+ VerticalSpacer(Space.g12)
+
+ ComponentVariants(
+ name = "Input",
+ appearances = listOf(
+ "Blue" to { InputAppearance.blue() },
+ "White" to { InputAppearance.white() }
+ ),
+ shapes = listOf(
+ "Large" to { InputShape.large() },
+ "Normal" to { InputShape.normal() }
+ )
+ ) { appearance: InputAppearance, shape: InputShape ->
+ Input(
+ value = textValue,
+ onValueChange = { textValue = it },
+ appearance = appearance,
+ shape = shape,
+ enabled = enabled,
+ isError = isError,
+ placeholder = {
+ InputPlaceholder(
+ text = "Placeholder",
+ appearance = appearance,
+ shape = shape
+ )
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Appearance.kt
new file mode 100644
index 000000000..155d9c6dd
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Appearance.kt
@@ -0,0 +1,24 @@
+package com.atls.hyperion.ui.components.input.style.appearance
+
+import com.atls.hyperion.ui.components.input.state.InputState
+
+data class InputAppearance(
+ val default: Colors,
+ val filled: Colors,
+ val focused: Colors,
+ val disabled: Colors,
+ val error: Colors,
+ val active: Colors
+) {
+ companion object Companion
+
+ fun getColorsFromState(state: InputState): Colors =
+ when (state) {
+ InputState.Default -> default
+ InputState.Disabled -> disabled
+ InputState.Error -> error
+ InputState.Filled -> filled
+ InputState.Focused -> focused
+ InputState.Active -> active
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Colors.kt
new file mode 100644
index 000000000..9397dd7c5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Colors.kt
@@ -0,0 +1,20 @@
+package com.atls.hyperion.ui.components.input.style.appearance
+
+import androidx.compose.ui.graphics.Color
+import com.atls.hyperion.ui.theme.tokens.colors.ColorSet
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+data class Colors(
+ val backgroundColor: Color,
+ val textColor: Color,
+ val borderColor: Color = ThemeColors.Palette.transparent,
+ val cursorColor: Color = textColor
+) {
+ companion object {
+ fun fromColorSet(colorSet: ColorSet): Colors = Colors(
+ backgroundColor = colorSet.background,
+ textColor = colorSet.font,
+ borderColor = colorSet.border
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Variants.kt
new file mode 100644
index 000000000..32f59a713
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/appearance/Variants.kt
@@ -0,0 +1,26 @@
+package com.atls.hyperion.ui.components.input.style.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as TokenColors
+
+@Composable
+fun InputAppearance.Companion.blue(): InputAppearance =
+ InputAppearance(
+ default = Colors.fromColorSet(TokenColors.Input.Blue.Default),
+ filled = Colors.fromColorSet(TokenColors.Input.Blue.Default),
+ focused = Colors.fromColorSet(TokenColors.Input.Blue.Focus),
+ disabled = Colors.fromColorSet(TokenColors.Input.Blue.Disabled),
+ error = Colors.fromColorSet(TokenColors.Input.Blue.Default),
+ active = Colors.fromColorSet(TokenColors.Input.Blue.Active)
+ )
+
+@Composable
+fun InputAppearance.Companion.white(): InputAppearance =
+ InputAppearance(
+ default = Colors.fromColorSet(TokenColors.Input.White.Default),
+ filled = Colors.fromColorSet(TokenColors.Input.White.Default),
+ focused = Colors.fromColorSet(TokenColors.Input.White.Focus),
+ disabled = Colors.fromColorSet(TokenColors.Input.White.Disabled),
+ error = Colors.fromColorSet(TokenColors.Input.White.Default),
+ active = Colors.fromColorSet(TokenColors.Input.White.Active)
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Shape.kt
new file mode 100644
index 000000000..daab415a8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Shape.kt
@@ -0,0 +1,16 @@
+package com.atls.hyperion.ui.components.input.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+data class InputShape(
+ val cornerRadius: Dp,
+ val borderStroke: Dp,
+ val paddings: PaddingValues,
+ val textPaddings: PaddingValues = PaddingValues(Space.zero),
+ val typography: TextStyle
+) {
+ companion object Companion
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Variants.kt
new file mode 100644
index 000000000..504233cea
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/input/style/shape/Variants.kt
@@ -0,0 +1,38 @@
+package com.atls.hyperion.ui.components.input.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+
+@Composable
+fun InputShape.Companion.large(): InputShape =
+ InputShape(
+ cornerRadius = CornerRadius.md,
+ borderStroke = BorderStroke.tiny,
+ paddings = PaddingValues(
+ vertical = Space.g12,
+ horizontal = Space.g16
+ ),
+ textPaddings = PaddingValues(Space.zero),
+ typography = TextStyle(fontSize = FontSize.md)
+ )
+
+@Composable
+fun InputShape.Companion.normal(): InputShape =
+ InputShape(
+ cornerRadius = CornerRadius.xs3,
+ borderStroke = BorderStroke.tiny,
+ paddings = PaddingValues(
+ vertical = Space.g8,
+ horizontal = Space.g12
+ ),
+ textPaddings = PaddingValues(
+ vertical = Space.g4,
+ horizontal = Space.zero
+ ),
+ typography = TextStyle(fontSize = FontSize.sm)
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/Component.kt
new file mode 100644
index 000000000..f691e5306
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/Component.kt
@@ -0,0 +1,59 @@
+package com.atls.hyperion.ui.components.modal.bottom
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBars
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.SheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.DragHandle
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.appearance.DragHandleAppearance
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.appearance.default
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.shape.DragHandleShape
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.shape.default
+import com.atls.hyperion.ui.components.modal.style.appearance.ModalAppearance
+import com.atls.hyperion.ui.components.modal.style.appearance.default
+import com.atls.hyperion.ui.components.modal.style.shape.ModalShape
+import com.atls.hyperion.ui.components.modal.style.shape.bottom
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BottomDialog(
+ modifier: Modifier = Modifier,
+ appearance: ModalAppearance = ModalAppearance.default(),
+ shape: ModalShape = ModalShape.bottom(),
+ dragHandleShape: DragHandleShape = DragHandleShape.default(),
+ dragHandleAppearance: DragHandleAppearance = DragHandleAppearance.default(),
+ dragHandle: @Composable () -> Unit = {
+ DragHandle(
+ appearance = dragHandleAppearance,
+ shape = dragHandleShape
+ )
+ },
+ sheetState: SheetState,
+ onDismissRequest: () -> Unit,
+ content: @Composable () -> Unit
+) {
+ ModalBottomSheet(
+ modifier = modifier
+ .windowInsetsPadding(WindowInsets.statusBars),
+ onDismissRequest = onDismissRequest,
+ sheetState = sheetState,
+ shape = shape.shape,
+ dragHandle = dragHandle,
+ containerColor = appearance.backgroundColor,
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(shape.paddings)
+ ) {
+ content()
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/Component.kt
new file mode 100644
index 000000000..ecb695f41
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/Component.kt
@@ -0,0 +1,29 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.appearance.DragHandleAppearance
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.shape.DragHandleShape
+
+@Composable
+fun DragHandle(
+ appearance: DragHandleAppearance,
+ shape: DragHandleShape
+) {
+ Box(
+ modifier = Modifier
+ .padding(shape.paddings)
+ .width(shape.width)
+ .height(shape.height)
+ .background(
+ color = appearance.backgroundColor,
+ shape = RoundedCornerShape(shape.cornerRadius)
+ )
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/Size.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/Size.kt
new file mode 100644
index 000000000..a442f8a74
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/Size.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle.style
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+object DragHandleSize {
+ val height: Dp = 6.dp
+ val width: Dp = 79.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Appearance.kt
new file mode 100644
index 000000000..14cea132b
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Appearance.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class DragHandleAppearance(
+ val backgroundColor: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Variants.kt
new file mode 100644
index 000000000..b6b63ace1
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/appearance/Variants.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+
+@Composable
+fun DragHandleAppearance.Companion.default(): DragHandleAppearance =
+ DragHandleAppearance(
+ backgroundColor = Colors.Palette.gray
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Shape.kt
new file mode 100644
index 000000000..78edfd16a
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Shape.kt
@@ -0,0 +1,13 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+
+data class DragHandleShape(
+ val height: Dp,
+ val width: Dp,
+ val cornerRadius: Dp,
+ val paddings: PaddingValues
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Variants.kt
new file mode 100644
index 000000000..ed2d95e21
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/dragHandle/style/shape/Variants.kt
@@ -0,0 +1,16 @@
+package com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.components.modal.bottom.dragHandle.style.DragHandleSize
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+@Composable
+fun DragHandleShape.Companion.default(): DragHandleShape =
+ DragHandleShape(
+ height = DragHandleSize.height,
+ width = DragHandleSize.width,
+ cornerRadius = CornerRadius.xl6,
+ paddings = PaddingValues(top = Space.g8)
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt
new file mode 100644
index 000000000..8ce2156a5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt
@@ -0,0 +1,77 @@
+package com.atls.hyperion.ui.components.modal.bottom.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.ui.components.button.Button
+import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance
+import com.atls.hyperion.ui.components.button.styles.appearance.blue
+import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape
+import com.atls.hyperion.ui.components.button.styles.shape.normal
+import com.atls.hyperion.ui.components.modal.bottom.BottomDialog
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import kotlinx.coroutines.launch
+
+class BottomDialogStory : ComponentExample {
+ override val name: String = "BottomDialog"
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ override fun Content() {
+ var showDialog by remember { mutableStateOf(false) }
+ val sheetState = rememberModalBottomSheetState()
+ val scope = rememberCoroutineScope()
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(Space.g12)
+ ) {
+ Button(
+ text = "Show Bottom Dialog",
+ appearance = ButtonAppearance.blue(),
+ shape = ButtonShape.normal(),
+ onClick = { showDialog = true }
+ )
+
+ if (showDialog) {
+ BottomDialog(
+ sheetState = sheetState,
+ onDismissRequest = { showDialog = false }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(Space.g16)
+ ) {
+ Text(text = "This is a Bottom Dialog")
+ Button(
+ modifier = Modifier.padding(top = Space.g12),
+ text = "Close",
+ appearance = ButtonAppearance.blue(),
+ shape = ButtonShape.normal(),
+ onClick = {
+ scope.launch { sheetState.hide() }.invokeOnCompletion {
+ if (!sheetState.isVisible) {
+ showDialog = false
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/Popup.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/Popup.kt
new file mode 100644
index 000000000..f5f5cd0f8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/Popup.kt
@@ -0,0 +1,43 @@
+package com.atls.hyperion.ui.components.modal.popup
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.atls.hyperion.ui.components.modal.style.appearance.ModalAppearance
+import com.atls.hyperion.ui.components.modal.style.appearance.default
+import com.atls.hyperion.ui.components.modal.style.shape.ModalShape
+import com.atls.hyperion.ui.components.modal.style.shape.popup
+
+@Composable
+fun Popup(
+ modifier: Modifier = Modifier,
+ appearance: ModalAppearance = ModalAppearance.default(),
+ shape: ModalShape = ModalShape.popup(),
+ onDismissRequest: () -> Unit,
+ content: @Composable () -> Unit
+) {
+ Dialog(
+ onDismissRequest = onDismissRequest,
+ properties = DialogProperties(
+ usePlatformDefaultWidth = false
+ )
+ ) {
+ Box(
+ modifier = modifier
+ .padding(shape.spacers)
+ .background(
+ color = appearance.backgroundColor,
+ shape = shape.shape
+ )
+ .fillMaxWidth()
+ .padding(shape.paddings)
+ ) {
+ content()
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt
new file mode 100644
index 000000000..378158877
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt
@@ -0,0 +1,63 @@
+package com.atls.hyperion.ui.components.modal.popup.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.ui.components.button.Button
+import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance
+import com.atls.hyperion.ui.components.button.styles.appearance.blue
+import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape
+import com.atls.hyperion.ui.components.button.styles.shape.normal
+import com.atls.hyperion.ui.components.modal.popup.Popup
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+class PopupStory : ComponentExample {
+ override val name: String = "Popup"
+
+ @Composable
+ override fun Content() {
+ var showDialog by remember { mutableStateOf(false) }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(Space.g12)
+ ) {
+ Button(
+ text = "Show Popup",
+ appearance = ButtonAppearance.blue(),
+ shape = ButtonShape.normal(),
+ onClick = { showDialog = true }
+ )
+
+ if (showDialog) {
+ Popup(
+ onDismissRequest = { showDialog = false }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(Space.g16)
+ ) {
+ Text(text = "This is a Popup Dialog")
+ Button(
+ modifier = Modifier.padding(top = Space.g12),
+ text = "Close",
+ appearance = ButtonAppearance.blue(),
+ shape = ButtonShape.normal(),
+ onClick = { showDialog = false }
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Appearance.kt
new file mode 100644
index 000000000..45aa7714d
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Appearance.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.modal.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class ModalAppearance(
+ val backgroundColor: Color
+) {
+ companion object Companion
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Variants.kt
new file mode 100644
index 000000000..52bafdf81
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/appearance/Variants.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.modal.style.appearance
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+
+@Composable
+fun ModalAppearance.Companion.default(): ModalAppearance =
+ ModalAppearance(
+ backgroundColor = Colors.Palette.white
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Shape.kt
new file mode 100644
index 000000000..5245e4d98
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Shape.kt
@@ -0,0 +1,14 @@
+package com.atls.hyperion.ui.components.modal.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+
+data class ModalShape(
+ val shape: Shape,
+ val shadowElevation: Dp,
+ val paddings: PaddingValues,
+ val spacers: PaddingValues
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Variants.kt
new file mode 100644
index 000000000..dd33044dc
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/style/shape/Variants.kt
@@ -0,0 +1,34 @@
+package com.atls.hyperion.ui.components.modal.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Elevation
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+@Composable
+fun ModalShape.Companion.popup(): ModalShape =
+ ModalShape(
+ shape = RoundedCornerShape(CornerRadius.zero),
+ shadowElevation = Elevation.tiny,
+ paddings = PaddingValues(Space.g20),
+ spacers = PaddingValues(horizontal = Space.g24)
+ )
+
+@Composable
+fun ModalShape.Companion.bottom(): ModalShape =
+ ModalShape(
+ shape = RoundedCornerShape(
+ topStart = CornerRadius.xl4,
+ topEnd = CornerRadius.xl4,
+ ),
+ shadowElevation = Elevation.zero,
+ paddings = PaddingValues(
+ top = Space.g4,
+ bottom = Space.g20,
+ start = Space.g20,
+ end = Space.g20
+ ),
+ spacers = PaddingValues(horizontal = Space.zero)
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/Switch.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/Switch.kt
new file mode 100644
index 000000000..32c7d9df1
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/Switch.kt
@@ -0,0 +1,80 @@
+package com.atls.hyperion.ui.components.switch
+
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import com.atls.hyperion.ui.components.switch.state.SwitchState
+import com.atls.hyperion.ui.components.switch.styles.appearance.SwitchAppearance
+import com.atls.hyperion.ui.components.switch.styles.appearance.default
+import com.atls.hyperion.ui.components.switch.styles.shape.SwitchShape
+import com.atls.hyperion.ui.components.switch.styles.shape.medium
+
+@Composable
+fun Switch(
+ checked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ disabled: Boolean = false,
+ appearance: SwitchAppearance = SwitchAppearance.default(),
+ shape: SwitchShape = SwitchShape.medium(),
+) {
+ val state = remember(checked, disabled) {
+ if (disabled) {
+ SwitchState.Disabled
+ } else {
+ if (checked) SwitchState.Checked else SwitchState.Default
+ }
+ }
+
+ val colors = appearance.fromState(state)
+
+ val thumbOffset by animateDpAsState(
+ targetValue = if (checked) shape.trackWidth - shape.thumbSize - shape.thumbPadding else shape.thumbPadding
+ )
+
+ Box(
+ modifier = modifier
+ .size(width = shape.trackWidth, height = shape.trackHeight)
+ .clip(RoundedCornerShape(shape.trackHeight / 2))
+ .background(colors.trackColor)
+ .border(
+ width = 1.dp,
+ color = colors.trackBorderColor,
+ shape = RoundedCornerShape(shape.trackHeight / 2)
+ )
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ enabled = !disabled
+ ) {
+ onCheckedChange(!checked)
+ },
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Box(
+ modifier = Modifier
+ .offset(x = thumbOffset)
+ .size(shape.thumbSize)
+ .clip(RoundedCornerShape(shape.thumbSize / 2))
+ .background(colors.thumbColor)
+ .border(
+ width = 1.dp,
+ color = colors.thumbBorderColor,
+ shape = RoundedCornerShape(shape.thumbSize / 2)
+ )
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/state/SwitchState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/state/SwitchState.kt
new file mode 100644
index 000000000..659acc8e4
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/state/SwitchState.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.components.switch.state
+
+enum class SwitchState {
+ Default,
+ Checked,
+ Disabled
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/stories/Component.kt
new file mode 100644
index 000000000..5015f5c81
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/stories/Component.kt
@@ -0,0 +1,69 @@
+package com.atls.hyperion.ui.components.switch.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.storybook.shared.ui.ComponentVariants
+import com.atls.hyperion.ui.components.switch.Switch
+import com.atls.hyperion.ui.components.switch.styles.appearance.SwitchAppearance
+import com.atls.hyperion.ui.components.switch.styles.appearance.default
+import com.atls.hyperion.ui.components.switch.styles.shape.SwitchShape
+import com.atls.hyperion.ui.components.switch.styles.shape.medium
+import com.atls.hyperion.ui.primitives.HorizontalSpacer
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+class SwitchStory : ComponentExample {
+ override val name: String = "Switch"
+
+ @Composable
+ override fun Content() {
+ var checked by remember { mutableStateOf(false) }
+ var enabled by remember { mutableStateOf(true) }
+
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Space.g12),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(modifier = Modifier.weight(Weight.full), text = "Enabled")
+ HorizontalSpacer(Space.g12)
+ androidx.compose.material.Switch(
+ checked = enabled,
+ onCheckedChange = { enabled = it }
+ )
+ }
+ VerticalSpacer(Space.g8)
+ ComponentVariants(
+ name = "Switch",
+ appearances = listOf(
+ "Primary" to { SwitchAppearance.default() }
+ ),
+ shapes = listOf(
+ "Medium" to { SwitchShape.medium() }
+ )
+ ) { appearance: SwitchAppearance, shape: SwitchShape ->
+ Switch(
+ checked = checked,
+ disabled = !enabled,
+ appearance = appearance,
+ shape = shape,
+ onCheckedChange = { checked = it }
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/Size.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/Size.kt
new file mode 100644
index 000000000..d0be7b9ec
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/Size.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.components.switch.styles
+
+import androidx.compose.ui.unit.dp
+
+object SwitchSize {
+ val defaultWidth = 44.dp
+ val defaultHeight = 22.dp
+ val defaultTrackSize = 18.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Appearance.kt
new file mode 100644
index 000000000..d2dc4a8d0
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Appearance.kt
@@ -0,0 +1,19 @@
+package com.atls.hyperion.ui.components.switch.styles.appearance
+
+import com.atls.hyperion.ui.components.switch.state.SwitchState
+
+data class SwitchAppearance(
+ val default: Colors,
+ val checked: Colors = default,
+ val disabled: Colors = default
+) {
+ fun fromState(state: SwitchState): Colors {
+ return when (state) {
+ SwitchState.Default -> default
+ SwitchState.Checked -> checked
+ SwitchState.Disabled -> disabled
+ }
+ }
+
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Colors.kt
new file mode 100644
index 000000000..75eac9fac
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Colors.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.components.switch.styles.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class Colors(
+ val trackColor: Color,
+ val trackBorderColor: Color,
+ val thumbColor: Color,
+ val thumbBorderColor: Color,
+)
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Variants.kt
new file mode 100644
index 000000000..b61709f33
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/appearance/Variants.kt
@@ -0,0 +1,19 @@
+package com.atls.hyperion.ui.components.switch.styles.appearance
+
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as ThemeColors
+
+fun SwitchAppearance.Companion.default(): SwitchAppearance =
+ SwitchAppearance(
+ default = Colors(
+ trackColor = ThemeColors.Palette.transparent,
+ trackBorderColor = ThemeColors.Palette.blue,
+ thumbColor = ThemeColors.Palette.blue,
+ thumbBorderColor = ThemeColors.Palette.blue
+ ),
+ disabled = Colors(
+ trackColor = ThemeColors.Palette.transparent,
+ trackBorderColor = ThemeColors.Palette.blue,
+ thumbColor = ThemeColors.Palette.transparent,
+ thumbBorderColor = ThemeColors.Palette.blue
+ )
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Shape.kt
new file mode 100644
index 000000000..dec93f9d9
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Shape.kt
@@ -0,0 +1,12 @@
+package com.atls.hyperion.ui.components.switch.styles.shape
+
+import androidx.compose.ui.unit.Dp
+
+data class SwitchShape(
+ val trackWidth: Dp,
+ val trackHeight: Dp,
+ val thumbSize: Dp,
+ val thumbPadding: Dp
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Variants.kt
new file mode 100644
index 000000000..bda4ed823
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/switch/styles/shape/Variants.kt
@@ -0,0 +1,12 @@
+package com.atls.hyperion.ui.components.switch.styles.shape
+
+import com.atls.hyperion.ui.components.switch.styles.SwitchSize
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+fun SwitchShape.Companion.medium(): SwitchShape =
+ SwitchShape(
+ trackWidth = SwitchSize.defaultWidth,
+ trackHeight = SwitchSize.defaultHeight,
+ thumbSize = SwitchSize.defaultTrackSize,
+ thumbPadding = Space.g2
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/Fragment.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/Fragment.kt
new file mode 100644
index 000000000..6fa36b2df
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/Fragment.kt
@@ -0,0 +1,33 @@
+package com.atls.hyperion.ui.fragment.datepicker
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.components.modal.popup.Popup
+import com.atls.hyperion.ui.fragment.datepicker.model.DateSelection
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.variants.default
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.variants.default
+import com.atls.hyperion.ui.fragment.datepicker.ui.DatePickerContent
+
+@Composable
+fun DatePicker(
+ modifier: Modifier = Modifier,
+ selection: DateSelection,
+ onSelectionChange: (DateSelection) -> Unit,
+ onDismiss: () -> Unit,
+ appearance: DatePickerAppearance = DatePickerAppearance.default(),
+ shape: DatePickerShape = DatePickerShape.default()
+) {
+ Popup(
+ modifier = modifier,
+ onDismissRequest = onDismiss
+ ) {
+ DatePickerContent(
+ selection = selection,
+ onSelectionChange = onSelectionChange,
+ appearance = appearance,
+ shape = shape
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/config/Constants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/config/Constants.kt
new file mode 100644
index 000000000..e86eadb6f
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/config/Constants.kt
@@ -0,0 +1,4 @@
+package com.atls.hyperion.ui.fragment.datepicker.config
+
+internal val DAYS_IN_WEEK = 7
+internal val WEEK_RANGE = 0..6
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextDateRange.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextDateRange.kt
new file mode 100644
index 000000000..cb65aa690
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextDateRange.kt
@@ -0,0 +1,16 @@
+package com.atls.hyperion.ui.fragment.datepicker.lib
+
+import com.atls.hyperion.ui.fragment.datepicker.model.DateSelection
+import kotlinx.datetime.LocalDate
+
+fun DateSelection.Range.next(clicked: LocalDate): DateSelection.Range =
+ when {
+ start == null || end != null ->
+ copy(start = clicked, end = null)
+
+ clicked < start ->
+ copy(start = clicked, end = start)
+
+ else ->
+ copy(end = clicked)
+ }
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextMonth.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextMonth.kt
new file mode 100644
index 000000000..7d054307f
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/NextMonth.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.fragment.datepicker.lib
+
+import kotlinx.datetime.Month
+import kotlinx.datetime.YearMonth
+
+fun YearMonth.next(): YearMonth =
+ if (month == Month.DECEMBER)
+ YearMonth(year + 1, Month.JANUARY)
+ else
+ YearMonth(year, Month.entries[month.ordinal + 1])
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/PreviousMonth.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/PreviousMonth.kt
new file mode 100644
index 000000000..1610c86fb
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/lib/PreviousMonth.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.fragment.datepicker.lib
+
+import kotlinx.datetime.Month
+import kotlinx.datetime.YearMonth
+
+fun YearMonth.previous(): YearMonth =
+ if (month == Month.JANUARY)
+ YearMonth(year - 1, Month.DECEMBER)
+ else
+ YearMonth(year, Month.entries[month.ordinal - 1])
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DateSelection.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DateSelection.kt
new file mode 100644
index 000000000..2996cd71c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DateSelection.kt
@@ -0,0 +1,15 @@
+package com.atls.hyperion.ui.fragment.datepicker.model
+
+import kotlinx.datetime.LocalDate
+
+sealed interface DateSelection {
+
+ data class Single(
+ val date: LocalDate? = null
+ ) : DateSelection
+
+ data class Range(
+ val start: LocalDate? = null,
+ val end: LocalDate? = null
+ ) : DateSelection
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DividerPosition.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DividerPosition.kt
new file mode 100644
index 000000000..a6340af9e
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/model/DividerPosition.kt
@@ -0,0 +1,5 @@
+package com.atls.hyperion.ui.fragment.datepicker.model
+
+enum class DividerPosition {
+ TOP, BOTTOM, NONE
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/stories/Component.kt
new file mode 100644
index 000000000..39b06502e
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/stories/Component.kt
@@ -0,0 +1,69 @@
+package com.atls.hyperion.ui.fragment.datepicker.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.ui.fragment.datepicker.DatePicker
+import com.atls.hyperion.ui.fragment.datepicker.model.DateSelection
+
+class DatePickerStory(
+ override val name: String = "Date picker"
+) : ComponentExample {
+
+ @Composable
+ override fun Content() {
+ var selection by remember {
+ mutableStateOf(DateSelection.Single(null))
+ }
+
+ Column(Modifier.padding(16.dp)) {
+ Text(
+ text = when (val s = selection) {
+ is DateSelection.Single -> "Selected: ${s.date ?: "null"}"
+ else -> "Unexpected state"
+ }
+ )
+
+ DatePicker(
+ selection = selection,
+ onSelectionChange = { selection = it },
+ onDismiss = {}
+ )
+ }
+ }
+}
+
+class DateRangePickerStory(
+ override val name: String = "Date range picker"
+) : ComponentExample {
+
+ @Composable
+ override fun Content() {
+ var selection by remember {
+ mutableStateOf(DateSelection.Range(null, null))
+ }
+
+ Column(Modifier.padding(16.dp)) {
+ Text(
+ text = when (val s = selection) {
+ is DateSelection.Range -> "From: ${s.start ?: "null"} To: ${s.end ?: "null"}"
+ else -> "Unexpected state"
+ }
+ )
+
+ DatePicker(
+ selection = selection,
+ onSelectionChange = { selection = it },
+ onDismiss = {}
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Cell.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Cell.kt
new file mode 100644
index 000000000..bca22879b
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Cell.kt
@@ -0,0 +1,18 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class CellAppearance(
+ val backgroundColor: Color,
+ val textColor: Color,
+ val activeBackgroundColor: Color,
+ val activeTextColor: Color,
+ val inRangeBackgroundColor: Color,
+ val inRangeTextColor: Color,
+ val borderColor: Color = Color.Transparent,
+ val activeBorderColor: Color = Color.Transparent,
+ val inRangeBorderColor: Color = Color.Transparent,
+ val headerTextColor: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Header.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Header.kt
new file mode 100644
index 000000000..f1b5fc284
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Header.kt
@@ -0,0 +1,10 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class HeaderAppearance(
+ val textColor: Color,
+ val arrowColor: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Picker.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Picker.kt
new file mode 100644
index 000000000..a6f0ee657
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/Picker.kt
@@ -0,0 +1,17 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance
+
+import androidx.compose.ui.graphics.Color
+import com.atls.hyperion.ui.components.divider.style.appearance.DividerAppearance
+import com.atls.hyperion.ui.components.modal.style.appearance.ModalAppearance
+
+data class DatePickerAppearance(
+ val cellAppearance: CellAppearance,
+ val headerAppearance: HeaderAppearance,
+ val weekDaysAppearance: WeekDaysAppearance,
+ val backgroundColor: Color,
+ val dividerColor: Color,
+ val dividerAppearance: DividerAppearance,
+ val modalAppearance: ModalAppearance
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/WeekDays.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/WeekDays.kt
new file mode 100644
index 000000000..8f0b78e56
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/WeekDays.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance
+
+import androidx.compose.ui.graphics.Color
+
+data class WeekDaysAppearance(
+ val textColor: Color
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Cell.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Cell.kt
new file mode 100644
index 000000000..0119c7649
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Cell.kt
@@ -0,0 +1,17 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance.variants
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.CellAppearance
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as TokenColors
+
+@Composable
+fun CellAppearance.Companion.default(): CellAppearance =
+ CellAppearance(
+ backgroundColor = TokenColors.Palette.transparent,
+ textColor = TokenColors.Text.black,
+ activeBackgroundColor = TokenColors.Palette.blue,
+ activeTextColor = TokenColors.Text.white,
+ inRangeBackgroundColor = TokenColors.Palette.lightPurple,
+ inRangeTextColor = TokenColors.Text.black,
+ headerTextColor = TokenColors.Text.gray
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Header.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Header.kt
new file mode 100644
index 000000000..28afb20bc
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Header.kt
@@ -0,0 +1,12 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance.variants
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.HeaderAppearance
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as TokenColors
+
+@Composable
+fun HeaderAppearance.Companion.default(): HeaderAppearance =
+ HeaderAppearance(
+ textColor = TokenColors.Text.black,
+ arrowColor = TokenColors.Text.black
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Picker.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Picker.kt
new file mode 100644
index 000000000..12282c766
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/Picker.kt
@@ -0,0 +1,24 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance.variants
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.components.divider.style.appearance.DividerAppearance
+import com.atls.hyperion.ui.components.divider.style.appearance.default
+import com.atls.hyperion.ui.components.modal.style.appearance.ModalAppearance
+import com.atls.hyperion.ui.components.modal.style.appearance.default
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.CellAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.HeaderAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.WeekDaysAppearance
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as TokenColors
+
+@Composable
+fun DatePickerAppearance.Companion.default(): DatePickerAppearance =
+ DatePickerAppearance(
+ cellAppearance = CellAppearance.default(),
+ headerAppearance = HeaderAppearance.default(),
+ weekDaysAppearance = WeekDaysAppearance.default(),
+ backgroundColor = TokenColors.Palette.white,
+ dividerColor = TokenColors.Palette.gray,
+ dividerAppearance = DividerAppearance.default(),
+ modalAppearance = ModalAppearance.default()
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/WeekDays.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/WeekDays.kt
new file mode 100644
index 000000000..61a3199c8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/appearance/variants/WeekDays.kt
@@ -0,0 +1,11 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.appearance.variants
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.WeekDaysAppearance
+import com.atls.hyperion.ui.theme.tokens.colors.Colors as TokenColors
+
+@Composable
+fun WeekDaysAppearance.Companion.default(): WeekDaysAppearance =
+ WeekDaysAppearance(
+ textColor = TokenColors.Text.black
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Calendar.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Calendar.kt
new file mode 100644
index 000000000..5644885c7
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Calendar.kt
@@ -0,0 +1,15 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape
+
+import androidx.compose.foundation.layout.PaddingValues
+import com.atls.hyperion.ui.components.divider.style.shape.DividerShape
+import com.atls.hyperion.ui.components.modal.style.shape.ModalShape
+import com.atls.hyperion.ui.fragment.datepicker.model.DividerPosition
+
+data class CalendarShape(
+ val padding: PaddingValues,
+ val divider: DividerPosition = DividerPosition.NONE,
+ val dividerShape: DividerShape,
+ val modalShape: ModalShape
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Cell.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Cell.kt
new file mode 100644
index 000000000..1ff8bd822
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Cell.kt
@@ -0,0 +1,22 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.theme.tokens.layout.BorderStroke
+
+data class CellShape(
+ val spacing: Dp,
+ val padding: Dp,
+ val borderWidth: Dp = BorderStroke.none,
+ val borderColor: Color = Color.Transparent,
+ val shape: Shape,
+ val activeShape: Shape = shape,
+ val rangeShape: Shape = RectangleShape,
+ val typography: TextStyle,
+ val headerTypography: TextStyle
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Header.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Header.kt
new file mode 100644
index 000000000..8f3d4ad94
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Header.kt
@@ -0,0 +1,13 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape
+
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+
+data class HeaderShape(
+ val typography: TextStyle,
+ val spacing: Dp,
+ val iconSize: Dp,
+ val horizontalPadding: Dp
+) {
+ companion object
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Shape.kt
new file mode 100644
index 000000000..b552e3a92
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/Shape.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape
+
+data class DatePickerShape(
+ val cellShape: CellShape,
+ val headerShape: HeaderShape,
+ val calendarShape: CalendarShape
+) {
+ companion object Companion
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Calendar.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Calendar.kt
new file mode 100644
index 000000000..9c0f0fc14
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Calendar.kt
@@ -0,0 +1,20 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape.variants
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.components.divider.style.shape.DividerShape
+import com.atls.hyperion.ui.components.divider.style.shape.default
+import com.atls.hyperion.ui.components.modal.style.shape.ModalShape
+import com.atls.hyperion.ui.components.modal.style.shape.popup
+import com.atls.hyperion.ui.fragment.datepicker.model.DividerPosition
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.CalendarShape
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+
+@Composable
+fun CalendarShape.Companion.default(): CalendarShape =
+ CalendarShape(
+ padding = PaddingValues(Space.g12),
+ divider = DividerPosition.BOTTOM,
+ dividerShape = DividerShape.default(),
+ modalShape = ModalShape.popup()
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Cell.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Cell.kt
new file mode 100644
index 000000000..9488b92a8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Cell.kt
@@ -0,0 +1,26 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape.variants
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.CellShape
+import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+import com.atls.hyperion.ui.theme.typography.FontWeight
+
+@Composable
+fun CellShape.Companion.default(): CellShape =
+ CellShape(
+ spacing = Space.zero,
+ padding = Space.g8,
+ shape = RoundedCornerShape(CornerRadius.xs2),
+ typography = TextStyle(
+ fontSize = FontSize.xs,
+ fontWeight = FontWeight.regular,
+ ),
+ headerTypography = TextStyle(
+ fontSize = FontSize.xs2,
+ fontWeight = FontWeight.medium,
+ )
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Header.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Header.kt
new file mode 100644
index 000000000..f95c7a479
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Header.kt
@@ -0,0 +1,21 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape.variants
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.HeaderShape
+import com.atls.hyperion.ui.primitives.icon.IconSize
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+import com.atls.hyperion.ui.theme.typography.FontWeight
+
+@Composable
+fun HeaderShape.Companion.default(): HeaderShape =
+ HeaderShape(
+ typography = TextStyle(
+ fontSize = FontSize.md,
+ fontWeight = FontWeight.bold,
+ ),
+ spacing = Space.g12,
+ iconSize = IconSize.medium,
+ horizontalPadding = Space.g12
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Picker.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Picker.kt
new file mode 100644
index 000000000..5746a0e12
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/style/shape/variants/Picker.kt
@@ -0,0 +1,15 @@
+package com.atls.hyperion.ui.fragment.datepicker.style.shape.variants
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.CalendarShape
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.CellShape
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.HeaderShape
+
+@Composable
+fun DatePickerShape.Companion.default(): DatePickerShape =
+ DatePickerShape(
+ cellShape = CellShape.default(),
+ headerShape = HeaderShape.default(),
+ calendarShape = CalendarShape.default()
+ )
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/CalendarHeader.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/CalendarHeader.kt
new file mode 100644
index 000000000..1d82b3a63
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/CalendarHeader.kt
@@ -0,0 +1,79 @@
+package com.atls.hyperion.ui.fragment.datepicker.ui
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import com.atls.hyperion.ui.components.divider.horizontal.HorizontalDivider
+import com.atls.hyperion.ui.fragment.datepicker.model.DividerPosition
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.generated.resources.Res
+import com.atls.hyperion.ui.generated.resources.chevron_left
+import com.atls.hyperion.ui.generated.resources.chevron_right
+import com.atls.hyperion.ui.primitives.Text
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.primitives.icon.Icon
+import org.jetbrains.compose.resources.painterResource
+
+@Composable
+fun CalendarHeader(
+ monthName: String,
+ appearance: DatePickerAppearance,
+ shape: DatePickerShape,
+ beforeIcon: Painter = painterResource(Res.drawable.chevron_left),
+ afterIcon: Painter = painterResource(Res.drawable.chevron_right),
+ arrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
+ alignment: Alignment.Vertical = Alignment.CenterVertically,
+ onPrevMonth: () -> Unit,
+ onNextMonth: () -> Unit,
+) {
+ if (shape.calendarShape.divider == DividerPosition.TOP) {
+ HorizontalDivider(
+ appearance = appearance.dividerAppearance,
+ shape = shape.calendarShape.dividerShape
+ )
+ VerticalSpacer(shape.headerShape.spacing)
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = shape.headerShape.horizontalPadding),
+ horizontalArrangement = arrangement,
+ verticalAlignment = alignment
+ ) {
+ Icon(
+ icon = beforeIcon,
+ size = shape.headerShape.iconSize,
+ modifier = Modifier.clickable { onPrevMonth() },
+ color = appearance.headerAppearance.arrowColor
+ )
+
+ Text(
+ text = monthName,
+ typography = shape.headerShape.typography,
+ color = appearance.headerAppearance.textColor
+ )
+
+ Icon(
+ icon = afterIcon,
+ size = shape.headerShape.iconSize,
+ modifier = Modifier.clickable { onNextMonth() },
+ color = appearance.headerAppearance.arrowColor
+ )
+ }
+
+ if (shape.calendarShape.divider == DividerPosition.BOTTOM) {
+ VerticalSpacer(shape.headerShape.spacing)
+ HorizontalDivider(
+ appearance = appearance.dividerAppearance,
+ shape = shape.calendarShape.dividerShape
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/DatePickerContent.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/DatePickerContent.kt
new file mode 100644
index 000000000..3e010b20c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/DatePickerContent.kt
@@ -0,0 +1,149 @@
+package com.atls.hyperion.ui.fragment.datepicker.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.fragment.datepicker.config.DAYS_IN_WEEK
+import com.atls.hyperion.ui.fragment.datepicker.config.WEEK_RANGE
+import com.atls.hyperion.ui.fragment.datepicker.lib.next
+import com.atls.hyperion.ui.fragment.datepicker.lib.previous
+import com.atls.hyperion.ui.fragment.datepicker.model.DateSelection
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.primitives.layout.column.Column
+import com.kizitonwose.calendar.compose.HorizontalCalendar
+import com.kizitonwose.calendar.compose.rememberCalendarState
+import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
+import kotlinx.coroutines.launch
+import kotlinx.datetime.DayOfWeek
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.YearMonth
+import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+
+@OptIn(ExperimentalTime::class)
+@Composable
+fun DatePickerContent(
+ modifier: Modifier = Modifier,
+ selection: DateSelection,
+ onSelectionChange: (DateSelection) -> Unit,
+ appearance: DatePickerAppearance,
+ shape: DatePickerShape,
+) {
+ val today = remember {
+ Clock.System.now()
+ .toLocalDateTime(TimeZone.currentSystemDefault())
+ .date
+ }
+
+ val currentMonth = remember { YearMonth(today.year, today.month) }
+ val startMonth = remember { YearMonth(1, today.month) }
+ val endMonth = remember { YearMonth(2200, today.month) }
+ val firstDayOfWeek = remember { firstDayOfWeekFromLocale() }
+
+ val state = rememberCalendarState(
+ startMonth = startMonth,
+ endMonth = endMonth,
+ firstVisibleMonth = currentMonth,
+ firstDayOfWeek = firstDayOfWeek
+ )
+
+ val coroutineScope = rememberCoroutineScope()
+
+ Column(modifier = modifier.background(appearance.backgroundColor)) {
+
+ val visibleMonth = state.firstVisibleMonth.yearMonth
+ val monthName =
+ visibleMonth.month.name.lowercase().replaceFirstChar { it.uppercase() }
+
+ CalendarHeader(
+ monthName = "$monthName ${visibleMonth.year}",
+ appearance = appearance,
+ shape = shape,
+ onPrevMonth = {
+ coroutineScope.launch {
+ state.animateScrollToMonth(visibleMonth.previous())
+ }
+ },
+ onNextMonth = {
+ coroutineScope.launch {
+ state.animateScrollToMonth(visibleMonth.next())
+ }
+ }
+ )
+
+ HorizontalCalendar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(shape.calendarShape.padding),
+
+ state = state,
+ dayContent = { day ->
+ val date = day.date
+
+ val isSelected = when (selection) {
+ is DateSelection.Single -> selection.date == date
+ is DateSelection.Range -> false
+ }
+
+ val isRangeStart = when (selection) {
+ is DateSelection.Range -> selection.start == date
+ else -> false
+ }
+
+ val isRangeEnd = when (selection) {
+ is DateSelection.Range -> selection.end == date
+ else -> false
+ }
+
+ val isInRange = when (selection) {
+ is DateSelection.Range ->
+ selection.start != null &&
+ selection.end != null &&
+ date > selection.start &&
+ date < selection.end
+
+ else -> false
+ }
+
+ Day(
+ day = day,
+ isSelected = isSelected,
+ isInRange = isInRange,
+ isRangeStart = isRangeStart,
+ isRangeEnd = isRangeEnd,
+ appearance = appearance,
+ shape = shape,
+ onClick = { clicked ->
+ onSelectionChange(
+ when (selection) {
+ is DateSelection.Single ->
+ selection.copy(date = clicked)
+
+ is DateSelection.Range ->
+ selection.next(clicked)
+ }
+ )
+ }
+ )
+ },
+
+ monthHeader = {
+ WeekDays(
+ weekDays = state.firstDayOfWeek.let { first ->
+ WEEK_RANGE.map {
+ DayOfWeek.entries[(first.ordinal + it) % DAYS_IN_WEEK]
+ }
+ },
+ appearance = appearance,
+ shape = shape
+ )
+ }
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/Day.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/Day.kt
new file mode 100644
index 000000000..72a8237cd
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/Day.kt
@@ -0,0 +1,99 @@
+package com.atls.hyperion.ui.fragment.datepicker.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.primitives.Text
+import com.atls.hyperion.ui.shared.layout.aspectSquare
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+import com.kizitonwose.calendar.core.CalendarDay
+import com.kizitonwose.calendar.core.DayPosition
+import kotlinx.datetime.LocalDate
+
+@Composable
+fun Day(
+ day: CalendarDay,
+ isSelected: Boolean,
+ isInRange: Boolean,
+ isRangeStart: Boolean,
+ isRangeEnd: Boolean,
+ appearance: DatePickerAppearance,
+ shape: DatePickerShape,
+ onClick: (LocalDate) -> Unit,
+) {
+ val isCurrentMonth = day.position == DayPosition.MonthDate
+ if (!isCurrentMonth) {
+ Box(modifier = Modifier.aspectSquare())
+ return
+ }
+
+ val date = LocalDate(day.date.year, day.date.monthNumber, day.date.dayOfMonth)
+
+ val backgroundColor = when {
+ isSelected || isRangeStart || isRangeEnd -> appearance.cellAppearance.activeBackgroundColor
+ isInRange -> appearance.cellAppearance.inRangeBackgroundColor
+ else -> appearance.cellAppearance.backgroundColor
+ }
+
+ val cellShape = when {
+ isSelected -> shape.cellShape.activeShape
+ isInRange -> shape.cellShape.rangeShape
+ else -> shape.cellShape.shape
+ }
+
+ val textColor = when {
+ isSelected || isRangeStart || isRangeEnd -> appearance.cellAppearance.activeTextColor
+ isInRange -> appearance.cellAppearance.inRangeTextColor
+ else -> appearance.cellAppearance.textColor
+ }
+
+
+ val containerModifier = Modifier
+ .aspectSquare()
+ .padding(shape.cellShape.spacing)
+ .clip(cellShape)
+ .background(backgroundColor)
+ .padding(shape.cellShape.padding)
+ .then(
+ if ((isSelected || isRangeStart || isRangeEnd) && appearance.cellAppearance.activeBorderColor != Colors.Palette.transparent)
+ Modifier.border(
+ shape.cellShape.borderWidth,
+ appearance.cellAppearance.activeBorderColor,
+ cellShape
+ )
+ else if (isInRange && appearance.cellAppearance.inRangeBorderColor != Colors.Palette.transparent)
+ Modifier.border(
+ shape.cellShape.borderWidth,
+ appearance.cellAppearance.inRangeBorderColor,
+ cellShape
+ )
+ else if (appearance.cellAppearance.borderColor != Colors.Palette.transparent)
+ Modifier.border(
+ shape.cellShape.borderWidth,
+ appearance.cellAppearance.borderColor,
+ cellShape
+ )
+ else Modifier
+ )
+ .clickable { onClick(date) }
+
+
+ Box(
+ modifier = containerModifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = day.date.dayOfMonth.toString(),
+ color = textColor,
+ typography = shape.cellShape.typography
+ )
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/WeekDays.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/WeekDays.kt
new file mode 100644
index 000000000..d4a355a18
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/fragment/datepicker/ui/WeekDays.kt
@@ -0,0 +1,39 @@
+package com.atls.hyperion.ui.fragment.datepicker.ui
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.atls.hyperion.ui.fragment.datepicker.config.DAYS_IN_WEEK
+import com.atls.hyperion.ui.fragment.datepicker.style.appearance.DatePickerAppearance
+import com.atls.hyperion.ui.fragment.datepicker.style.shape.DatePickerShape
+import com.atls.hyperion.ui.primitives.Text
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+import kotlinx.datetime.DayOfWeek
+import kotlinx.datetime.format.DayOfWeekNames
+
+@Composable
+fun WeekDays(
+ weekDays: List,
+ appearance: DatePickerAppearance,
+ shape: DatePickerShape,
+ dayNames: List = DayOfWeekNames.ENGLISH_ABBREVIATED.names
+) {
+ require(dayNames.size == DAYS_IN_WEEK) { "dayNames must contain exactly 7 elements (Mon → Sun)" }
+
+ Row(modifier = Modifier.fillMaxWidth()) {
+ weekDays.forEach { dayOfWeek ->
+ val nameIndex = (dayOfWeek.ordinal - weekDays.first().ordinal + DAYS_IN_WEEK) % DAYS_IN_WEEK
+ val shortName = dayNames[nameIndex]
+
+ Text(
+ modifier = Modifier.weight(Weight.full),
+ textAlign = TextAlign.Center,
+ text = shortName,
+ typography = shape.cellShape.headerTypography,
+ color = appearance.weekDaysAppearance.textColor,
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Link.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Link.kt
new file mode 100644
index 000000000..6464ae359
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Link.kt
@@ -0,0 +1,45 @@
+package com.atls.hyperion.ui.primitives
+
+import androidx.compose.foundation.clickable
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+
+@Composable
+fun Link(
+ modifier: Modifier = Modifier,
+ textDecoration: String,
+ url: String,
+ color: Color,
+ typography: TextStyle
+) {
+ val uriHandler = LocalUriHandler.current
+
+ Text(
+ modifier = modifier.clickable { uriHandler.openUri(url) },
+ text = textDecoration,
+ color = color,
+ typography = typography.copy(color = color)
+ )
+}
+
+@Composable
+fun Link(
+ modifier: Modifier = Modifier,
+ textDecoration: AnnotatedString,
+ url: String,
+ color: Color,
+ typography: TextStyle
+) {
+ val uriHandler = LocalUriHandler.current
+
+ Text(
+ modifier = modifier.clickable { uriHandler.openUri(url) },
+ text = textDecoration,
+ color = color,
+ typography = typography.copy(color = color)
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Spacer.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Spacer.kt
new file mode 100644
index 000000000..5076d045f
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Spacer.kt
@@ -0,0 +1,18 @@
+package com.atls.hyperion.ui.primitives
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun VerticalSpacer(height: Dp) {
+ Spacer(Modifier.height(height))
+}
+
+@Composable
+fun HorizontalSpacer(width: Dp) {
+ Spacer(Modifier.width(width))
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Text.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Text.kt
new file mode 100644
index 000000000..67aaece8b
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Text.kt
@@ -0,0 +1,52 @@
+package com.atls.hyperion.ui.primitives
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+
+@Composable
+fun Text(
+ modifier: Modifier = Modifier,
+ text: String,
+ color: Color,
+ typography: TextStyle,
+ maxLines: Int = Int.MAX_VALUE,
+ overflow: TextOverflow = TextOverflow.Ellipsis,
+ textAlign: TextAlign? = null
+) {
+ Text(
+ modifier = modifier,
+ text = text,
+ color = color,
+ style = typography,
+ overflow = overflow,
+ maxLines = maxLines,
+ textAlign = textAlign
+ )
+}
+
+@Composable
+fun Text(
+ modifier: Modifier = Modifier,
+ text: AnnotatedString,
+ color: Color,
+ typography: TextStyle,
+ maxLines: Int = Int.MAX_VALUE,
+ overflow: TextOverflow = TextOverflow.Ellipsis,
+ textAlign: TextAlign? = null
+) {
+ Text(
+ modifier = modifier,
+ text = text,
+ color = color,
+ style = typography,
+ overflow = overflow,
+ maxLines = maxLines,
+ textAlign = textAlign
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Primitive.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Primitive.kt
new file mode 100644
index 000000000..2f9897700
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Primitive.kt
@@ -0,0 +1,24 @@
+package com.atls.hyperion.ui.primitives.icon
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun Icon(
+ modifier: Modifier = Modifier,
+ icon: Painter,
+ color: Color = Color.Unspecified,
+ size: Dp = Dp.Unspecified
+) {
+ Icon(
+ painter = icon,
+ contentDescription = null,
+ tint = color,
+ modifier = modifier.size(size = size)
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Size.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Size.kt
new file mode 100644
index 000000000..8e6faec47
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/icon/Size.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.primitives.icon
+
+import androidx.compose.ui.unit.dp
+
+object IconSize {
+ val medium = 24.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Primitive.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Primitive.kt
new file mode 100644
index 000000000..5d95b53db
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Primitive.kt
@@ -0,0 +1,55 @@
+package com.atls.hyperion.ui.primitives.image
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.layout.ContentScale
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.painterResource
+
+@Composable
+fun Image(
+ modifier: Modifier = Modifier,
+ image: DrawableResource,
+ contentScale: ContentScale = ContentScale.Fit,
+ contentDescription: String? = null
+) {
+ Image(
+ painter = painterResource(image),
+ contentDescription = contentDescription,
+ contentScale = contentScale,
+ modifier = modifier
+ )
+}
+
+@Composable
+fun Image(
+ modifier: Modifier = Modifier,
+ image: ImageVector,
+ contentScale: ContentScale = ContentScale.Fit,
+ contentDescription: String? = null
+) {
+ Image(
+ imageVector = image,
+ contentDescription = contentDescription,
+ contentScale = contentScale,
+ modifier = modifier
+ )
+}
+
+@Composable
+fun Image(
+ modifier: Modifier = Modifier,
+ image: Painter,
+ contentScale: ContentScale = ContentScale.Fit,
+ contentDescription: String? = null
+) {
+ Image(
+ painter = image,
+ contentDescription = contentDescription,
+ contentScale = contentScale,
+ modifier = modifier
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Size.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Size.kt
new file mode 100644
index 000000000..46ce977a8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/image/Size.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.primitives.image
+
+import androidx.compose.ui.unit.dp
+
+object ImageSize {
+ val huge = 58.dp
+ val large = 48.dp
+ val medium = 40.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Lazy.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Lazy.kt
new file mode 100644
index 000000000..e5ac8dd0c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Lazy.kt
@@ -0,0 +1,39 @@
+package com.atls.hyperion.ui.primitives.layout.column
+
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import androidx.compose.foundation.lazy.LazyColumn as ComposeColumn
+
+@Composable
+fun LazyColumn(
+ modifier: Modifier = Modifier,
+ state: LazyListState = rememberLazyListState(),
+ contentPadding: PaddingValues = PaddingValues(Space.zero),
+ reverseLayout: Boolean = false,
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+ flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+ userScrollEnabled: Boolean = true,
+ content: LazyListScope.() -> Unit
+) {
+ ComposeColumn(
+ modifier = modifier,
+ state = state,
+ contentPadding = contentPadding,
+ reverseLayout = reverseLayout,
+ verticalArrangement = verticalArrangement,
+ horizontalAlignment = horizontalAlignment,
+ flingBehavior = flingBehavior,
+ userScrollEnabled = userScrollEnabled,
+ content = content
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Static.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Static.kt
new file mode 100644
index 000000000..8a76a92d6
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/column/Static.kt
@@ -0,0 +1,23 @@
+package com.atls.hyperion.ui.primitives.layout.column
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.foundation.layout.Column as ComposeColumn
+
+@Composable
+fun Column(
+ modifier: Modifier = Modifier,
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+ content: @Composable ColumnScope.() -> Unit
+) {
+ ComposeColumn(
+ modifier = modifier,
+ verticalArrangement = verticalArrangement,
+ horizontalAlignment = horizontalAlignment,
+ content = content
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Lazy.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Lazy.kt
new file mode 100644
index 000000000..aa828d3b0
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Lazy.kt
@@ -0,0 +1,42 @@
+package com.atls.hyperion.ui.primitives.layout.grid
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun LazyGrid(
+ columns: Int,
+ orientation: GridOrientation,
+ modifier: Modifier = Modifier,
+ horizontalSpacing: Dp,
+ verticalSpacing: Dp,
+ content: LazyGridScope.() -> Unit
+) {
+ when (orientation) {
+ GridOrientation.Vertical -> {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(columns),
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
+ verticalArrangement = Arrangement.spacedBy(verticalSpacing),
+ content = content
+ )
+ }
+
+ GridOrientation.Horizontal -> {
+ LazyHorizontalGrid(
+ rows = GridCells.Fixed(columns),
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
+ verticalArrangement = Arrangement.spacedBy(verticalSpacing),
+ content = content
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Orientation.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Orientation.kt
new file mode 100644
index 000000000..78e05afb1
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Orientation.kt
@@ -0,0 +1,6 @@
+package com.atls.hyperion.ui.primitives.layout.grid
+
+enum class GridOrientation {
+ Vertical,
+ Horizontal
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Static.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Static.kt
new file mode 100644
index 000000000..4d2558604
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/grid/Static.kt
@@ -0,0 +1,70 @@
+package com.atls.hyperion.ui.primitives.layout.grid
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.layout.Weight
+
+@Composable
+fun Grid(
+ items: List<@Composable () -> Unit>,
+ columns: Int,
+ orientation: GridOrientation,
+ modifier: Modifier = Modifier,
+ horizontalSpacing: Dp,
+ verticalSpacing: Dp
+) {
+ when (orientation) {
+ GridOrientation.Vertical -> {
+ Column(modifier = modifier) {
+ items.chunked(columns).forEach { rowItems ->
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ rowItems.forEach { item ->
+ Box(modifier = Modifier.weight(Weight.full)) {
+ item()
+ }
+ }
+
+ if (rowItems.size < columns) {
+ repeat(columns - rowItems.size) {
+ Spacer(modifier = Modifier.weight(Weight.full))
+ }
+ }
+ }
+ VerticalSpacer(verticalSpacing)
+ }
+ }
+ }
+
+ GridOrientation.Horizontal -> {
+ Row(modifier = modifier) {
+ items.chunked(columns).forEach { columnItems ->
+ Column(
+ verticalArrangement = Arrangement.spacedBy(verticalSpacing),
+ modifier = Modifier.weight(Weight.full)
+ ) {
+ columnItems.forEach { item ->
+ item()
+ }
+
+ if (columnItems.size < columns) {
+ repeat(columns - columnItems.size) {
+ Spacer(modifier = Modifier.weight(Weight.full))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Lazy.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Lazy.kt
new file mode 100644
index 000000000..dea948061
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Lazy.kt
@@ -0,0 +1,39 @@
+package com.atls.hyperion.ui.primitives.layout.row
+
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import androidx.compose.foundation.lazy.LazyRow as ComposeRow
+
+@Composable
+fun LazyRow(
+ modifier: Modifier = Modifier,
+ state: LazyListState = rememberLazyListState(),
+ contentPadding: PaddingValues = PaddingValues(Space.zero),
+ reverseLayout: Boolean = false,
+ horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+ verticalAlignment: Alignment.Vertical = Alignment.Top,
+ flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+ userScrollEnabled: Boolean = true,
+ content: LazyListScope.() -> Unit
+) {
+ ComposeRow(
+ modifier = modifier,
+ state = state,
+ contentPadding = contentPadding,
+ reverseLayout = reverseLayout,
+ horizontalArrangement = horizontalArrangement,
+ verticalAlignment = verticalAlignment,
+ flingBehavior = flingBehavior,
+ userScrollEnabled = userScrollEnabled,
+ content = content
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Static.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Static.kt
new file mode 100644
index 000000000..a7cf0542c
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/layout/row/Static.kt
@@ -0,0 +1,23 @@
+package com.atls.hyperion.ui.primitives.layout.row
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.foundation.layout.Row as ComposeRow
+
+@Composable
+fun Row(
+ modifier: Modifier = Modifier,
+ horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+ verticalAlignment: Alignment.Vertical = Alignment.Top,
+ content: @Composable RowScope.() -> Unit
+) {
+ ComposeRow(
+ modifier = modifier,
+ horizontalArrangement = horizontalArrangement,
+ verticalAlignment = verticalAlignment,
+ content = content
+ )
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Link.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Link.kt
new file mode 100644
index 000000000..76a9a9bf0
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Link.kt
@@ -0,0 +1,55 @@
+package com.atls.hyperion.ui.primitives.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.ui.primitives.Link
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+import com.atls.hyperion.ui.theme.typography.LineHeights
+
+class LinkStory : ComponentExample {
+ override val name: String = "Link"
+
+ @Composable
+ override fun Content() {
+ Column(
+ modifier = Modifier.padding(Space.g16)
+ ) {
+ Link(
+ textDecoration = "Standard Blue Link",
+ url = "https://google.com",
+ color = Colors.Text.blue,
+ typography = TextStyle(
+ fontSize = FontSize.md,
+ lineHeight = LineHeights.md
+ )
+ )
+ VerticalSpacer(Space.g12)
+ Link(
+ textDecoration = "Soft Blue Link",
+ url = "https://google.com",
+ color = Colors.Text.softBlue,
+ typography = TextStyle(
+ fontSize = FontSize.md,
+ lineHeight = LineHeights.md
+ )
+ )
+ VerticalSpacer(Space.g12)
+ Link(
+ textDecoration = "Small Gray Link",
+ url = "https://google.com",
+ color = Colors.Text.gray,
+ typography = TextStyle(
+ fontSize = FontSize.xs,
+ lineHeight = LineHeights.xs
+ )
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Text.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Text.kt
new file mode 100644
index 000000000..f3e878db8
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/stories/Text.kt
@@ -0,0 +1,61 @@
+package com.atls.hyperion.ui.primitives.stories
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import com.atls.hyperion.storybook.shared.model.ComponentExample
+import com.atls.hyperion.ui.primitives.Text
+import com.atls.hyperion.ui.primitives.VerticalSpacer
+import com.atls.hyperion.ui.theme.tokens.colors.Colors
+import com.atls.hyperion.ui.theme.tokens.layout.Space
+import com.atls.hyperion.ui.theme.typography.FontSize
+import com.atls.hyperion.ui.theme.typography.LineHeights
+
+class TextStory : ComponentExample {
+ override val name: String = "Text"
+
+ @Composable
+ override fun Content() {
+ Column(
+ modifier = Modifier.padding(Space.g16)
+ ) {
+ Text(
+ text = "Heading XL",
+ color = Colors.Text.black,
+ typography = TextStyle(
+ fontSize = FontSize.xl4,
+ lineHeight = LineHeights.xl4
+ )
+ )
+ VerticalSpacer(Space.g12)
+ Text(
+ text = "Body Medium",
+ color = Colors.Text.almostBlack,
+ typography = TextStyle(
+ fontSize = FontSize.md,
+ lineHeight = LineHeights.md
+ )
+ )
+ VerticalSpacer(Space.g12)
+ Text(
+ text = "Small Caption",
+ color = Colors.Text.gray,
+ typography = TextStyle(
+ fontSize = FontSize.xs,
+ lineHeight = LineHeights.xs
+ )
+ )
+ VerticalSpacer(Space.g12)
+ Text(
+ text = "Error Text",
+ color = Colors.Text.red,
+ typography = TextStyle(
+ fontSize = FontSize.sm,
+ lineHeight = LineHeights.sm
+ )
+ )
+ }
+ }
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Addon.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Addon.kt
new file mode 100644
index 000000000..bb11c1777
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Addon.kt
@@ -0,0 +1,11 @@
+package com.atls.hyperion.ui.shared.addon
+
+import androidx.compose.runtime.Composable
+
+interface Addon {
+ @Composable
+ fun Content()
+
+ @Composable
+ fun Spacer()
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Position.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Position.kt
new file mode 100644
index 000000000..b856469cb
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Position.kt
@@ -0,0 +1,5 @@
+package com.atls.hyperion.ui.shared.addon
+
+enum class AddonPosition {
+ Before, After
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Scope.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Scope.kt
new file mode 100644
index 000000000..8ba6412ed
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/Scope.kt
@@ -0,0 +1,15 @@
+package com.atls.hyperion.ui.shared.addon
+
+class AddonScope {
+ private val slots = mutableMapOf>()
+
+ fun before(vararg addons: Addon) {
+ slots.getOrPut(AddonPosition.Before) { mutableListOf() }.addAll(addons)
+ }
+
+ fun after(vararg addons: Addon) {
+ slots.getOrPut(AddonPosition.After) { mutableListOf() }.addAll(addons)
+ }
+
+ fun build() = AddonSlotManager(slots)
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/SlotManager.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/SlotManager.kt
new file mode 100644
index 000000000..be5ea8d68
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/SlotManager.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.shared.addon
+
+data class AddonSlotManager(
+ val addons: Map> = emptyMap()
+) {
+ fun get(position: AddonPosition): List = addons[position].orEmpty()
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/dsl/Build.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/dsl/Build.kt
new file mode 100644
index 000000000..aee2a56f4
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/addon/dsl/Build.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.shared.addon.dsl
+
+import androidx.compose.runtime.Composable
+import com.atls.hyperion.ui.shared.addon.AddonScope
+import com.atls.hyperion.ui.shared.addon.AddonSlotManager
+
+@Composable
+fun build(builder: @Composable AddonScope.() -> Unit): AddonSlotManager =
+ AddonScope().apply { builder() }.build()
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/layout/AspectSquare.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/layout/AspectSquare.kt
new file mode 100644
index 000000000..50377e1f5
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/shared/layout/AspectSquare.kt
@@ -0,0 +1,9 @@
+package com.atls.hyperion.ui.shared.layout
+
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+fun Modifier.aspectSquare(): Modifier =
+ this.aspectRatio(1f)
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Colors.kt
index 760ba665c..0486fded9 100644
--- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Colors.kt
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Colors.kt
@@ -13,6 +13,7 @@ object Colors {
val black = Color(0xFF000000)
val blackThreeQuarters = Color(0xBF000000)
val lightPurple = Color(0xFFDADEED)
+ val transparent = Color.Transparent
}
object Text {
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/effects/Alpha.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/effects/Alpha.kt
new file mode 100644
index 000000000..6ef296fef
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/effects/Alpha.kt
@@ -0,0 +1,7 @@
+package com.atls.hyperion.ui.theme.tokens.effects
+
+object Alpha {
+ val huge = 0.9f
+ val large = 0.8f
+ val medium = 0.6f
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Borders.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Borders.kt
index c1fb5510a..ef8a9668a 100644
--- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Borders.kt
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Borders.kt
@@ -1,26 +1,8 @@
package com.atls.hyperion.ui.theme.tokens.layout
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-object Border {
- val none: BorderStroke? = null
- val thinLightBlue = BorderStroke(0.5.dp, Color(0xFFD2DDF9))
- val thinLightGray = BorderStroke(0.5.dp, Color(0xFFE4E4E4))
- val normalGray = BorderStroke(1.dp, Color(0xFF727272))
- val normalSilver = BorderStroke(1.dp, Color(0xFFE0E0E0))
- val normalCloudyWhite = BorderStroke(1.dp, Color(0xFFE4E4E4))
- val normalBlue = BorderStroke(1.dp, Color(0xFF416DDF))
- val normalLightGray = BorderStroke(1.dp, Color(0xFFB8B8B8))
- val normalLightBlue = BorderStroke(1.dp, Color(0xFFE8EDFB))
- val normalSoftBlue = BorderStroke(1.dp, Color(0xFF1270FC))
- val normalMediumGray = BorderStroke(1.dp, Color(0xFFD4D4D4))
- val normalConcrete = BorderStroke(1.dp, Color(0xFFF3F3F3))
- val mediumBlue = BorderStroke(3.dp, Color(0xFF416DDF))
- val mediumLightBlue = BorderStroke(3.dp, Color(0xFFE8EDFB))
- val bigLightBlue = BorderStroke(7.dp, Color(0xFFDBD6FF))
- val bigWhite = BorderStroke(6.dp, Color(0xFFFFFFFF))
- val dashedGray = BorderStroke(2.dp, Color(0xFFB8B8B8))
- val dashedBlue = BorderStroke(2.dp, Color(0xFF416DDF))
+object BorderStroke {
+ val none = 0.dp
+ val tiny = 1.dp
}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Elevation.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Elevation.kt
new file mode 100644
index 000000000..29d13b027
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Elevation.kt
@@ -0,0 +1,8 @@
+package com.atls.hyperion.ui.theme.tokens.layout
+
+import androidx.compose.ui.unit.dp
+
+object Elevation {
+ val zero = 0.dp
+ val tiny = 2.dp
+}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt
index 79ff17ec8..119557275 100644
--- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt
@@ -5,13 +5,16 @@ import androidx.compose.ui.unit.dp
object Space {
val zero = 0.dp
val g1 = 1.dp
+ val g2 = 2.dp
val g4 = 4.dp
val g6 = 6.dp
val g8 = 8.dp
val g10 = 10.dp
val g12 = 12.dp
val g14 = 14.dp
+ val g16 = 16.dp
val g17 = 17.dp
+ val g20 = 20.dp
val g22 = 22.dp
val g24 = 24.dp
}
diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Weight.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Weight.kt
new file mode 100644
index 000000000..c5a93acc9
--- /dev/null
+++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Weight.kt
@@ -0,0 +1,6 @@
+package com.atls.hyperion.ui.theme.tokens.layout
+
+object Weight {
+ val full = 1f
+ val large = 0.7f
+}