diff --git a/docs/design-system/badges.html b/docs/design-system/badges.html index 836f0a1..46b927e 100644 --- a/docs/design-system/badges.html +++ b/docs/design-system/badges.html @@ -8,6 +8,6 @@ -

Design System

Badges

Semantic status labels for todo, in-progress, success, and destructive states.

Status set

The four semantic badge tones used across the app.

À faire
En cours
Terminé
Erreur
@ui.Badge(ui.BadgeProps{Label: "En cours", Variant: ui.BadgeVariantWarning})
+

Design System

Badges

Semantic status labels for todo, in-progress, success, and destructive states.

Status set

The four semantic badge tones used across the app.

À faire
En cours
Terminé
Erreur
@ui.Badge(ui.BadgeProps{Label: "En cours", Variant: ui.BadgeVariantWarning})
diff --git a/docs/design-system/buttons.html b/docs/design-system/buttons.html index 3ab955f..b6c36fb 100644 --- a/docs/design-system/buttons.html +++ b/docs/design-system/buttons.html @@ -8,7 +8,7 @@ -

Design System

Buttons

Primary, secondary, ghost, and destructive actions built from shared templ primitives.

Default solid action

Used for the main action in a page section or modal footer.

@ui.Button(ui.ButtonProps{
+

Design System

Buttons

Primary, secondary, ghost, and destructive actions built from shared templ primitives.

Default solid action

Used for the main action in a page section or modal footer.

@ui.Button(ui.ButtonProps{
 	Label:   "Nouveau projet",
 	Variant: ui.ButtonVariantDefault,
 	Size:    ui.SizeMD,
diff --git a/docs/design-system/cards.html b/docs/design-system/cards.html
index 4b7f232..a34f364 100644
--- a/docs/design-system/cards.html
+++ b/docs/design-system/cards.html
@@ -8,7 +8,7 @@
   
 
 
-

Design System

Cards

Reusable bordered surfaces with optional header, body, and footer regions.

Surface card

Generic elevated surface with optional header and footer.

Header
Body
@ui.Card(ui.CardProps{
+

Design System

Cards

Reusable bordered surfaces with optional header, body, and footer regions.

Surface card

Generic elevated surface with optional header and footer.

Header
Body
@ui.Card(ui.CardProps{
 	Header: textComponent("Header"),
 	Body:   textComponent("Body"),
 	Footer: textComponent("Footer"),
diff --git a/docs/design-system/empty-states.html b/docs/design-system/empty-states.html
index 1aa9494..e96bcc5 100644
--- a/docs/design-system/empty-states.html
+++ b/docs/design-system/empty-states.html
@@ -8,7 +8,7 @@
   
 
 
-

Design System

Empty States

Centered fallback messaging with optional icon and action.

Centered empty state

Used when a list has no rows yet and the next action should stay obvious.

Aucun projet trouvé

Créez votre premier projet

@ui.EmptyState(ui.EmptyStateProps{
+

Design System

Empty States

Centered fallback messaging with optional icon and action.

Centered empty state

Used when a list has no rows yet and the next action should stay obvious.

Aucun projet trouvé

Créez votre premier projet

@ui.EmptyState(ui.EmptyStateProps{
 	Title:       "Aucun projet trouvé",
 	Description: "Créez votre premier projet",
 	Icon:        ui.UIIcon("grid3x3"),
diff --git a/docs/design-system/form-fields.html b/docs/design-system/form-fields.html
index 8efee19..3acfe7d 100644
--- a/docs/design-system/form-fields.html
+++ b/docs/design-system/form-fields.html
@@ -8,7 +8,7 @@
   
 
 
-

Design System

Form Fields

Labeled controls with optional hint and error messaging.

Field with validation

Wraps a control with label and inline error feedback.

Le nom est requis

@ui.FormField(ui.FormFieldProps{
+

Design System

Form Fields

Labeled controls with optional hint and error messaging.

Field with validation

Wraps a control with label and inline error feedback.

Le nom est requis

@ui.FormField(ui.FormFieldProps{
 	Label: "Nom",
 	For:   "catalog-name",
 	Field: ui.Input(ui.InputProps{
diff --git a/docs/design-system/icon-buttons.html b/docs/design-system/icon-buttons.html
index c2c632a..48b0a0f 100644
--- a/docs/design-system/icon-buttons.html
+++ b/docs/design-system/icon-buttons.html
@@ -8,7 +8,7 @@
   
 
 
-

Design System

Icon Buttons

Compact icon-only actions for destructive and neutral controls.

Borderless destructive action

Used for delete controls inside project cards and list rows.

@ui.IconButton(ui.IconButtonProps{
+

Design System

Icon Buttons

Compact icon-only actions for destructive and neutral controls.

Borderless destructive action

Used for delete controls inside project cards and list rows.

@ui.IconButton(ui.IconButtonProps{
 	Label:   "Supprimer le projet",
 	Icon:    "trash",
 	Variant: ui.IconButtonVariantDanger,
diff --git a/docs/design-system/index.html b/docs/design-system/index.html
index ec86913..cbb2557 100644
--- a/docs/design-system/index.html
+++ b/docs/design-system/index.html
@@ -8,6 +8,6 @@
   
 
 
-

Design System

Component Catalog

Static documentation generated from the same templ primitives used by the Go application.

+

Design System

Component Catalog

Static documentation generated from the same templ primitives used by the Go application.

diff --git a/docs/design-system/inputs.html b/docs/design-system/inputs.html index 324f30a..7c72834 100644 --- a/docs/design-system/inputs.html +++ b/docs/design-system/inputs.html @@ -8,7 +8,7 @@ -

Design System

Inputs

Shared single-line and multiline text controls.

Text input

Single-line input for names, titles, and short labels.

@ui.Input(ui.InputProps{
+

Design System

Inputs

Shared single-line and multiline text controls.

Text input

Single-line input for names, titles, and short labels.

@ui.Input(ui.InputProps{
 	Name:        "name",
 	Value:       "Projet Atlas",
 	Placeholder: "Nom du projet",
diff --git a/docs/design-system/modals.html b/docs/design-system/modals.html
index a543d12..135b58e 100644
--- a/docs/design-system/modals.html
+++ b/docs/design-system/modals.html
@@ -8,7 +8,7 @@
   
 
 
-

Design System

Modals

Shared modal shell for focused create, edit, and confirm flows.

Create modal

Shared modal shell with a form body and action footer.

Créer un projet

@ui.Modal(ui.ModalProps{
+

Design System

Modals

Shared modal shell for focused create, edit, and confirm flows.

Create modal

Shared modal shell with a form body and action footer.

Créer un projet

@ui.Modal(ui.ModalProps{
 	Title: "Créer un projet",
 	Body: ui.FormField(...),
 	Actions: ui.Button(...),
diff --git a/docs/design-system/selects.html b/docs/design-system/selects.html
new file mode 100644
index 0000000..13ad8eb
--- /dev/null
+++ b/docs/design-system/selects.html
@@ -0,0 +1,302 @@
+
+
+
+  
+  
+  Selects
+  
+  
+
+
+

Design System

Selects

Single and multi-value select controls with a shared server-rendered shell.

Single select

Single-choice dropdown with the shared input shell and custom chevron.

@ui.Select(ui.SelectProps{
+	Name:        "status",
+	Placeholder: "Select a status",
+	Value:       "in-progress",
+	Options: []ui.SelectOption{
+		{Value: "todo", Label: "To do"},
+		{Value: "in-progress", Label: "In progress"},
+		{Value: "done", Label: "Done"},
+	},
+})

Multiple select

Multi-value selection with inline pills that stay form-compatible.

@ui.Select(ui.SelectProps{
+	Name:        "assignee_ids",
+	Placeholder: "Select multiple values",
+	Multiple:    true,
+	Values:      []string{"alice", "bob"},
+	Options: []ui.SelectOption{
+		{Value: "alice", Label: "Alice"},
+		{Value: "bob", Label: "Bob"},
+		{Value: "charlie", Label: "Charlie"},
+	},
+})
+ + diff --git a/docs/design-system/spacing.html b/docs/design-system/spacing.html index c5d8028..6a79ae1 100644 --- a/docs/design-system/spacing.html +++ b/docs/design-system/spacing.html @@ -8,6 +8,6 @@ -

Design System

Spacing

Fixed horizontal and vertical spacer primitives for composing gaps between UI components.

Horizontal spacing

Use SpaceX to insert fixed horizontal gaps between inline or row-aligned components.

@ui.SpaceX(ui.SpaceProps{Size: ui.SpacingStepLG})

Vertical spacing

Use SpaceY to insert fixed vertical gaps between stacked blocks.

Bloc 1
Bloc 2
@ui.SpaceY(ui.SpaceProps{Size: ui.SpacingStepMD})
+

Design System

Spacing

Fixed horizontal and vertical spacer primitives for composing gaps between UI components.

Horizontal spacing

Use SpaceX to insert fixed horizontal gaps between inline or row-aligned components.

@ui.SpaceX(ui.SpaceProps{Size: ui.SpacingStepLG})

Vertical spacing

Use SpaceY to insert fixed vertical gaps between stacked blocks.

Bloc 1
Bloc 2
@ui.SpaceY(ui.SpaceProps{Size: ui.SpacingStepMD})
diff --git a/docs/design-system/tables.html b/docs/design-system/tables.html index ae87212..b0d8c54 100644 --- a/docs/design-system/tables.html +++ b/docs/design-system/tables.html @@ -8,7 +8,7 @@ -

Design System

Tables

Shared table shell for server-rendered list views.

List shell

Shared wrapper for server-rendered resource tables.

ProjetStatut
Table ViewEn cours
@ui.Table(ui.TableProps{
+

Design System

Tables

Shared table shell for server-rendered list views.

List shell

Shared wrapper for server-rendered resource tables.

ProjetStatut
Table ViewEn cours
@ui.Table(ui.TableProps{
 	Head: TabloListHead(),
 	Body: TabloListBody(tablos),
 })
diff --git a/docs/design-system/tokens.html b/docs/design-system/tokens.html index 1784085..dafba33 100644 --- a/docs/design-system/tokens.html +++ b/docs/design-system/tokens.html @@ -8,6 +8,6 @@ -

Design System

Tokens

Semantic colors and status roles used by the Go design system.

Status tones

Shared semantic badges for info, warning, success, and danger states.

À faire
En cours
Terminé
Erreur
@ui.Badge(ui.BadgeProps{Label: "En cours", Variant: ui.BadgeVariantWarning})
+

Design System

Tokens

Semantic colors and status roles used by the Go design system.

Status tones

Shared semantic badges for info, warning, success, and danger states.

À faire
En cours
Terminé
Erreur
@ui.Badge(ui.BadgeProps{Label: "En cours", Variant: ui.BadgeVariantWarning})
diff --git a/go-backend/cmd/buildstyles/main.go b/go-backend/cmd/buildstyles/main.go index 4cdd36c..92ef6b5 100644 --- a/go-backend/cmd/buildstyles/main.go +++ b/go-backend/cmd/buildstyles/main.go @@ -15,6 +15,7 @@ var sourceOrder = []string{ filepath.Join("internal", "web", "ui", "badge.css"), filepath.Join("internal", "web", "ui", "icon-button.css"), filepath.Join("internal", "web", "ui", "input.css"), + filepath.Join("internal", "web", "ui", "select.css"), filepath.Join("internal", "web", "ui", "textarea.css"), filepath.Join("internal", "web", "ui", "form-field.css"), filepath.Join("internal", "web", "ui", "modal.css"), diff --git a/go-backend/internal/web/ui/catalog/examples.go b/go-backend/internal/web/ui/catalog/examples.go index 692767c..ae02a7d 100644 --- a/go-backend/internal/web/ui/catalog/examples.go +++ b/go-backend/internal/web/ui/catalog/examples.go @@ -193,6 +193,61 @@ func inputExamples() []Example { } } +func selectExamples() []Example { + return []Example{ + { + Title: "Single select", + Description: "Single-choice dropdown with the shared input shell and custom chevron.", + Preview: ui.Select(ui.SelectProps{ + Name: "status", + Placeholder: "Select a status", + Value: "in-progress", + Options: []ui.SelectOption{ + {Value: "todo", Label: "To do"}, + {Value: "in-progress", Label: "In progress"}, + {Value: "done", Label: "Done"}, + }, + }), + Snippet: `@ui.Select(ui.SelectProps{ + Name: "status", + Placeholder: "Select a status", + Value: "in-progress", + Options: []ui.SelectOption{ + {Value: "todo", Label: "To do"}, + {Value: "in-progress", Label: "In progress"}, + {Value: "done", Label: "Done"}, + }, +})`, + }, + { + Title: "Multiple select", + Description: "Multi-value selection with inline pills that stay form-compatible.", + Preview: ui.Select(ui.SelectProps{ + Name: "assignee_ids", + Placeholder: "Select multiple values", + Multiple: true, + Values: []string{"alice", "bob"}, + Options: []ui.SelectOption{ + {Value: "alice", Label: "Alice"}, + {Value: "bob", Label: "Bob"}, + {Value: "charlie", Label: "Charlie"}, + }, + }), + Snippet: `@ui.Select(ui.SelectProps{ + Name: "assignee_ids", + Placeholder: "Select multiple values", + Multiple: true, + Values: []string{"alice", "bob"}, + Options: []ui.SelectOption{ + {Value: "alice", Label: "Alice"}, + {Value: "bob", Label: "Bob"}, + {Value: "charlie", Label: "Charlie"}, + }, +})`, + }, + } +} + func formFieldExamples() []Example { return []Example{ { diff --git a/go-backend/internal/web/ui/catalog/pages.go b/go-backend/internal/web/ui/catalog/pages.go index 3d9f2bc..698dacf 100644 --- a/go-backend/internal/web/ui/catalog/pages.go +++ b/go-backend/internal/web/ui/catalog/pages.go @@ -48,6 +48,12 @@ func Pages() []Page { Description: "Shared single-line and multiline text controls.", Examples: inputExamples(), }, + { + Slug: "selects", + Title: "Selects", + Description: "Single and multi-value select controls with a shared server-rendered shell.", + Examples: selectExamples(), + }, { Slug: "form-fields", Title: "Form Fields", diff --git a/go-backend/internal/web/ui/select.css b/go-backend/internal/web/ui/select.css new file mode 100644 index 0000000..aa24802 --- /dev/null +++ b/go-backend/internal/web/ui/select.css @@ -0,0 +1,154 @@ +.ui-select { + position: relative; + width: 100%; +} + +.ui-select-native { + height: 0; + left: 0; + opacity: 0; + pointer-events: none; + position: absolute; + top: 0; + width: 0; +} + +.ui-select-control { + align-items: center; + appearance: none; + background: var(--color-surface-default); + border: 1px solid var(--color-border-default); + border-radius: 0.75rem; + color: var(--color-text-primary); + cursor: pointer; + display: flex; + gap: 0.75rem; + justify-content: space-between; + min-height: 44px; + padding: 0.55rem 0.75rem 0.55rem 0.95rem; + text-align: left; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + width: 100%; +} + +.ui-select-control:focus-visible { + border-color: var(--color-brand-focus); + box-shadow: 0 0 0 3px var(--color-focus-ring-strong); + outline: none; +} + +.ui-select-control:disabled { + color: var(--color-text-faint); + cursor: not-allowed; +} + +.ui-select-value-wrapper { + align-items: center; + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + gap: 0.35rem; + min-width: 0; +} + +.ui-select-placeholder { + color: var(--color-text-faint); +} + +.ui-select-chip { + background: var(--color-surface-muted); + border-radius: 999px; + color: var(--color-text-primary); + display: inline-flex; + font-size: 0.875rem; + line-height: 1; + padding: 0.35rem 0.6rem; +} + +.ui-select-arrow-zone { + align-items: center; + color: var(--color-text-secondary); + display: inline-flex; + flex: 0 0 auto; + justify-content: center; +} + +.ui-select-arrow-icon { + height: 1rem; + transition: transform 0.2s ease; + width: 1rem; +} + +.ui-select.is-open .ui-select-arrow-icon { + transform: rotate(180deg); +} + +.ui-select-menu { + background: var(--color-surface-default); + border: 1px solid var(--color-border-default); + border-radius: 0.9rem; + box-shadow: var(--shadow-surface-md); + display: grid; + gap: 0.25rem; + left: 0; + margin-top: 0.45rem; + max-height: 16rem; + overflow-y: auto; + padding: 0.4rem; + position: absolute; + right: 0; + top: 100%; + z-index: 30; +} + +.ui-select-menu[hidden] { + display: none; +} + +.ui-select-option { + align-items: center; + appearance: none; + background: transparent; + border: 0; + border-radius: 0.7rem; + color: var(--color-text-primary); + cursor: pointer; + display: flex; + font: inherit; + gap: 0.75rem; + justify-content: space-between; + padding: 0.7rem 0.8rem; + text-align: left; + width: 100%; +} + +.ui-select-option:hover, +.ui-select-option:focus-visible { + background: var(--color-surface-muted); + outline: none; +} + +.ui-select-option.is-selected { + background: var(--color-status-info-soft-bg); + color: var(--color-text-brand); +} + +.ui-select-option:disabled { + color: var(--color-text-faint); + cursor: not-allowed; +} + +.ui-select-option-text { + flex: 1 1 auto; +} + +.ui-select-option-check { + color: currentColor; + opacity: 0; +} + +.ui-select-option.is-selected .ui-select-option-check { + opacity: 1; +} diff --git a/go-backend/internal/web/ui/select.templ b/go-backend/internal/web/ui/select.templ new file mode 100644 index 0000000..befd82e --- /dev/null +++ b/go-backend/internal/web/ui/select.templ @@ -0,0 +1,251 @@ +package ui + +type SelectOption struct { + Value string + Label string + Disabled bool +} + +type SelectProps struct { + ID string + Name string + Placeholder string + Value string + Values []string + Multiple bool + Options []SelectOption + Attrs templ.Attributes +} + +templ Select(props SelectProps) { +
+ + + + +
+} diff --git a/go-backend/internal/web/ui/select_helpers.go b/go-backend/internal/web/ui/select_helpers.go new file mode 100644 index 0000000..5cf941e --- /dev/null +++ b/go-backend/internal/web/ui/select_helpers.go @@ -0,0 +1,104 @@ +package ui + +import ( + "strings" + + "github.com/a-h/templ" +) + +func selectPlaceholder(props SelectProps) string { + if props.Placeholder != "" { + return props.Placeholder + } + if props.Multiple { + return "Select values" + } + return "Select an option" +} + +func selectNativeID(id string, name string) string { + baseID := inputID(id, name) + if baseID == "" { + return "ui-select-native" + } + return baseID + "-native" +} + +func selectMenuID(id string, name string) string { + baseID := inputID(id, name) + if baseID == "" { + return "ui-select-menu" + } + return baseID + "-menu" +} + +func selectBoolData(value bool) string { + if value { + return "true" + } + return "false" +} + +func selectSelectedValues(props SelectProps) []string { + if props.Multiple { + return props.Values + } + if props.Value == "" { + return nil + } + return []string{props.Value} +} + +func selectOptionSelected(props SelectProps, value string) bool { + for _, selected := range selectSelectedValues(props) { + if selected == value { + return true + } + } + return false +} + +func selectSelectedLabels(props SelectProps) []string { + var labels []string + for _, option := range props.Options { + if selectOptionSelected(props, option.Value) { + labels = append(labels, option.Label) + } + } + return labels +} + +func selectSelectedLabel(props SelectProps) string { + return strings.Join(selectSelectedLabels(props), ", ") +} + +func selectMenuOptionClass(selected bool, disabled bool) string { + className := "ui-select-option" + if selected { + className += " is-selected" + } + if disabled { + className += " is-disabled" + } + return className +} + +func selectIsDisabled(attrs templ.Attributes) bool { + if attrs == nil { + return false + } + + value, ok := attrs["disabled"] + if !ok { + return false + } + + switch typed := value.(type) { + case bool: + return typed + case string: + return typed != "" && typed != "false" + default: + return true + } +} diff --git a/go-backend/internal/web/ui/select_templ.go b/go-backend/internal/web/ui/select_templ.go new file mode 100644 index 0000000..abd197c --- /dev/null +++ b/go-backend/internal/web/ui/select_templ.go @@ -0,0 +1,429 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package ui + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type SelectOption struct { + Value string + Label string + Disabled bool +} + +type SelectProps struct { + ID string + Name string + Placeholder string + Value string + Values []string + Multiple bool + Options []SelectOption + Attrs templ.Attributes +} + +func Select(props SelectProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/go-backend/static/styles.css b/go-backend/static/styles.css index 9b89ab0..667950e 100644 --- a/go-backend/static/styles.css +++ b/go-backend/static/styles.css @@ -665,6 +665,162 @@ input { outline: none; } +/* Source: internal/web/ui/select.css */ +.ui-select { + position: relative; + width: 100%; +} + +.ui-select-native { + height: 0; + left: 0; + opacity: 0; + pointer-events: none; + position: absolute; + top: 0; + width: 0; +} + +.ui-select-control { + align-items: center; + appearance: none; + background: var(--color-surface-default); + border: 1px solid var(--color-border-default); + border-radius: 0.75rem; + color: var(--color-text-primary); + cursor: pointer; + display: flex; + gap: 0.75rem; + justify-content: space-between; + min-height: 44px; + padding: 0.55rem 0.75rem 0.55rem 0.95rem; + text-align: left; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + width: 100%; +} + +.ui-select-control:focus-visible { + border-color: var(--color-brand-focus); + box-shadow: 0 0 0 3px var(--color-focus-ring-strong); + outline: none; +} + +.ui-select-control:disabled { + color: var(--color-text-faint); + cursor: not-allowed; +} + +.ui-select-value-wrapper { + align-items: center; + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + gap: 0.35rem; + min-width: 0; +} + +.ui-select-placeholder { + color: var(--color-text-faint); +} + +.ui-select-chip { + background: var(--color-surface-muted); + border-radius: 999px; + color: var(--color-text-primary); + display: inline-flex; + font-size: 0.875rem; + line-height: 1; + padding: 0.35rem 0.6rem; +} + +.ui-select-arrow-zone { + align-items: center; + color: var(--color-text-secondary); + display: inline-flex; + flex: 0 0 auto; + justify-content: center; +} + +.ui-select-arrow-icon { + height: 1rem; + transition: transform 0.2s ease; + width: 1rem; +} + +.ui-select.is-open .ui-select-arrow-icon { + transform: rotate(180deg); +} + +.ui-select-menu { + background: var(--color-surface-default); + border: 1px solid var(--color-border-default); + border-radius: 0.9rem; + box-shadow: var(--shadow-surface-md); + display: grid; + gap: 0.25rem; + left: 0; + margin-top: 0.45rem; + max-height: 16rem; + overflow-y: auto; + padding: 0.4rem; + position: absolute; + right: 0; + top: 100%; + z-index: 30; +} + +.ui-select-menu[hidden] { + display: none; +} + +.ui-select-option { + align-items: center; + appearance: none; + background: transparent; + border: 0; + border-radius: 0.7rem; + color: var(--color-text-primary); + cursor: pointer; + display: flex; + font: inherit; + gap: 0.75rem; + justify-content: space-between; + padding: 0.7rem 0.8rem; + text-align: left; + width: 100%; +} + +.ui-select-option:hover, +.ui-select-option:focus-visible { + background: var(--color-surface-muted); + outline: none; +} + +.ui-select-option.is-selected { + background: var(--color-status-info-soft-bg); + color: var(--color-text-brand); +} + +.ui-select-option:disabled { + color: var(--color-text-faint); + cursor: not-allowed; +} + +.ui-select-option-text { + flex: 1 1 auto; +} + +.ui-select-option-check { + color: currentColor; + opacity: 0; +} + +.ui-select-option.is-selected .ui-select-option-check { + opacity: 1; +} + /* Source: internal/web/ui/textarea.css */ .ui-textarea { appearance: none;