Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,184 changes: 7 additions & 1,177 deletions layouts.lint-baseline.json

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions src/components/button/Button.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@
.button:disabled,
.button[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
background-color: color-mix(
in oklab,
var(--button-bg),
transparent 50%
);
border-color: color-mix(
in oklab,
var(--button-border),
transparent 50%
);
}

.button:active,
Expand Down Expand Up @@ -128,7 +137,6 @@
}
.button--state-disabled {
cursor: not-allowed;
opacity: 0.55;
}
.button--state-hidden {
display: none;
Expand Down
1 change: 0 additions & 1 deletion src/components/color-wheel-flower/ColorWheelFlower.css
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@
inset: 0;
border-radius: var(--radius-pill, 9999px);
border: 2px solid rgb(255 255 255 / 100%);
mix-blend-mode: overlay;
pointer-events: none;
opacity: 0;
transition:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ const ColorWheelFlower: Layout<typeof componentRecipe, ColorWheelFlowerProps> =
isPulsing() && CLASSES.highlight.pulsing,
),
}}
style={{ "border-color": item.hex }}
/>
</div>
</div>
Expand Down
106 changes: 70 additions & 36 deletions src/components/language-switcher/LanguageSwitcher.layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import "./LanguageSwitcher.css";
import { type Component, For, omit } from "solid-js";
import { createSignal, For, omit, Show } from "solid-js";
import type { Layout } from "../../lib/layouts";
import { twMerge } from "../../lib/twMerge";
import type { DropdownAlign } from "../dropdown";
import Dropdown, { type DropdownAlign } from "../dropdown";
import Icon from "../icon";
import type { UIBaseProps } from "../vocabulary";
import type { I18nStore } from "./createI18n";
import { CLASSES } from "./LanguageSwitcher.recipe";
import type { Layout } from "../../lib/layouts";
import { componentRecipe } from "./LanguageSwitcher.recipe";
import { CLASSES, componentRecipe } from "./LanguageSwitcher.recipe";

