Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions .changeset/gold-llamas-restructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@ifrc-go/ui": major
---

Restructure the library around a declared component layer model (raw → generic → specific) and token specs.

- **Token specs**: `textSize`, `backgroundColor`, `borderRadius` and `boxShadow` join `spacing` as token-valued props resolved through static utility classes (no more runtime style injection; `useSpacingToken` is replaced by the pure `getSpacingClassName`). Ordinal specs support offsets.
- **Variant model**: generic components keep the two-axis `colorVariant` + `styleVariant` API; specific components expose a single curated `variant` (`Button`: `primary`/`secondary`/`tertiary`/`subtle`; `Alert`'s `type` and `ProgressBar`'s `colorVariant` are renamed to `variant`).
- **Boolean→token conversions**: `withBackground`/`withDarkBackground`/`withLightBackground` → `backgroundColor`; `withShadow`/`withoutShadow` → `boxShadow`.
- **Naming**: `variant` → `styleVariant` on Container/InputContainer/Heading; `containerRef` → `elementRef`; `layoutElementRef` removed.
- **Structural**: new generic `RawOutput`→`RawDisplay` value-rendering adapter (DataDisplay and KeyFigure rebuilt on it); `KeyFigureView` → `KeyFigureCard`; `RawFileInput` split into a headless primitive plus the new `FileInputButton`; `List` removed in favor of `ListView` + `RawList` + `DefaultMessage`.
- **HTML/ARIA-aligned renames**: `Modal`→`Dialog`, `Popup`→`Popover`, `InlineFrame`→`Iframe`, `Pager`→`Pagination`, `Label`→`DisplayLabel`, `ReducedListDisplay`→`TruncatedList`, `TextOutput`→`DataDisplay` (renders `<dl>`), the `*Output` value family → `*Display` (renders `<data>`/`<time>`), `InfoPopup`→`MoreInfo`, `DropdownMenu`→`Menu` (+ new generic `Dropdown`), `Chip` split into `ChipLayout`/`Tag`/`Selection`/`SelectionList` (replacing the `Dismissable*Output` trio), `DropdownMenuContext`→`MenuContext`.
- **Accessibility**: form-field label/`aria-describedby`/`aria-invalid`/`role="alert"` wiring in `InputContainer`; dialog/switch/live-region/disclosure/nav roles; and the value-output pattern (native `<data value>`/`<time dateTime>` for machine + tests, `role="img"`+`aria-label` exposing the full reading of abbreviated values to screen readers). Some widget patterns (combobox, tabs roving-tabindex, tooltip keyboard) are deferred and marked `FIXME(a11y-tier2)`.

