# Marker

An inline annotation for timelines and conversations, such as a date divider or system event.

Marker is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

Source: https://ui.suiss.app/docs/components/marker

**React**

**Example**

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export default function MarkerDemo() {
  return (
    <div className="w-96">
      <Marker>
        <MarkerContent>Conversation started</MarkerContent>
      </Marker>
    </div>
  )
}
```

`Marker` displays a short line of secondary text, typically between messages or list entries. Put the text in `MarkerContent` and, optionally, a leading `MarkerIcon`.

The `variant` prop changes the treatment: plain text, text centered between two separator rules, or text above a bottom border. Use `Marker` for annotations within content; use `Separator` for a purely visual rule with no text.

## Installation

### CLI

```bash title="Terminal"
npx shadcn@latest add @esuiss/marker
```

Works in any project with a `components.json`. See [Installation](https://ui.suiss.app/docs/installation#add-components) for the one-line registry setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add marker
```

### Manual

1. Install the dependencies and add the [`cn` helper](https://ui.suiss.app/docs/installation#the-cn-helper):

```bash title="Terminal"
npm install @base-ui/react class-variance-authority
```

2. Copy the source into `components/ui/marker.tsx`:

```tsx title="components/ui/marker.tsx"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"
import { cn } from "@/lib/utils"

const markerVariants = cva(
  "group/marker relative flex min-h-4 w-full items-center gap-2 text-start text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:hover:text-foreground",
  {
    variants: {
      variant: {
        default: "",
        separator:
          "before:me-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ms-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
        border: "border-b border-border pb-2",
      },
    },
  }
)

function Marker({
  className,
  variant = "default",
  render,
  ...props
}: useRender.ComponentProps<"div"> & VariantProps<typeof markerVariants>) {
  return useRender({
    defaultTagName: "div",
    props: mergeProps<"div">(
      {
        className: cn(markerVariants({ variant, className })),
      },
      props
    ),
    render,
    state: {
      slot: "marker",
      variant,
    },
  })
}

function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="marker-icon"
      aria-hidden="true"
      className={cn(
        "size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    />
  )
}

function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="marker-content"
      className={cn(
        "min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:hover:text-foreground",
        className
      )}
      {...props}
    />
  )
}

export { Marker, MarkerContent, MarkerIcon }
```

3. Update the import paths to match your project.

## Usage

```tsx
import { Marker, MarkerIcon, MarkerContent } from "@/components/ui/marker"
```

```tsx
<div className="w-96">
  <Marker>
    <MarkerContent>Conversation started</MarkerContent>
  </Marker>
</div>
```

Links inside the marker or its content are underlined automatically. The underlying element can be changed:

`Marker` is built on Base UI's `useRender`, so it accepts a `render` prop to change the underlying element.

## Examples

### Variants

The `default`, `separator` and `border` variants stacked for comparison.

**Example**

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export default function MarkerVariants() {
  return (
    <div className="w-96">
      <div className="flex flex-col gap-6">
        <Marker variant="default">
          <MarkerContent>Default</MarkerContent>
        </Marker>
        <Marker variant="separator">
          <MarkerContent>Today</MarkerContent>
        </Marker>
        <Marker variant="border">
          <MarkerContent>Earlier messages</MarkerContent>
        </Marker>
      </div>
    </div>
  )
}
```

### Separator

Text centered between two horizontal rules, as used for a date divider.

**Example**

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export default function MarkerSeparator() {
  return (
    <div className="w-96">
      <Marker variant="separator">
        <MarkerContent>Yesterday</MarkerContent>
      </Marker>
    </div>
  )
}
```

### With icon

Markers with a leading decorative icon in the default and separator variants.

**Example**

```tsx
import { CalendarBlankIcon, InfoIcon } from "@phosphor-icons/react"
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export default function MarkerWithIcon() {
  return (
    <div className="w-96">
      <div className="flex flex-col gap-6">
        <Marker>
          <MarkerIcon>
            <InfoIcon />
          </MarkerIcon>
          <MarkerContent>Maya joined the conversation</MarkerContent>
        </Marker>
        <Marker variant="separator">
          <MarkerIcon>
            <CalendarBlankIcon />
          </MarkerIcon>
          <MarkerContent>March 12</MarkerContent>
        </Marker>
      </div>
    </div>
  )
}
```

### With link

A marker whose content includes an inline link.

**Example**

```tsx
import { Marker, MarkerContent } from "@/components/ui/marker"

export default function MarkerWithLink() {
  return (
    <div className="w-96">
      <Marker>
        <MarkerContent>
          This chat was archived. <a href="#restore">Restore it</a>
        </MarkerContent>
      </Marker>
    </div>
  )
}
```

## API Reference

- `Marker`
- `MarkerContent`
- `MarkerIcon`

### Marker

The container row that applies the variant styling.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "separator" \| "border"` | `"default"` | Sets the visual treatment: `default` for plain text, `separator` for text between two rules, or `border` for text above a bottom border. |

Also accepts the props of `useRender.ComponentProps<"div">`.

### MarkerIcon

A decorative icon slot shown before the content and hidden from assistive technology.

Also accepts the props of `React.ComponentProps<"span">`.

### MarkerContent

Holds the marker text and any inline links.

Also accepts the props of `React.ComponentProps<"span">`.

## Accessibility

- `MarkerIcon` sets `aria-hidden="true"`, so icons must be decorative; put meaningful text in `MarkerContent`.
- Links inside the content remain in the normal tab order.

## Notes

- Styling hooks: `data-slot="marker"`, `data-slot="marker-icon"` and `data-slot="marker-content"`. The root also exposes `data-variant`.
- Rules and borders use the `border` color; text uses `muted-foreground` and links hover to `foreground`.

**React Native, SwiftUI, Jetpack Compose**

![Preview](https://ui.suiss.app/previews/components-marker--variants.light.webp)

```tsx
import { CalendarBlankIcon, InfoIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Icon } from "@/components/ui/icon"
import { Marker, MarkerContent, MarkerIcon } from "@/components/ui/marker"

export function MarkerVariantsExample() {
  return (
    <View className="gap-8">
      <Marker>
        <MarkerIcon>
          <Icon as={InfoIcon} />
        </MarkerIcon>
        <MarkerContent>Messages are end-to-end encrypted.</MarkerContent>
      </Marker>
      <Marker variant="separator">
        <MarkerContent>Today</MarkerContent>
      </Marker>
      <Marker variant="border">
        <MarkerIcon>
          <Icon as={CalendarBlankIcon} />
        </MarkerIcon>
        <MarkerContent>Upcoming</MarkerContent>
      </Marker>
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct MarkerVariantsExample: View {
  var body: some View {
    VStack(spacing: 32) {
      Marker {
        MarkerIcon { Ph.info.regular }
        MarkerContent("Messages are end-to-end encrypted.")
      }
      Marker(.separator) {
        MarkerContent("Today")
      }
      Marker(.border) {
        MarkerIcon { Ph.calendarBlank.regular }
        MarkerContent("Upcoming")
      }
    }
    .frame(maxWidth: .infinity)
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.CalendarBlank
import com.adamglin.phosphoricons.regular.Info
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Marker
import com.esuiss.ui.components.MarkerContent
import com.esuiss.ui.components.MarkerIcon
import com.esuiss.ui.components.MarkerVariant

@Composable
fun MarkerVariants() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(32.dp)) {
        Marker {
            MarkerIcon { Icon(PhosphorIcons.Regular.Info, contentDescription = null) }
            MarkerContent("Messages are end-to-end encrypted.")
        }
        Marker(variant = MarkerVariant.Separator) {
            MarkerContent("Today")
        }
        Marker(variant = MarkerVariant.Border) {
            MarkerIcon { Icon(PhosphorIcons.Regular.CalendarBlank, contentDescription = null) }
            MarkerContent("Upcoming")
        }
    }
}
```

`Marker` is a row of secondary text with an optional icon. It has three variants: `default` for a plain row, `separator` for centered text with a line on each side, and `border` for a row with a bottom border.

Use `separator` for date dividers in a chat or feed, `border` for section headings, and `default` for short notices.

## Installation

**React Native**

```bash title="Terminal"
npx shadcn@latest add @esuiss-rn/marker
```

See [Installation](https://ui.suiss.app/docs/installation) for the `components.json` setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add marker
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add marker --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add marker --platform kotlin
```

The CLI also adds what it builds on: [Icon](https://ui.suiss.app/docs/components/icon), [Text](https://ui.suiss.app/docs/components/text).

### Manual

**React Native**

**Uniwind**

1. Install the dependencies:

```bash title="Terminal"
npx expo install class-variance-authority cn
```

2. Add the pieces it builds on: [Icon](https://ui.suiss.app/docs/components/icon), [Text](https://ui.suiss.app/docs/components/text), and the primitives `components/primitives/types.tsx`, `components/primitives/use-render.tsx`.

3. Copy the source into `components/ui/marker.tsx`:

```tsx title="uniwind/components/ui/marker.tsx"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { View, type ViewProps } from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type MarkerVariant = "default" | "separator" | "border"

const MarkerContext = React.createContext<MarkerVariant>("default")

const markerVariants = cva("min-h-4 w-full flex-row items-center gap-2", {
  variants: {
    variant: {
      default: "",
      separator: "",
      border: "border-b border-border pb-2",
    },
  },
  defaultVariants: {
    variant: "default",
  },
})

function MarkerLine() {
  return (
    <View
      accessible={false}
      importantForAccessibility="no"
      className="h-px min-w-0 flex-1 bg-border"
    />
  )
}

function Marker({
  className,
  variant = "default",
  render,
  children,
  ...props
}: ViewProps &
  VariantProps<typeof markerVariants> & {
    render?: RenderProp<Record<string, unknown>, Record<string, never>>
  }) {
  const resolved = variant ?? "default"
  const separator = resolved === "separator"
  const element = useRender({
    defaultComponent: View,
    render,
    props: [
      {
        className: cn(markerVariants({ variant: resolved }), className),
        children: (
          <>
            {separator ? <MarkerLine /> : null}
            {children}
            {separator ? <MarkerLine /> : null}
          </>
        ),
      },
      props,
    ],
  })
  return (
    <MarkerContext.Provider value={resolved}>
      <TextClassContext.Provider value="text-start text-sm text-muted-foreground">
        <IconClassContext.Provider value="size-4 text-muted-foreground">
          {element}
        </IconClassContext.Provider>
      </TextClassContext.Provider>
    </MarkerContext.Provider>
  )
}

function MarkerIcon({ className, ...props }: ViewProps) {
  return (
    <View
      accessible={false}
      importantForAccessibility="no-hide-descendants"
      className={cn("size-4 shrink-0 items-center justify-center", className)}
      {...props}
    />
  )
}

function MarkerContent({
  className,
  ...props
}: React.ComponentProps<typeof Text>) {
  const variant = React.useContext(MarkerContext)
  return (
    <Text
      className={cn(
        "min-w-0 shrink",
        variant === "separator" && "flex-none shrink-0 text-center",
        className
      )}
      {...props}
    />
  )
}

export { Marker, MarkerContent, MarkerIcon }
```

4. Update the import paths to match your project.

**NativeWind**

1. Install the dependencies:

```bash title="Terminal"
npx expo install class-variance-authority cn
```

2. Add the pieces it builds on: [Icon](https://ui.suiss.app/docs/components/icon), [Text](https://ui.suiss.app/docs/components/text), and the primitives `components/primitives/types.tsx`, `components/primitives/use-render.tsx`.

3. Copy the source into `components/ui/marker.tsx`:

```tsx title="nativewind/components/ui/marker.tsx"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import * as React from "react"
import { View, type ViewProps } from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"

type MarkerVariant = "default" | "separator" | "border"

const MarkerContext = React.createContext<MarkerVariant>("default")

const markerVariants = cva("min-h-4 w-full flex-row items-center gap-2", {
  variants: {
    variant: {
      default: "",
      separator: "",
      border: "border-b border-border pb-2",
    },
  },
  defaultVariants: {
    variant: "default",
  },
})

function MarkerLine() {
  return (
    <View
      accessible={false}
      importantForAccessibility="no"
      className="h-px min-w-0 flex-1 bg-border"
    />
  )
}

function Marker({
  className,
  variant = "default",
  render,
  children,
  ...props
}: ViewProps &
  VariantProps<typeof markerVariants> & {
    render?: RenderProp<Record<string, unknown>, Record<string, never>>
  }) {
  const resolved = variant ?? "default"
  const separator = resolved === "separator"
  const element = useRender({
    defaultComponent: View,
    render,
    props: [
      {
        className: cn(markerVariants({ variant: resolved }), className),
        children: (
          <>
            {separator ? <MarkerLine /> : null}
            {children}
            {separator ? <MarkerLine /> : null}
          </>
        ),
      },
      props,
    ],
  })
  return (
    <MarkerContext.Provider value={resolved}>
      <TextClassContext.Provider value="text-start text-sm text-muted-foreground">
        <IconClassContext.Provider value="size-4 text-muted-foreground">
          {element}
        </IconClassContext.Provider>
      </TextClassContext.Provider>
    </MarkerContext.Provider>
  )
}

function MarkerIcon({ className, ...props }: ViewProps) {
  return (
    <View
      accessible={false}
      importantForAccessibility="no-hide-descendants"
      className={cn("size-4 shrink-0 items-center justify-center", className)}
      {...props}
    />
  )
}

function MarkerContent({
  className,
  ...props
}: React.ComponentProps<typeof Text>) {
  const variant = React.useContext(MarkerContext)
  return (
    <Text
      className={cn(
        "min-w-0 shrink",
        variant === "separator" && "flex-none shrink-0 text-center",
        className
      )}
      {...props}
    />
  )
}

export { Marker, MarkerContent, MarkerIcon }
```

4. Update the import paths to match your project.

**StyleSheet**

1. Add the pieces it builds on: [Icon](https://ui.suiss.app/docs/components/icon), [Text](https://ui.suiss.app/docs/components/text), and the primitives `components/primitives/types.tsx`, `components/primitives/use-render.tsx`.

2. Copy the source into `components/ui/marker.tsx`:

```tsx title="stylesheet/components/ui/marker.tsx"
import * as React from "react"
import { StyleSheet, View, type ViewProps } from "react-native"
import type { RenderProp } from "@/components/primitives/types"
import { useRender } from "@/components/primitives/use-render"
import { IconSizeContext } from "@/components/ui/icon"
import { Text, TextStyleContext } from "@/components/ui/text"
import { fontSize, useTheme } from "@/components/ui/theme"

type MarkerVariant = "default" | "separator" | "border"

const MarkerContext = React.createContext<MarkerVariant>("default")

const styles = StyleSheet.create({
  root: {
    minHeight: 16,
    width: "100%",
    flexDirection: "row",
    alignItems: "center",
    gap: 8,
  },
  border: { borderBottomWidth: 1, paddingBottom: 8 },
  line: { height: 1, minWidth: 0, flex: 1 },
  icon: {
    width: 16,
    height: 16,
    flexShrink: 0,
    alignItems: "center",
    justifyContent: "center",
  },
  content: { minWidth: 0, flexShrink: 1 },
  contentSeparator: { flexShrink: 0, textAlign: "center" },
})

function MarkerLine() {
  const { colors } = useTheme()
  return (
    <View
      accessible={false}
      importantForAccessibility="no"
      style={[styles.line, { backgroundColor: colors.border }]}
    />
  )
}

function Marker({
  style,
  variant = "default",
  render,
  children,
  ...props
}: ViewProps & {
  variant?: MarkerVariant
  render?: RenderProp<Record<string, unknown>, Record<string, never>>
}) {
  const { colors } = useTheme()
  const separator = variant === "separator"
  const element = useRender({
    defaultComponent: View,
    render,
    props: [
      {
        style: [
          styles.root,
          variant === "border" && [
            styles.border,
            { borderColor: colors.border },
          ],
          style,
        ],
        children: (
          <>
            {separator ? <MarkerLine /> : null}
            {children}
            {separator ? <MarkerLine /> : null}
          </>
        ),
      },
      props,
    ],
  })
  return (
    <MarkerContext.Provider value={variant}>
      <TextStyleContext.Provider
        value={{
          ...fontSize.sm,
          textAlign: "left",
          color: colors.mutedForeground,
        }}
      >
        <IconSizeContext.Provider value={16}>
          {element}
        </IconSizeContext.Provider>
      </TextStyleContext.Provider>
    </MarkerContext.Provider>
  )
}

function MarkerIcon({ style, ...props }: ViewProps) {
  return (
    <View
      accessible={false}
      importantForAccessibility="no-hide-descendants"
      style={[styles.icon, style]}
      {...props}
    />
  )
}

function MarkerContent({ style, ...props }: React.ComponentProps<typeof Text>) {
  const variant = React.useContext(MarkerContext)
  return (
    <Text
      style={[
        styles.content,
        variant === "separator" && styles.contentSeparator,
        style,
      ]}
      {...props}
    />
  )
}

export { Marker, MarkerContent, MarkerIcon }
```

3. Update the import paths to match your project.

**SwiftUI**

1. Target iOS 17 or later.

2. Copy the theme files every view uses:

- `swift/Theme/Theme.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Theme/Theme.swift)

- `swift/Theme/Tokens.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Theme/Tokens.swift)

3. Copy the source and every file it uses:

```swift title="swift/Components/Marker.swift"
import SwiftUI

public enum MarkerVariant: CaseIterable, Sendable {
  case `default`, separator, border
}

extension EnvironmentValues {
  @Entry var markerVariant: MarkerVariant = .default
}

public struct Marker<Content: View>: View {
  let variant: MarkerVariant
  let content: Content

  public init(_ variant: MarkerVariant = .default, @ViewBuilder content: () -> Content) {
    self.variant = variant
    self.content = content()
  }

  public var body: some View {
    Group {
      switch variant {
      case .default:
        row
      case .separator:
        HStack(spacing: 12) {
          line
          row.fixedSize(horizontal: true, vertical: false)
          line
        }
      case .border:
        row
          .padding(.bottom, 8)
          .overlay(alignment: .bottom) {
            Rectangle().fill(EsuissColor.border).frame(height: 1)
          }
      }
    }
    .frame(maxWidth: .infinity, minHeight: 16, alignment: .leading)
    .environment(\.markerVariant, variant)
  }

  private var row: some View {
    HStack(spacing: 8) {
      content
    }
    .scaledTextStyle(.sm)
    .foregroundStyle(EsuissColor.mutedForeground)
    .tint(EsuissColor.mutedForeground)
    .multilineTextAlignment(variant == .separator ? .center : .leading)
    .accessibilityElement(children: .combine)
  }

  private var line: some View {
    Rectangle()
      .fill(EsuissColor.border)
      .frame(height: 1)
      .frame(maxWidth: .infinity)
      .accessibilityHidden(true)
  }
}

extension Marker where Content == MarkerLabel {
  public init(_ title: LocalizedStringKey, icon: Image? = nil, variant: MarkerVariant = .default) {
    self.init(variant) { MarkerLabel(title: Text(title), icon: icon) }
  }

  public init(verbatim title: String, icon: Image? = nil, variant: MarkerVariant = .default) {
    self.init(variant) { MarkerLabel(title: Text(verbatim: title), icon: icon) }
  }
}

public struct MarkerLabel: View {
  let title: Text
  let icon: Image?

  public var body: some View {
    if let icon {
      MarkerIcon { icon }
    }
    MarkerContent { title }
  }
}

public struct MarkerIcon<Content: View>: View {
  let content: Content

  public init(@ViewBuilder content: () -> Content) {
    self.content = content()
  }

  public var body: some View {
    content
      .frame(width: 16, height: 16)
      .accessibilityHidden(true)
  }
}

public struct MarkerContent<Content: View>: View {
  let content: Content

  @Environment(\.markerVariant) private var variant

  public init(@ViewBuilder content: () -> Content) {
    self.content = content()
  }

  public var body: some View {
    content
      .fixedSize(horizontal: variant == .separator, vertical: true)
      .frame(maxWidth: variant == .separator ? nil : .infinity, alignment: .leading)
  }
}

extension MarkerContent where Content == Text {
  public init(_ text: LocalizedStringKey) {
    self.init { Text(text) }
  }

  public init(verbatim text: String) {
    self.init { Text(verbatim: text) }
  }
}
```

- `swift/Components/TextVariant.swift`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/swiftui/Sources/EsuissUI/Components/TextVariant.swift)

4. Add the files to your app target.

**Jetpack Compose**

1. Set `minSdk` to 24 or higher and add the dependencies:

```kotlin title="build.gradle.kts"
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
```

2. Copy the theme files every view uses:

- `kotlin/theme/Theme.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Theme.kt)

- `kotlin/theme/Tokens.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/theme/Tokens.kt)

3. Copy the source and every file it uses:

```kt title="kotlin/components/Marker.kt"
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography

enum class MarkerVariant { Default, Separator, Border }

private val LocalMarkerVariant = staticCompositionLocalOf { MarkerVariant.Default }

@Composable
fun Marker(
    modifier: Modifier = Modifier,
    variant: MarkerVariant = MarkerVariant.Default,
    content: @Composable RowScope.() -> Unit,
) {
    val colors = EsuissTheme.colors
    val border = if (variant == MarkerVariant.Border) {
        Modifier
            .drawBehind {
                val stroke = 1.dp.toPx()
                drawRect(colors.border, Offset(0f, size.height - stroke), Size(size.width, stroke))
            }
            .padding(bottom = 8.dp)
    } else {
        Modifier
    }
    Row(
        modifier = modifier
            .fillMaxWidth()
            .heightIn(min = 16.dp)
            .then(border)
            .semantics(mergeDescendants = true) {},
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        CompositionLocalProvider(
            LocalMarkerVariant provides variant,
            LocalContentStyle provides Typography.sm.copy(color = colors.mutedForeground),
            LocalContentColor provides colors.mutedForeground,
            LocalIconSize provides 16.dp,
        ) {
            if (variant == MarkerVariant.Separator) {
                MarkerLine(Modifier.padding(end = 4.dp))
            }
            content()
            if (variant == MarkerVariant.Separator) {
                MarkerLine(Modifier.padding(start = 4.dp))
            }
        }
    }
}

@Composable
fun Marker(
    text: String,
    modifier: Modifier = Modifier,
    variant: MarkerVariant = MarkerVariant.Default,
    icon: ImageVector? = null,
) {
    Marker(modifier, variant) {
        if (icon != null) MarkerIcon { Icon(icon, contentDescription = null) }
        MarkerContent(text)
    }
}

@Composable
private fun RowScope.MarkerLine(modifier: Modifier) {
    Box(
        modifier = Modifier
            .weight(1f)
            .then(modifier)
            .height(1.dp)
            .background(EsuissTheme.colors.border),
    )
}

@Composable
fun MarkerIcon(
    modifier: Modifier = Modifier,
    content: @Composable BoxScope.() -> Unit,
) {
    Box(
        modifier = modifier.size(16.dp).clearAndSetSemantics {},
        contentAlignment = Alignment.Center,
    ) {
        CompositionLocalProvider(LocalIconSize provides 16.dp) {
            content()
        }
    }
}

@Composable
fun RowScope.MarkerContent(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    val separator = LocalMarkerVariant.current == MarkerVariant.Separator
    Box(
        modifier = if (separator) modifier else modifier.weight(1f, fill = false),
        contentAlignment = if (separator) Alignment.Center else Alignment.CenterStart,
    ) {
        content()
    }
}

@Composable
fun RowScope.MarkerContent(text: String, modifier: Modifier = Modifier) {
    val separator = LocalMarkerVariant.current == MarkerVariant.Separator
    MarkerContent(modifier) {
        BasicText(
            text,
            style = LocalContentStyle.current.copy(textAlign = if (separator) TextAlign.Center else TextAlign.Start),
        )
    }
}
```

- `kotlin/components/Content.kt`: [source](https://github.com/e-suiss/ui/blob/main/packages/mobile/kotlin/ui/src/main/kotlin/com/esuiss/ui/components/Content.kt)

- `kotlin/components/Icon.kt`: see [Icon](https://ui.suiss.app/docs/components/icon.md)

- `kotlin/components/Separator.kt`: see [Separator](https://ui.suiss.app/docs/components/separator.md)

4. Update the `com.esuiss.ui` package names to match your project.

## Usage

```tsx
import { Marker, MarkerIcon, MarkerContent } from "@/components/ui/marker"
```

```tsx
<View className="gap-8">
  <Marker>
    <MarkerIcon>
      <Icon as={InfoIcon} />
    </MarkerIcon>
    <MarkerContent>Messages are end-to-end encrypted.</MarkerContent>
  </Marker>
  <Marker variant="separator">
    <MarkerContent>Today</MarkerContent>
  </Marker>
  <Marker variant="border">
    <MarkerIcon>
      <Icon as={CalendarBlankIcon} />
    </MarkerIcon>
    <MarkerContent>Upcoming</MarkerContent>
  </Marker>
</View>
```

Put the text in `MarkerContent` and an `Icon` in `MarkerIcon`. Text and icons inside the marker pick up the `text-muted-foreground` color and size through context.

Pass a `render` element to `Marker` to render it as another component instead of a `View`.

## API Reference

### Marker

Row that sets the variant and the text and icon styles.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "separator" \| "border"` | `"default"` | `default`, `separator` or `border`. |
| `render` | `RenderProp<Record<string, unknown>, Record<string, never>>` | - | Element to render in place of the default `View`. |

Also accepts the props of `ViewProps`.

### MarkerIcon

Fixed-size slot for an icon.

Also accepts the props of `ViewProps`.

### MarkerContent

Marker text, centered in the `separator` variant.

Also accepts the props of `React.ComponentProps<typeof Text>`.

## Accessibility

- `MarkerIcon` and the separator lines are hidden from screen readers, so only the text is read.
- If the icon carries meaning, include it in the text.

## Notes

- Text uses `text-sm text-muted-foreground` and icons `size-4`.
- Lines and the bottom border use `bg-border` and `border-border`.
- Light and dark colors follow the system appearance.