export interface LanguageSwitcherProps extends UIBaseProps {
/** Stable identity for the native language control. */
/** Stable identity for the language control. */
id?: string;
/**
* The i18n store to use for language state
Expand Down Expand Up @@ -42,7 +42,10 @@ export interface LanguageSwitcherProps extends UIBaseProps {
onLanguageChange?: (lang: string) => void;
}

const LanguageSwitcher: Layout<typeof componentRecipe, LanguageSwitcherProps> = () => {
const LanguageSwitcher: Layout<
typeof componentRecipe,
LanguageSwitcherProps
> = () => {
const others = omit(
props,
"i18n",
Expand All @@ -57,46 +60,77 @@ const LanguageSwitcher: Layout<typeof componentRecipe, LanguageSwitcherProps> =
);

const currentLanguageName = () => props.i18n.languageNames[props.i18n.locale];
const isSelected = (lang: string) => props.i18n.locale === lang;
const [open, setOpen] = createSignal(false);
const handleSelect = async (lang: string) => {
await props.i18n.setLocale(lang);
props.onLanguageChange?.(lang);
};

const classes = () => twMerge(CLASSES.base, CLASSES.trigger, props.class);

const move = (direction: -1 | 1): void => {
const languages = props.i18n.languages;
const current = languages.findIndex((language) => language.code === props.i18n.locale);
const next = (Math.max(0, current) + direction + languages.length) % languages.length;
const language = languages[next];
if (language) void handleSelect(language.code);
};
const classes = () => twMerge(CLASSES.base, props.class);

return (
<select
<Dropdown.Root
{...others}
{...{ class: classes() }}
style={props.style}
value={props.i18n.locale}
open={open()}
onOpenChange={setOpen}
disabled={props.i18n.isLoading}
aria-busy={props.i18n.isLoading ? "true" : undefined}
aria-label={`${props.currentLanguageLabel ?? "Current language"}: ${currentLanguageName()}`}
title={props.optionsLabel ?? local["aria-label"] ?? "Language selector"}
onChange={(event) => void handleSelect(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
move(1);
} else if (event.key === "ArrowUp") {
event.preventDefault();
move(-1);
}
}}
style={props.style}
>
<For each={props.i18n.languages}>
{(lang) => <option value={lang.code}>{lang.name}</option>}
</For>
</select>
<Dropdown.Trigger
{...{ class: CLASSES.trigger }}
aria-busy={props.i18n.isLoading ? "true" : undefined}
aria-label={`${props.currentLanguageLabel ?? "Current language"}: ${currentLanguageName()}`}
title={props.optionsLabel ?? local["aria-label"] ?? "Language selector"}
>
<Show
when={!props.i18n.isLoading}
fallback={
<Icon
src="icon-[mdi--loading]"
{...{ class: CLASSES.loadingIcon }}
width={16}
height={16}
aria-label={props.loadingLabel ?? "Loading language"}
/>
}
>
<span
data-slot="language-current"
{...{ class: CLASSES.locale }}
aria-hidden="true"
>
{props.i18n.locale.toUpperCase()}
</span>
</Show>
</Dropdown.Trigger>

<Dropdown.Menu
{...{ class: CLASSES.menu }}
align={props.align}
aria-label={props.optionsLabel ?? "Language options"}
>
<For each={props.i18n.languages}>
{(lang) => (
<Dropdown.Item
id={props.id ? `${props.id}-option-${lang.code}` : undefined}
onClick={() => void handleSelect(lang.code)}
aria-label={lang.name}
{...{
class: twMerge(
CLASSES.item,
isSelected(lang.code) && CLASSES.itemSelected,
),
}}
aria-current={isSelected(lang.code) ? "true" : undefined}
>
{lang.name}
</Dropdown.Item>
)}
</For>
</Dropdown.Menu>
</Dropdown.Root>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export const CLASSES = {
item: "language-switcher__item",
itemSelected: "language-switcher__item--selected",
} as const;
export const componentRecipe = recipe({component:"language-switcher",slots:{"root":{},},});
export const componentRecipe = recipe({component:"language-switcher",slots:{"root":{},"language-current":{},},});
3 changes: 0 additions & 3 deletions src/components/language-switcher/createI18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,8 @@ export function createI18n(options: I18nOptions): I18nStore {
setIsLoading(true);

try {
const minDelay = new Promise((resolve) => setTimeout(resolve, 100));
let data: Record<string, unknown>;

await minDelay;

if (
lang === defaultLanguage &&
Object.keys(initialTranslations).length > 0
Expand Down
18 changes: 14 additions & 4 deletions tests/ps-qa/dropdown.ron
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,23 @@
subject: "fixture",
expect: Paints,
),
(
id: "dropdown-selected-value-paints",
group: "dropdown",
what: "Dropdown paints the selected value a person reads",
open: None,
hover: None,
click: None,
subject: "#qa-dropdown--trigger",
expect: InteriorInk,
),
(
id: "dropdown-opens",
group: "dropdown",
what: "the Dropdown menu reaches the renderer as an addressable item",
open: None,
hover: None,
prepare: Some("Effort:"),
prepare: Some("#qa-dropdown--trigger"),
prepare_unless: Some("menuitem:low"),
settle_after_ms: 600,
click: None,
Expand All @@ -43,11 +53,11 @@
what: "choosing another value changes what the Dropdown trigger reads",
open: None,
hover: None,
prepare: Some("Effort:"),
prepare: Some("#qa-dropdown--trigger"),
prepare_unless: Some("menuitem:low"),
settle_after_ms: 600,
click: Some("menuitem:high"),
subject: "button:Effort:",
subject: "#qa-dropdown--trigger",
expect: NameChanges,
),
(
Expand All @@ -56,7 +66,7 @@
what: "Escape closes the Dropdown menu without choosing",
open: None,
hover: None,
prepare: Some("Effort:"),
prepare: Some("#qa-dropdown--trigger"),
prepare_unless: Some("menuitem:low"),
settle_after_ms: 600,
click: None,
Expand Down
49 changes: 45 additions & 4 deletions tests/ps-qa/language-switcher.ron
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,54 @@
expect: Paints,
),
(
id: "language-switcher-paints",
id: "language-switcher-selected-value-paints",
group: "language-switcher",
what: "the LanguageSwitcher reaches the renderer with a box",
what: "LanguageSwitcher paints the selected value a person reads",
open: None,
hover: None,
click: None,
subject: "LanguageSwitcher",
expect: Paints,
subject: "#qa-language-switcher--trigger",
expect: InteriorInk,
),
(
id: "language-switcher-opens",
group: "language-switcher",
what: "the LanguageSwitcher menu reaches the renderer as an addressable item",
open: None,
hover: None,
prepare: Some("#qa-language-switcher--trigger"),
prepare_unless: Some("menuitem:Chinese"),
settle_after_ms: 600,
click: None,
subject: "menuitem:Chinese",
expect: PaintsNamed,
),
(
id: "language-switcher-changes",
group: "language-switcher",
what: "choosing another value changes what the LanguageSwitcher trigger reads",
open: None,
hover: None,
prepare: Some("#qa-language-switcher--trigger"),
prepare_unless: Some("menuitem:Chinese"),
settle_after_ms: 600,
click: Some("menuitem:Chinese"),
subject: "#qa-language-switcher--trigger",
expect: NameChanges,
),
(
id: "language-switcher-escape-closes",
group: "language-switcher",
what: "Escape closes the LanguageSwitcher menu without choosing",
open: None,
hover: None,
prepare: Some("#qa-language-switcher--trigger"),
prepare_unless: Some("menuitem:Chinese"),
settle_after_ms: 600,
click: None,
key: Some("Escape"),
key_on: Some("menuitem:Chinese"),
subject: "menuitem:Chinese",
expect: Vanishes,
),
]
18 changes: 14 additions & 4 deletions tests/ps-qa/select.ron
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
subject: "fixture",
expect: Paints,
),
(
id: "select-selected-value-paints",
group: "select",
what: "Select paints the selected value a person reads",
open: None,
hover: None,
click: None,
subject: "#qa-select-trigger",
expect: InteriorInk,
),
(
id: "select-closed-content-is-absent",
group: "select",
Expand All @@ -40,7 +50,7 @@
what: "the Select menu reaches the renderer as an addressable item",
open: None,
hover: None,
prepare: Some("Session:"),
prepare: Some("#qa-select-trigger"),
prepare_unless: Some("option:first fixture"),
settle_after_ms: 600,
click: None,
Expand All @@ -53,11 +63,11 @@
what: "choosing another value changes what the Select trigger reads",
open: None,
hover: None,
prepare: Some("Session:"),
prepare: Some("#qa-select-trigger"),
prepare_unless: Some("option:first fixture"),
settle_after_ms: 600,
click: Some("option:second fixture"),
subject: "button:Session:",
subject: "#qa-select-trigger",
expect: NameChanges,
),
(
Expand All @@ -66,7 +76,7 @@
what: "Escape closes the Select menu without choosing",
open: None,
hover: None,
prepare: Some("Session:"),
prepare: Some("#qa-select-trigger"),
prepare_unless: Some("option:first fixture"),
settle_after_ms: 600,
click: None,
Expand Down
11 changes: 10 additions & 1 deletion tests/qa-harness/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export type ComponentSpec = {
subject?: string;
/** Role of that subject, so a check cannot assert on the wrong node. */
subjectRole?: string;
/** Stable selector for the subject when its authored DOM id is the contract. */
subjectSelector?: string;
/** For `value`/`mode`/`tabs`: the option, item or tab to activate. */
activate?: string;
/** Node that proves an overlay/editor opened or a tab panel changed. */
Expand Down Expand Up @@ -245,6 +247,7 @@ export const COMPONENTS: ComponentSpec[] = [
// failed before reaching its own assertion.
subject: "Effort:",
subjectRole: "button",
subjectSelector: "#qa-dropdown--trigger",
activate: "menuitem:high",
opens: "menuitem:low",
options: [
Expand Down Expand Up @@ -288,7 +291,12 @@ export const COMPONENTS: ComponentSpec[] = [
{
id: "language-switcher",
component: "LanguageSwitcher",
kind: "display",
kind: "value",
subject: "Current language: English",
subjectRole: "button",
subjectSelector: "#qa-language-switcher--trigger",
activate: "menuitem:Chinese",
opens: "menuitem:Chinese",
},
{
id: "link",
Expand Down Expand Up @@ -399,6 +407,7 @@ export const COMPONENTS: ComponentSpec[] = [
// while the option nodes themselves stay out of the renderer until then.
subject: "Session:",
subjectRole: "button",
subjectSelector: "#qa-select-trigger",
activate: "option:second fixture",
opens: "option:first fixture",
closedContent: "option:first fixture",
Expand Down
Loading
Loading