See `packages/ui/MIGRATION.md` for exact call-site rewrites.
2 changes: 1 addition & 1 deletion app/src/App/PageError/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function PageError() {
<Button
name={undefined}
onClick={toggleFullErrorVisibility}
styleVariant="action"
variant="tertiary"
>
{fullErrorVisible ? strings.errorPageHide : strings.errorPageShowError}
</Button>
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/CatalogueInfoCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ function CatalogueInfoCard(props: Props) {
withHeaderBorder
headerDescription={description}
withPadding
withBackground
withShadow
backgroundColor="foreground"
boxShadow="md"
>
<ListView
layout="block"
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/DiffWrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { TextOutput } from '@ifrc-go/ui';
import { DataDisplay } from '@ifrc-go/ui';
import { useTranslation } from '@ifrc-go/ui/hooks';
import {
_cs,
Expand Down Expand Up @@ -68,7 +68,7 @@ function DiffWrapper<
{children}
{showPreviousValue
&& (
<TextOutput
<DataDisplay
className={styles.previousValue}
label={strings.previousValueLabel}
value={previousValue}
Expand Down
9 changes: 9 additions & 0 deletions app/src/components/DropdownMenuItem/i18n.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"namespace": "common",
"strings": {
"confirmation": "Confirmation",
"confirmMessage": "Are you sure you want to continue?",
"buttonCancel": "Cancel",
"buttonOk": "Ok"
}
}
161 changes: 143 additions & 18 deletions app/src/components/DropdownMenuItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import {
useCallback,
useContext,
useState,
} from 'react';
import {
Button,
ButtonLayout,
type ButtonProps,
ConfirmButton,
type ConfirmButtonProps,
Dialog,
ListView,
RawButton,
} from '@ifrc-go/ui';
import { DropdownMenuContext } from '@ifrc-go/ui/contexts';
import { MenuContext } from '@ifrc-go/ui/contexts';
import { useTranslation } from '@ifrc-go/ui/hooks';
import { isDefined } from '@togglecorp/fujs';

import Link, { type Props as LinkProps } from '#components/Link';

import i18n from './i18n.json';
import styles from './styles.module.css';

type CommonProp = {
persist?: boolean;
withoutFullWidth?: boolean;
}

type ButtonTypeProps<NAME> = Omit<ButtonProps<NAME>, 'type'> & {
type ButtonTypeProps<NAME> = Omit<ButtonProps<NAME>, 'type' | 'variant' | 'withFullWidth'> & {
type: 'button';
}

Expand All @@ -27,7 +35,7 @@ type LinkTypeProps = LinkProps & {
onClick?: never;
}

type ConfirmButtonTypeProps<NAME> = Omit<ConfirmButtonProps<NAME>, 'type'> & {
type ConfirmButtonTypeProps<NAME> = Omit<ConfirmButtonProps<NAME>, 'type' | 'variant' | 'withFullWidth'> & {
type: 'confirm-button',
}

Expand All @@ -43,7 +51,13 @@ function DropdownMenuItem<NAME>(props: Props<NAME>) {
...remainingProps
} = props;

const { setShowDropdown } = useContext(DropdownMenuContext);
const strings = useTranslation(i18n);
const { setShowDropdown } = useContext(MenuContext);
const [showConfirmation, setShowConfirmation] = useState(false);

const onConfirm = remainingProps.type === 'confirm-button'
? remainingProps.onConfirm
: undefined;

const handleLinkClick = useCallback(
() => {
Expand All @@ -70,6 +84,24 @@ function DropdownMenuItem<NAME>(props: Props<NAME>) {
[setShowDropdown, persist, onClick, remainingProps.type],
);

const handleConfirmButtonClick = useCallback(
(name: NAME, e: React.MouseEvent<HTMLButtonElement>) => {
handleButtonClick(name, e);
setShowConfirmation(true);
},
[handleButtonClick],
);

const handleConfirmClick = useCallback(
(name: NAME) => {
setShowConfirmation(false);
if (isDefined(onConfirm)) {
onConfirm(name);
}
},
[onConfirm],
);

if (remainingProps.type === 'link') {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -98,37 +130,130 @@ function DropdownMenuItem<NAME>(props: Props<NAME>) {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type: _,
styleVariant = 'transparent',
after,
before,
children,
className,
disabled,
elementRef,
spacing,
spacingOffset = -3,
textSize,
withoutPadding,
...otherProps
} = remainingProps;

// NOTE: composing RawButton + ButtonLayout to preserve the old
// (text, transparent) menu item look, which is not in Button's
// curated variant set
return (
<Button
<RawButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
styleVariant={styleVariant}
className={styles.menuItem}
onClick={handleButtonClick}
withFullWidth={!withoutFullWidth}
/>
disabled={disabled}
>
<ButtonLayout
className={className}
elementRef={elementRef}
colorVariant="text"
styleVariant="transparent"
spacing={spacing}
spacingOffset={spacingOffset}
withoutPadding={withoutPadding}
withFullWidth={!withoutFullWidth}
before={before}
after={after}
textSize={textSize}
disabled={disabled}
>
{children}
</ButtonLayout>
</RawButton>
);
}

if (remainingProps.type === 'confirm-button') {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type: _,
styleVariant = 'transparent',
after,
before,
children,
className,
confirmHeading = strings.confirmation,
confirmMessage = strings.confirmMessage,
disabled,
elementRef,
name,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onConfirm: _unusedOnConfirm,
spacing,
spacingOffset = -3,
textSize,
withoutPadding,
...otherProps
} = remainingProps;

// NOTE: composing RawButton + ButtonLayout to preserve the old
// (text, transparent) menu item look, which is not in
// ConfirmButton's curated variant set; the confirmation dialog
// is replicated from ConfirmButton
return (
<ConfirmButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
styleVariant={styleVariant}
onClick={handleButtonClick}
withFullWidth={!withoutFullWidth}
/>
<>
<RawButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
name={name}
className={styles.menuItem}
onClick={handleConfirmButtonClick}
disabled={disabled}
>
<ButtonLayout
className={className}
elementRef={elementRef}
colorVariant="text"
styleVariant="transparent"
spacing={spacing}
spacingOffset={spacingOffset}
withoutPadding={withoutPadding}
withFullWidth={!withoutFullWidth}
before={before}
after={after}
textSize={textSize}
disabled={disabled}
>
{children}
</ButtonLayout>
</RawButton>
{showConfirmation && (
<Dialog
heading={confirmHeading}
closeOnEscape={false}
size="sm"
footerActions={(
<ListView spacing="sm">
<Button
name={false}
onClick={setShowConfirmation}
>
{strings.buttonCancel}
</Button>
<Button
name={name}
variant="primary"
onClick={handleConfirmClick}
>
{strings.buttonOk}
</Button>
</ListView>
)}
>
{confirmMessage}
</Dialog>
)}
</>
);
}
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/components/DropdownMenuItem/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.menu-item {
display: contents;
}
Loading
Loading