diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01f51b3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.css] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8cf0c..b12005a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2026-04-29] + +### Added + +- `odyssey_toggle_button` now accepts a `layout` attr (`:inline` default, `:stacked` for vertical radio-style list). In stacked mode, options render as a full-width vertical list — each row has a radio circle SVG (filled blue when selected, outlined when not) with the option name to the right and an optional description beneath it. Options support a `description` key for stacked mode. +- Added `.ody-neutral-{0,50,100,200,250,300,350,400}` CSS helper classes for applying Odyssey neutral palette colors to text. +- Added complete set of `.ody-*` CSS classes covering H1–H3, P1–P3, Mobile Footer, and link variants across regular/medium/bold weights. + ## [2026-04-27] ### Added diff --git a/assets/components/core-components.css b/assets/components/core-components.css index a907966..ecc19c4 100644 --- a/assets/components/core-components.css +++ b/assets/components/core-components.css @@ -68,8 +68,20 @@ /* Override DaisyUI's disabled background colors */ /* For default values we have default colors. Note: ghost and link has not yet been implemented */ - .btn[disabled]:not(.btn-link, .btn-ghost, .btn-secondary, .btn-tertiary, .btn-danger), - .btn.disabled:not(.btn-link, .btn-ghost, .btn-secondary, .btn-tertiary, .btn-danger) { + .btn[disabled]:not( + .btn-link, + .btn-ghost, + .btn-secondary, + .btn-tertiary, + .btn-danger + ), + .btn.disabled:not( + .btn-link, + .btn-ghost, + .btn-secondary, + .btn-tertiary, + .btn-danger + ) { @apply bg-interaction-300 hover:bg-interaction-300; } @@ -171,24 +183,24 @@ table.table.table-zebra td { } /* Checkbox fieldset styling - only for labels containing checkboxes */ - .fieldset label:has(input[type='checkbox']) { + .fieldset label:has(input[type="checkbox"]) { @apply inline-flex items-center w-fit text-neutrals-300 hover:text-neutrals-400 focus:text-neutrals-400; } - .fieldset label:has(input[type='checkbox']) input[type='checkbox'] { + .fieldset label:has(input[type="checkbox"]) input[type="checkbox"] { @apply flex-shrink-0; } - .fieldset label:has(input[type='checkbox']) span.label { + .fieldset label:has(input[type="checkbox"]) span.label { @apply text-base; position: relative; padding-left: 1.5rem; /* Space for checkbox */ } .fieldset - label:has(input[type='checkbox']) + label:has(input[type="checkbox"]) span.label - input[type='checkbox'] { + input[type="checkbox"] { position: absolute; left: 0; top: 50%; @@ -249,3 +261,322 @@ table.table.table-zebra td { .toast .alert span.hero-exclamation-circle { display: none !important; } + +/* Odyssey Neutral Text Colors */ + +.ody-neutral-0 { + color: #ffffff; +} +.ody-neutral-50 { + color: #fafaff; +} +.ody-neutral-100 { + color: #f2f3fa; +} +.ody-neutral-200 { + color: #dadce7; +} +.ody-neutral-250 { + color: #9ca1b9; +} +.ody-neutral-300 { + color: #656a81; +} +.ody-neutral-350 { + color: #414159; +} +.ody-neutral-400 { + color: #202024; +} + +/* Odyssey Text Styles */ + +.ody-p1 { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 150%; +} + +.ody-p1-medium { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 150%; /* 24px */ +} + +.ody-p1-bold { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 700; + line-height: 150%; /* 24px */ +} + +.ody-h3-medium { + font-family: "IBM Plex Sans"; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: 125%; /* 25px */ +} + +/* Typography/H1/Regular */ +.ody-h1 { + font-family: "IBM Plex Sans"; + font-size: 28px; + font-style: normal; + font-weight: 400; + line-height: 125%; +} + +/* Typography/H1/Medium */ +.ody-h1-medium { + font-family: "IBM Plex Sans"; + font-size: 28px; + font-style: normal; + font-weight: 500; + line-height: 125%; +} + +/* Typography/H1/Bold */ +.ody-h1-bold { + font-family: "IBM Plex Sans"; + font-size: 28px; + font-style: normal; + font-weight: 700; + line-height: 125%; +} + +/* Typography/H2/Regular */ +.ody-h2 { + font-family: "IBM Plex Sans"; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: 125%; +} + +/* Typography/H2/Medium */ +.ody-h2-medium { + font-family: "IBM Plex Sans"; + font-size: 24px; + font-style: normal; + font-weight: 500; + line-height: 125%; +} + +/* Typography/H2/Bold */ +.ody-h2-bold { + font-family: "IBM Plex Sans"; + font-size: 24px; + font-style: normal; + font-weight: 700; + line-height: 125%; +} + +/* Typography/H3/Regular */ +.ody-h3 { + font-family: "IBM Plex Sans"; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 125%; +} + +/* Typography/H3/Bold */ +.ody-h3-bold { + font-family: "IBM Plex Sans"; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: 125%; +} + +/* Typography/P2 Small/Regular */ +.ody-p2 { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 150%; +} + +/* Typography/P2 Small/Medium */ +.ody-p2-medium { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 150%; +} + +/* Typography/P2 Small/Bold */ +.ody-p2-bold { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 700; + line-height: 150%; +} + +/* Typography/P3 X-Small/Regular */ +.ody-p3 { + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 125%; +} + +/* Typography/P3 X-Small/Medium */ +.ody-p3-medium { + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 125%; +} + +/* Typography/P3 X-Small/Bold */ +.ody-p3-bold { + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 125%; +} + +/* Typography/Mobile Footer/Regular */ +.ody-mobile-footer { + font-family: "IBM Plex Sans"; + font-size: 10px; + font-style: normal; + font-weight: 400; + line-height: 125%; +} + +/* Typography/Mobile Footer/Medium */ +.ody-mobile-footer-medium { + font-family: "IBM Plex Sans"; + font-size: 10px; + font-style: normal; + font-weight: 500; + line-height: 125%; +} + +/* Typography/Mobile Footer/Bold */ +.ody-mobile-footer-bold { + font-family: "IBM Plex Sans"; + font-size: 10px; + font-style: normal; + font-weight: 700; + line-height: 125%; +} + +/* Typography/P1 Base/Regular Link */ +.ody-p1-link { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P1 Base/Medium Link */ +.ody-p1-medium-link { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P1 Base/Bold Link */ +.ody-p1-bold-link { + font-family: "IBM Plex Sans"; + font-size: 16px; + font-style: normal; + font-weight: 700; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P2 Small/Regular Link */ +.ody-p2-link { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P2 Small/Medium Link */ +.ody-p2-medium-link { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P2 Small/Bold Link */ +.ody-p2-bold-link { + font-family: "IBM Plex Sans"; + font-size: 14px; + font-style: normal; + font-weight: 700; + line-height: 150%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} + +/* Typography/P3 X-Small/Link */ +.ody-p3-link { + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 125%; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-skip-ink: none; + text-decoration-thickness: auto; + text-underline-offset: auto; + text-underline-position: from-font; +} diff --git a/demo/lib/demo_web/live/odyssey_showcase_live.ex b/demo/lib/demo_web/live/odyssey_showcase_live.ex index 0e13fda..a8bf663 100644 --- a/demo/lib/demo_web/live/odyssey_showcase_live.ex +++ b/demo/lib/demo_web/live/odyssey_showcase_live.ex @@ -32,6 +32,7 @@ defmodule DemoWeb.OdysseyShowcaseLive do |> assign(:status_selection, "Active") |> assign(:size_selection, "Medium") |> assign(:channel_selection, :email) + |> assign(:stacked_selection, :pre_tax) |> assign(:standalone_select_item, nil) |> assign(:form_data, form_data) |> assign(:form, to_form(form_data, as: "form")) @@ -96,6 +97,11 @@ defmodule DemoWeb.OdysseyShowcaseLive do {:noreply, socket} end + @impl true + def handle_event("change_commission_type", %{"value" => option}, socket) do + {:noreply, assign(socket, :stacked_selection, option)} + end + @impl true def handle_event("validate", %{"form" => form_params}, socket) do socket = @@ -326,6 +332,22 @@ defmodule DemoWeb.OdysseyShowcaseLive do tooltip="Select how often you want to receive reports" /> + +
Selected: {inspect(@stacked_selection)}
+