Design system: Shonar tokens + shared UI primitives (redesign phase 2)
Tokens centralize the reference design language: near-black navy page, one elevated surface, hairline borders, mint-teal accent reserved for active/live/selected, soft glow only on the selected pill; radius, 4dp spacing scale, icon sizes, and the full type hierarchy (22/700 page title through 12/regular labels) all live in one place. Components.kt adds the primitives every screen will use: SonarCard, ShonarButton (filled mint primary), ShonarOutlinedButton, ShonarGhostButton, ShonarDangerButton, PillChip (the reference's selected-pill-with-glow), SonarField (filled dark input with teal focus border), SectionHeader, HelperText. Material3 scheme derived from the same tokens so leftover defaults inherit the language.
This commit is contained in:
parent
4800364f09
commit
5c0d1edbef
2 changed files with 435 additions and 28 deletions
288
app/src/main/kotlin/com/shonar/desktop/Components.kt
Normal file
288
app/src/main/kotlin/com/shonar/desktop/Components.kt
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
package com.shonar.desktop
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
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.draw.shadow
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable SHONAR UI primitives — every button, card, pill, and field in
|
||||||
|
* the app routes through these so the design language stays uniform.
|
||||||
|
* Behavior is plain pass-through: onClick fires exactly as before.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** App-wide card: elevated surface, hairline border, 14dp radius,
|
||||||
|
* no drop shadow (reference cards sit flush in the page). */
|
||||||
|
@Composable
|
||||||
|
fun SonarCard(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val clickModifier = if (onClick != null)
|
||||||
|
Modifier.clickable(onClick = onClick) else Modifier
|
||||||
|
Surface(
|
||||||
|
modifier = modifier.then(clickModifier),
|
||||||
|
shape = Shonar.ShapeCard,
|
||||||
|
color = Shonar.Surface,
|
||||||
|
border = BorderStroke(1.dp, Shonar.Border),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Primary action: filled mint, dark ink label, 12dp radius. */
|
||||||
|
@Composable
|
||||||
|
fun ShonarButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.height(Shonar.ControlH),
|
||||||
|
enabled = enabled,
|
||||||
|
shape = Shonar.ShapeButton,
|
||||||
|
elevation = null,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Shonar.Accent,
|
||||||
|
contentColor = Shonar.InkOnAccent,
|
||||||
|
disabledContainerColor = Shonar.SurfaceAlt,
|
||||||
|
disabledContentColor = Shonar.TextMuted,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Secondary action: quiet outlined pill-button, hairline border. */
|
||||||
|
@Composable
|
||||||
|
fun ShonarOutlinedButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.height(Shonar.ControlH),
|
||||||
|
enabled = enabled,
|
||||||
|
shape = Shonar.ShapeButton,
|
||||||
|
border = BorderStroke(1.dp, Shonar.Border),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = Shonar.TextPrimary,
|
||||||
|
disabledContentColor = Shonar.TextMuted,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ghost action: text-only, teal label (nav links, row actions). */
|
||||||
|
@Composable
|
||||||
|
fun ShonarGhostButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
shape = Shonar.ShapeButton,
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = Shonar.Accent,
|
||||||
|
disabledContentColor = Shonar.TextMuted,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destructive action: quiet red outline — visible without shouting. */
|
||||||
|
@Composable
|
||||||
|
fun ShonarDangerButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.height(Shonar.ControlH),
|
||||||
|
enabled = enabled,
|
||||||
|
shape = Shonar.ShapeButton,
|
||||||
|
border = BorderStroke(1.dp, Shonar.Error.copy(alpha = 0.5f)),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = Shonar.Error,
|
||||||
|
disabledContentColor = Shonar.TextMuted,
|
||||||
|
),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selection pill (sort/show/filters/voices): ghost outline by default;
|
||||||
|
* selected = teal border + teal label + soft teal glow — the one place
|
||||||
|
* the glow appears, matching the reference's active "Newest" pill.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun PillChip(
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
label: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
) {
|
||||||
|
val borderColor = when {
|
||||||
|
!enabled -> Shonar.Border.copy(alpha = 0.5f)
|
||||||
|
selected -> Shonar.Accent
|
||||||
|
else -> Shonar.Border
|
||||||
|
}
|
||||||
|
val textColor = when {
|
||||||
|
!enabled -> Shonar.TextMuted
|
||||||
|
selected -> Shonar.Accent
|
||||||
|
else -> Shonar.TextSecondary
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
modifier = modifier
|
||||||
|
.height(32.dp)
|
||||||
|
.then(
|
||||||
|
if (selected) Modifier.shadow(
|
||||||
|
elevation = 8.dp, shape = Shonar.ShapePill, clip = false,
|
||||||
|
ambientColor = Shonar.AccentGlow, spotColor = Shonar.AccentGlow,
|
||||||
|
) else Modifier
|
||||||
|
),
|
||||||
|
shape = Shonar.ShapePill,
|
||||||
|
color = if (selected) Shonar.SurfaceAlt else Shonar.Surface,
|
||||||
|
border = BorderStroke(1.dp, borderColor),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier.padding(horizontal = 14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(label, style = MaterialTheme.typography.labelLarge, color = textColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filled dark search/input field with leading icon slot: hairline
|
||||||
|
* border, 14dp radius, teal-tinted border while focused. Used for
|
||||||
|
* library search, in-recording search, rename, and text entry.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SonarField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
placeholder: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
singleLine: Boolean = true,
|
||||||
|
leadingIcon: (@Composable () -> Unit)? = null,
|
||||||
|
imeAction: ImeAction = ImeAction.Done,
|
||||||
|
onDone: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
var isFocused by androidx.compose.runtime.remember {
|
||||||
|
androidx.compose.runtime.mutableStateOf(false)
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = modifier.onFocusChanged { isFocused = it.hasFocus },
|
||||||
|
shape = Shonar.ShapeCard,
|
||||||
|
color = Shonar.SurfaceAlt,
|
||||||
|
border = BorderStroke(
|
||||||
|
1.dp, if (isFocused) Shonar.Accent else Shonar.Border),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||||
|
verticalAlignment = if (singleLine) Alignment.CenterVertically
|
||||||
|
else Alignment.Top,
|
||||||
|
) {
|
||||||
|
leadingIcon?.let {
|
||||||
|
it()
|
||||||
|
Spacer(Modifier.width(Shonar.Space2))
|
||||||
|
}
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
singleLine = singleLine,
|
||||||
|
textStyle = MaterialTheme.typography.bodyLarge
|
||||||
|
.copy(color = Shonar.TextPrimary),
|
||||||
|
cursorBrush = SolidColor(Shonar.Accent),
|
||||||
|
keyboardOptions = KeyboardOptions(imeAction = imeAction),
|
||||||
|
keyboardActions = KeyboardActions(onDone = { onDone() }),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
decorationBox = { inner ->
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
Text(placeholder,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = Shonar.TextMuted)
|
||||||
|
}
|
||||||
|
inner()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Section header row: title left, muted trailing meta right. */
|
||||||
|
@Composable
|
||||||
|
fun SectionHeader(
|
||||||
|
title: String,
|
||||||
|
meta: String? = null,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(title, style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.weight(1f))
|
||||||
|
if (meta != null) {
|
||||||
|
Text(meta, style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Shonar.TextMuted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Muted helper/explainer text used under controls. */
|
||||||
|
@Composable
|
||||||
|
fun HelperText(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
error: Boolean = false,
|
||||||
|
) {
|
||||||
|
Text(text,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = if (error) MaterialTheme.colorScheme.error
|
||||||
|
else Shonar.TextSecondary,
|
||||||
|
modifier = modifier)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
package com.shonar.desktop
|
package com.shonar.desktop
|
||||||
|
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Shapes
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -16,38 +22,132 @@ import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SHONAR Desktop theme — the "deep-sea sonar" palette: dark navy +
|
* SHONAR Desktop design system — the "deep-sea sonar" language from the
|
||||||
* teal ping. Seven role overrides; everything else falls through to
|
* reference design: near-black navy page, one elevated surface for all
|
||||||
* Material3 defaults.
|
* cards/fields, hairline borders, a single mint-teal accent used with
|
||||||
|
* restraint (active controls, live status, selection), and a soft teal
|
||||||
|
* glow reserved for the selected pill.
|
||||||
*
|
*
|
||||||
* Status semantics in the library list map to scheme roles:
|
* Everything visual routes through [Shonar] tokens or the MaterialTheme
|
||||||
|
* roles derived from them — screens must not hardcode colors, radii, or
|
||||||
|
* spacing. Reusable primitives live in Components.kt.
|
||||||
|
*
|
||||||
|
* Status semantics map to scheme roles app-wide:
|
||||||
* done -> primary (teal), failed -> error (red), queued/running ->
|
* done -> primary (teal), failed -> error (red), queued/running ->
|
||||||
* onSurfaceVariant with a primary progress bar.
|
* onSurfaceVariant text with a primary progress bar.
|
||||||
*/
|
*/
|
||||||
private val Teal = Color(0xFF4FD1C5)
|
object Shonar {
|
||||||
private val TealDark = Color(0xFF2A9D94)
|
// ---- color tokens ----
|
||||||
private val DeepNavy = Color(0xFF0B1220)
|
val Background = Color(0xFF0A0E15) // page: near-black cool navy
|
||||||
private val NavySurface = Color(0xFF121C2E)
|
val Surface = Color(0xFF121A25) // cards, fields, pills
|
||||||
private val Ink = Color(0xFF10141A)
|
val SurfaceAlt = Color(0xFF0E151F) // pressed / hover / inset
|
||||||
|
val Border = Color(0xFF1E2A38) // hairline on dark
|
||||||
|
val Accent = Color(0xFF5EEAD4) // mint-teal: icons, selection
|
||||||
|
val AccentStrong = Color(0xFF4FD1C5) // filled-accent hover depth
|
||||||
|
val AccentGlow = Color(0x405EEAD4) // soft halo (~25% mint)
|
||||||
|
val InkOnAccent = Color(0xFF06231F) // text on filled accent
|
||||||
|
val TextPrimary = Color(0xFFE8EEF4)
|
||||||
|
val TextSecondary = Color(0xFF8A97A8)
|
||||||
|
val TextMuted = Color(0xFF6B7686)
|
||||||
|
val Error = Color(0xFFF27272) // red readable on navy
|
||||||
|
val OnError = Color(0xFF1A0B0D)
|
||||||
|
|
||||||
private val ShonarDarkScheme = darkColorScheme(
|
// ---- shape tokens ----
|
||||||
primary = Teal,
|
val RadiusCard = 14.dp // cards + fields
|
||||||
onPrimary = Ink,
|
val RadiusButton = 12.dp // rectangular buttons
|
||||||
secondary = TealDark,
|
val ShapeCard = RoundedCornerShape(RadiusCard)
|
||||||
background = DeepNavy,
|
val ShapeButton = RoundedCornerShape(RadiusButton)
|
||||||
surface = NavySurface,
|
val ShapePill = RoundedCornerShape(50) // pills + round controls
|
||||||
onBackground = Color(0xFFE6EDF3),
|
|
||||||
onSurface = Color(0xFFE6EDF3),
|
// ---- spacing scale (4dp rhythm; compact but comfortable) ----
|
||||||
|
val Space1 = 4.dp
|
||||||
|
val Space2 = 8.dp
|
||||||
|
val Space3 = 12.dp
|
||||||
|
val Space4 = 16.dp
|
||||||
|
val Space5 = 20.dp // page padding
|
||||||
|
val CardPadding = 14.dp
|
||||||
|
val RowGap = 8.dp // gap between list cards
|
||||||
|
val ControlH = 40.dp // standard button height
|
||||||
|
|
||||||
|
// ---- icon sizing ----
|
||||||
|
val IconSm = 18.dp
|
||||||
|
val IconMd = 20.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ShonarShapes = Shapes(
|
||||||
|
extraSmall = RoundedCornerShape(6.dp),
|
||||||
|
small = RoundedCornerShape(8.dp),
|
||||||
|
medium = Shonar.ShapeCard, // cards & fields
|
||||||
|
large = Shonar.ShapeButton,
|
||||||
|
extraLarge = RoundedCornerShape(20.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Wrap the app content in the SHONAR dark sonar theme. */
|
// Typography hierarchy per the reference: page title 22/700 wide
|
||||||
|
// tracking; section 16/600; item title 15/600; body 14; metadata 13
|
||||||
|
// muted; labels 12–13.
|
||||||
|
private val ShonarTypography = Typography(
|
||||||
|
headlineSmall = TextStyle(
|
||||||
|
fontSize = 22.sp, fontWeight = FontWeight.Bold,
|
||||||
|
letterSpacing = 0.5.sp, color = Shonar.TextPrimary,
|
||||||
|
),
|
||||||
|
titleMedium = TextStyle(
|
||||||
|
fontSize = 16.sp, fontWeight = FontWeight.SemiBold,
|
||||||
|
color = Shonar.TextPrimary,
|
||||||
|
),
|
||||||
|
titleSmall = TextStyle(
|
||||||
|
fontSize = 15.sp, fontWeight = FontWeight.SemiBold,
|
||||||
|
color = Shonar.TextPrimary,
|
||||||
|
),
|
||||||
|
bodyLarge = TextStyle(fontSize = 15.sp, color = Shonar.TextPrimary),
|
||||||
|
bodyMedium = TextStyle(fontSize = 14.sp, color = Shonar.TextPrimary),
|
||||||
|
bodySmall = TextStyle(fontSize = 13.sp, color = Shonar.TextSecondary),
|
||||||
|
labelLarge = TextStyle(
|
||||||
|
fontSize = 14.sp, fontWeight = FontWeight.Medium,
|
||||||
|
),
|
||||||
|
labelMedium = TextStyle(
|
||||||
|
fontSize = 13.sp, fontWeight = FontWeight.Medium,
|
||||||
|
color = Shonar.TextSecondary,
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(fontSize = 12.sp, color = Shonar.TextMuted),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val ShonarDarkScheme = darkColorScheme(
|
||||||
|
primary = Shonar.AccentStrong,
|
||||||
|
onPrimary = Shonar.InkOnAccent,
|
||||||
|
primaryContainer = Shonar.AccentGlow,
|
||||||
|
onPrimaryContainer = Shonar.Accent,
|
||||||
|
secondary = Shonar.Accent,
|
||||||
|
onSecondary = Shonar.InkOnAccent,
|
||||||
|
secondaryContainer = Shonar.SurfaceAlt,
|
||||||
|
onSecondaryContainer = Shonar.TextSecondary,
|
||||||
|
background = Shonar.Background,
|
||||||
|
onBackground = Shonar.TextPrimary,
|
||||||
|
surface = Shonar.Surface,
|
||||||
|
onSurface = Shonar.TextPrimary,
|
||||||
|
surfaceVariant = Shonar.SurfaceAlt,
|
||||||
|
onSurfaceVariant = Shonar.TextSecondary,
|
||||||
|
error = Shonar.Error,
|
||||||
|
onError = Shonar.OnError,
|
||||||
|
outline = Shonar.Border,
|
||||||
|
outlineVariant = Shonar.Border,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Wrap the app content in the SHONAR dark sonar design system. */
|
||||||
@Composable
|
@Composable
|
||||||
fun ShonarTheme(content: @Composable () -> Unit) {
|
fun ShonarTheme(content: @Composable () -> Unit) {
|
||||||
MaterialTheme(colorScheme = ShonarDarkScheme, content = content)
|
MaterialTheme(
|
||||||
|
colorScheme = ShonarDarkScheme,
|
||||||
|
shapes = ShonarShapes,
|
||||||
|
typography = ShonarTypography,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,7 +161,7 @@ fun SonarLogo(size: Dp = 32.dp, modifier: Modifier = Modifier) {
|
||||||
val s = this.size.minDimension / 108f
|
val s = this.size.minDimension / 108f
|
||||||
fun c(x: Float, y: Float) = Offset(x * s, y * s)
|
fun c(x: Float, y: Float) = Offset(x * s, y * s)
|
||||||
// Center dot: r=6 at (54,54)
|
// Center dot: r=6 at (54,54)
|
||||||
drawCircle(Teal, radius = 6f * s, center = c(54f, 54f))
|
drawCircle(Shonar.Accent, radius = 6f * s, center = c(54f, 54f))
|
||||||
// Upper-right quarter arcs (teal), centered on the dot.
|
// Upper-right quarter arcs (teal), centered on the dot.
|
||||||
val arcStyle = Stroke(width = 4f * s, cap = StrokeCap.Round)
|
val arcStyle = Stroke(width = 4f * s, cap = StrokeCap.Round)
|
||||||
fun arc(color: Color, startDeg: Float, boxTopLeft: Float) {
|
fun arc(color: Color, startDeg: Float, boxTopLeft: Float) {
|
||||||
|
|
@ -73,24 +173,43 @@ fun SonarLogo(size: Dp = 32.dp, modifier: Modifier = Modifier) {
|
||||||
style = arcStyle,
|
style = arcStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
arc(Teal, -90f, 38f)
|
arc(Shonar.Accent, -90f, 38f)
|
||||||
arc(Teal, -90f, 26f)
|
arc(Shonar.Accent, -90f, 26f)
|
||||||
// Lower-left quarter arcs (teal-dark).
|
// Lower-left quarter arcs (teal-dark).
|
||||||
arc(TealDark, 90f, 38f)
|
arc(Shonar.AccentStrong, 90f, 38f)
|
||||||
arc(TealDark, 90f, 26f)
|
arc(Shonar.AccentStrong, 90f, 26f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wordmark row shared by screen headers: ping logo + S.H.O.N.A.R. + tagline. */
|
/** The reference header's logo treatment: ping inside a hairline
|
||||||
|
* rounded tile. */
|
||||||
|
@Composable
|
||||||
|
fun SonarLogoTile(modifier: Modifier = Modifier, size: Dp = 40.dp) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier.size(size).border(1.dp, Shonar.Border, Shonar.ShapeCard),
|
||||||
|
color = Shonar.Surface,
|
||||||
|
shape = Shonar.ShapeCard,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier.padding(Shonar.Space1),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
SonarLogo(size = size - Shonar.Space1 * 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wordmark row shared by screen headers: bordered logo tile +
|
||||||
|
* S.H.O.N.A.R. (wide tracking) + tagline, per the reference header. */
|
||||||
@Composable
|
@Composable
|
||||||
fun ShonarWordmark(
|
fun ShonarWordmark(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
logoSize: Dp = 28.dp,
|
logoSize: Dp = 28.dp,
|
||||||
style: androidx.compose.ui.text.TextStyle = MaterialTheme.typography.headlineSmall,
|
style: TextStyle = MaterialTheme.typography.headlineSmall,
|
||||||
) {
|
) {
|
||||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||||
SonarLogo(logoSize)
|
SonarLogoTile(size = logoSize + Shonar.Space3)
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(Shonar.Space3))
|
||||||
Column {
|
Column {
|
||||||
Text("S.H.O.N.A.R.", style = style)
|
Text("S.H.O.N.A.R.", style = style)
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue